[comp.sources.unix] v11i049: Tektronix4014 to PostScript filter

rs@uunet.UUCP (09/17/87)

Submitted-by: Michael S. Fischbein <msf@ames-nas.arpa>
Posting-number: Volume 11, Issue 49
Archive-name: tek2ps

Here's my first contribution to the wonderful world of public domain source
codes, with a tip of the hat to the GNU Manifesto.

I've used several of the postings, so I thought I'd deliver this small piece
up to the net.  We have several plot programs that have been developed or
customized over the years, but they ALL support 4014s.  So, we now have a
fast way to get good quality hardcopy.  Hope some other people can use it

Just thought I'd explain one code peculiarity: the use of fprintf(stdout,....
throughout is because I was originally going to add a -o outputfile option.
Redirecting stdout seemed a better choice once it was written.
		mike

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Contents:  Makefile README pstek.pro tek.h tek2ps.c tek2ps.doc
 
echo x - Makefile
sed 's/^@//' > "Makefile" <<'@//E*O*F Makefile//'
# %W% Makefile
CFLAGS=-g

OBJ=tek2ps.o

all: tek2ps

tek2ps: ${OBJ} tek.h
	cc ${CFLAGS} -o $@ ${OBJ}

@//E*O*F Makefile//
 
echo x - README
sed 's/^@//' > "README" <<'@//E*O*F README//'
This program will filter tektronix style plots to postscript output,
letting you use your laserprinter instead of the thermal printer that
tektronix has been selling since the dark ages.

I haven't been able to really test the defocussed Z axis stuff (no plot
programs that we have use it).

I have put as much as possible into the prolog file so a user can fairly
easily change the defaults I like to those he or she likes.  The tough
part about figuring out how this works is understanding the Tek addressing
schemes.  Other than that, it is pretty simple.  There is one `undocumented'
flag: -d, which enables more verbose messages when an otherwise ignored
character is encountered.  Usually, these are padding characters (4010s
are very slow by modern standards).

BE SURE TO EDIT THE PROGRAM (tek2ps.c, find def_pro) AND DOCUMENTATION
(tek2ps.doc, about the tenth line) to put the correct location
for the default prolog file in!

Any corrections or additions?  Let me know at:

Michael Fischbein                 msf@prandtl.arpa
                                  msf@prandtl.arc.nasa.gov
				  ...!seismo!decuac!csmunix!icase!msf

@//E*O*F README//
 
echo x - pstek.pro
sed 's/^@//' > "pstek.pro" <<'@//E*O*F pstek.pro//'
%!PS-Adobe-1.0
%%DocumentFonts:  Courier 
%%Title: pstek prolog file, version 1.0 @(#)pstek.pro	1.10
%%Creator: Michael Fischbein
%% Copyright 1987 Michael Fischbein.  Commercial reproduction prohibited;
%% non-profit reproduction and distribution encouraged.
%%CreationDate: %?% 5 June 1987
%%For: tektronics-to-PS converter
%%BoundingBox: 40 40 540 540
%%EndComments

% Font definitions (make 3/4 functions to avoid scaling if not needed)
/FntH /Courier findfont 80 scalefont def
/DFntL { /FntL /Courier findfont 73.4 scalefont def } def
/DFntM { /FntM /Courier findfont 50.2 scalefont def } def
/DFntS { /FntS /Courier findfont 44 scalefont def } def

% tektronix line styles
/NV { [] 0 setdash } def	% normal vectors
/DV { [8] 0 setdash } def	% dotted vectors
/DDV { [8 8 32 8] 0 setdash } def	% dot-dash vectors
/SDV { [32 8] 0 setdash } def	% short-dash vectors
/LDV { [64 8] 0 setdash } def	% long-dash vectors

% Defocussed Z axis and Focussed Z axis
/DZ { .5 setgray } def
/FZ {  0 setgray } def

/PR	% char x y -> -  prints character
{	moveto show } def

/NP	% - -> - new page
% change default scale and orentation to match tek's
{	572 40 translate	% leave a border
	90 rotate
	% .71707 .692308 scale	% 0-1023X, 0-780Y
	.1730769 .17626953 scale	%0-4096X, 0-3120Y
} def

/DP	% tsizey -> - erase and home
{	clippath 1 setgray fill
	0 setgray
	0 exch moveto
} def

FntH  setfont

NP

@//E*O*F pstek.pro//
 
echo x - tek.h
sed 's/^@//' > "tek.h" <<'@//E*O*F tek.h//'
/* t2ps header file @(#)tek.h	1.10 (Copyright) Michael Fischbein */
/* Copyright 1987 Michael Fischbein.  Commercial reproduction prohibited; */
/* non-profit reproduction and distribution encouraged. */
#define XDIM 4096
#define YDIM 3120

#define FALSE 0
#define TRUE (~FALSE)

/* 4014 modes */
#define ALPHA 0
#define GRAPH 1
#define INCRE 2
#define LCEMD 3
#define PTPLT 4

/* type sizes (char/line, or lines/page)  (for 12bit) */
#define CHUGEX 55
#define CLARGEX 51
#define CMEDX 34
#define CSMALLX 31
#define CHUGEY 89
#define CLARGEY 82
#define CMEDY 54
#define CSMALLY 49

/* ASCII */
#define NUL 0
#define SOH 1
#define STX 2
#define ETX 3
#define EOT 4
#define ENQ 5
#define ACK 6
#define BEL 7
#define BS  8
#define HT  9
#define LF  10
#define VT  11
#define FF  12
#define NL  13
#define CR  13
#define SO  14
#define SI  15
#define DLE 16
#define DC1 17
#define DC2 18
#define DC3 19
#define DC4 20
#define NAK 21
#define SYN 22
#define ETB 23
#define CAN 24
#define EM  25
#define SUB 26
#define ESC 27
#define FS  28
#define GS  29
#define RS  30
#define US  31
#define SPACE 32
#define DEL 127

@//E*O*F tek.h//
 
echo x - tek2ps.c
sed 's/^@//' > "tek2ps.c" <<'@//E*O*F tek2ps.c//'
#include <stdio.h>
#include <ctype.h>
#include "tek.h"

extern char	*optarg;
extern int	optind;
extern double	atof();

/* output filter for tektronics 4010-graphics files to generate postscript
 * files.  This does a simple-minded byte by byte translation to predefined
 * PS routines, contained in a prolog file.
 * Defaults:
 * input:	stdin
 * output:	stdout
 * prolog:	~msf/tek2ps/pstek.pro
 */

main(argc, argv)
	int	argc;
	char	*argv[];
{
#ifndef lint
	static char	sccsid[]=" @(#)t2p.c	1.10 tek2ps (Copyright) M. Fischbein  Commercial reproduction prohibited; non-profit reproduction and distribution encouraged.";
#endif
	static char	def_pro[]="/mnt/cmb/msf/tek2ps/pstek.pro";
	char		*pro_fn = def_pro;
	FILE		*pro_fp, *infile = stdin;
	int		c, mode=ALPHA, tsizex=CHUGEX, tsizey=CHUGEY, oldmode;
	int		cx=0, cy=YDIM-CHUGEY, leftmargin=0;
	int		used_large=FALSE, used_med=FALSE, used_small=FALSE;
	int		dark_vector, HiX=0, HiY=0, LoY=0, LoX=0, BX=0, BY=0;
	int		gotLoY=FALSE, debug=FALSE, beamon, pr_on_er=FALSE;
	double		scale_factor=1.0;

	/* first, parse command line */
	while ((c = getopt (argc, argv, "s:p:de")) != EOF) {
		switch (c) {
		case 'p' :	/* use custom prolog */
			pro_fn = optarg;
			break;

		case 'd' :	/* toggle debugging */
			if (debug) {
				debug = FALSE;
			} else {
				debug = TRUE;
			}
			break;

		case 'e':	/* turn on print-before-erase */
				/* and erase-after-print */
			pr_on_er = TRUE;
			break;

		case 's':	/* set scale option */
			scale_factor = atof(optarg)/100.;
			break;
		default:
			fprintf(stderr, "Usage: %s [-p prologfile] [-de] [input]\n", argv[0]);
			
		}
	}

	/* next, copy the prolog file */
	if( (pro_fp = fopen(pro_fn, "r")) == NULL) {
		fprintf(stderr, "Can't open prolog file %s\n", pro_fn);
		exit(1);
	}
	while ((c = getc(pro_fp)) != EOF) {
		putc((char) c, stdout);
	}
	fclose(pro_fp);

	/* check for named file (loop if more than one) */
	do {	/* should indent here */
	if (optind < argc) {
		if ((infile = fopen( argv[optind], "r")) == NULL) {
			fprintf(stderr, "Can't open input file %s\n", argv[optind]);
			exit(1);
		}
	}
	/* check for scale factor */
	if (scale_factor != 1.0) {	/* I know floating pt equality is
					 * a bad idea, but if the option is not
					 * present I explicity initialize to
					 * 1.0, so the bit pattern should be
					 * identical portably
					 */
		fprintf(stdout,"%f %f scale\n", scale_factor, scale_factor);
	}

	/* Now,  read a byte and figure out what to do about it */
	while ((c = getc(infile)) != EOF) { /* should indent below */
	switch (mode) {
	case ALPHA:
		if ( isgraph((char) c) ) {	/* normal printing char */
			/* put char at current position */
			fprintf(stdout,"(%c) %d %d PR\n", (char) c, cx, cy);
			/* increment current postion, wrt type size */
			if ((cx += tsizex) > XDIM) {
				/* new line or margin */
				if((cy -= tsizey) < 0) {
					cy = YDIM - tsizey;
					leftmargin = leftmargin ? 0 : XDIM/2;
				}
				cx = leftmargin;
			}
		} else { /* isn't normal printing character */
		switch (c) {
		case ( HT ):
		case ( SPACE ):
			if ((cx += tsizex) > XDIM) {
				/* new line or margin */
				if((cy -= tsizey) < 0) {
					cy = YDIM - tsizey;
					leftmargin = leftmargin ? 0 : XDIM/2;
				}
				cx = leftmargin;
			}
			break;

		case ( CR ):
		case ( LF ):
			if ((cy -= tsizey) < 0) {
				cy = YDIM - tsizey;
				leftmargin = leftmargin ? 0 : XDIM/2;
			}
			cx = leftmargin;
			break;

		case ( BS ):
			if ((cx -= tsizex) < 0) {
				cx = XDIM;
			}
			break;

		case ( VT ):
			if ((cy += tsizey) > YDIM) {
				cy = 0;
			}
			break;

		case ( GS ):
			mode = GRAPH;
			dark_vector = TRUE;
			break;

		case ( RS ):
			mode = INCRE;
			break;
		
		case ( FS ):
			mode = PTPLT;
			break;

		case ( US ):	/* put in ALPHA mode, already there */
		case ( BEL ):
		case ( SYN ):	/* padding character, ignore*/
		case ( NUL ):	/* padding character, ignore*/
			break;

		case ( ESC ):
			oldmode = ALPHA;
			mode = LCEMD;
			break;

		default :
			if (debug) fprintf(stderr, "Unknown ALPHA mode character 0x%02x\n", c);
			break;
		}	/* end of switch on non-printing char in ALPHA mode */
		}	/* end of printing vs non-printing char in ALPHA mode */
	break;	/* end of ALPHA mode */

	case PTPLT:
	case GRAPH:
		if ( (char) c > US ) {	/* first, handle vector case */
			if( (char) c < '@') { /* High byte */
				if (gotLoY) {	/* must be HiX */
					HiX = ((char) c & 0x1f) << 7;
				} else {	/* must be HiY */
					HiY = ((char) c & 0x1f) << 7;
				}
			} else if ( (char) c < '`') {	/* Lo X: plot */
				gotLoY = FALSE;
				LoX = (c & 0x1f) << 2;
				/* now actually do a plot */
				if (dark_vector) {
					dark_vector = FALSE;
					cx = HiX + LoX + BX;
					cy = HiY + LoY + BY;
					fprintf(stdout,"%d %d moveto\n",cx, cy);
				} else { /* draw the line */
					cx = HiX + LoX + BX;
					cy = HiY + LoY + BY;
					if (mode == GRAPH)  {
						fprintf(stdout,"%d %d lineto stroke %d %d moveto\n", cx, cy, cx, cy);
					} else {	/* mode == PTPLT */
						fprintf(stdout,"%d %d moveto %d %d 1 0 360 arc\n", cx, cy, cx, cy);
					}
				}
			} else {	/* Lo Y or extra byte */
				if (gotLoY) {	/* previous LoY => extra byte */
					BX = (LoY & 0x0c) >> 2;
					BY = (LoY & 0x30) >> 4;
					LoY = (c & 0x1f) << 2;
					/* gotLoY stays TRUE */
				} else {	/* assume is LoY */
					LoY = (c & 0x1f) << 2;
					gotLoY = TRUE;
				}
			}
		} else /* end of GRAPH mode vector address parsing*/
		/* so, it isn't a vector address */
		switch ( c ) {
		case ( NUL ): /* padding */
		case ( SYN ): /* padding */
		case ( BEL ): /* ignore */
			break;

		case ( LF ):
			cy -= tsizey;
			fprintf(stdout, "%d %d moveto\n", cx, cy);
			break;

		case ( CR ):
			mode = ALPHA;
			leftmargin = 0;
			break;

		case ( RS ):
			mode = INCRE;
			break;

		case ( FS ):
			fprintf(stderr,"special point plot not implemented\n");
			break;

		case ( GS ):
			dark_vector = TRUE;
			break;

		case ( US ):
			mode = ALPHA;
			break;

		case ( ESC ):
			oldmode = GRAPH;
			mode = LCEMD;
			break;

		default :
			if (debug) fprintf(stderr, "Unknown GRAPH mode character 0x%02x\n", c);
			break;
		}	/* end of switch on non-vector char in GRAPH mode */
	break;	/* end of GRAPH mode */

	case INCRE:
		/* could do with bit masking and check for control, */
		/* but this is is simpler. (Let the compiler work). */
		switch ( c ) {
		case ( 32 ):
			fprintf(stdout,"stroke %d %d moveto\n", cx, cy);
			beamon = FALSE;
			break;

		case ( 80 ):
			fprintf(stdout,"%d %d moveto\n", cx, cy);
			beamon = TRUE;
			break;

		case ( 68 ):	/* N */
			if (++cy > YDIM) cy = YDIM;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;
			
		case ( 69 ):	/* NE */
			if (++cy > YDIM) cy = YDIM;
			if (++cx > XDIM) cx = XDIM;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 65 ):	/* E */
			if (++cx > XDIM) cx = XDIM;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 73 ):	/* SE */
			if (--cy < 0) cy = 0;
			if (++cx > XDIM) cx = XDIM;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 72 ):	/* S */
			if (--cy < 0) cy = 0;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 74 ):	/* SW */
			if (--cy < 0) cy = 0;
			if (--cx < 0) cx = 0;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 66 ):	/* W */
			if (--cx < 0) cx = 0;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( 70 ):	/* NW */
			if (++cy > YDIM) cy = YDIM;
			if (--cx < 0) cx = 0;
			if (beamon) fprintf(stdout,"%d %d lineto\n", cx, cy);
			break;

		case ( ESC ):
			if (beamon) fprintf(stdout,"stroke %d %d moveto\n", cx, cy);
			oldmode = INCRE;
			mode = LCEMD;
			break;

		case ( FS ):
			if (beamon) fprintf(stdout,"stroke %d %d moveto\n", cx, cy);
			mode = PTPLT;
			break;

		case ( GS ):
			if (beamon) fprintf(stdout,"stroke %d %d moveto\n", cx, cy);
			mode = GRAPH;
			dark_vector = TRUE;
			break;

		case ( RS ):
			break;

		case ( US ):
			if (beamon) fprintf(stdout,"stroke %d %d moveto\n", cx, cy);
			mode = ALPHA;
			break;

		default:
			if (debug) fprintf(stderr,"Unknown incremental mode character 0x%02x\n", c);
			break;
		}	/* end of INCR switch on c */
	break;	/* end of INCREmental mode */

	case LCEMD:
		if ((c > 95) && (c < 117)) {
			/* set Z axis */
			if ((char) c & 0x08) {
				fprintf(stdout,"DZ\n");
			} else {
				fprintf(stdout,"FZ\n");
			}
			/* set vector type */
			switch ((char) c & 0x07) {
			case 0:	/* normal vectors */
				fprintf(stdout,"NV\n");
				break;

			case 1:	/* dotted vectors */
				fprintf(stdout,"DV\n");
				break;

			case 2:	/* dot-dash vectors */
				fprintf(stdout,"DDV\n");
				break;

			case 3:	/* short-dash vectors */
				fprintf(stdout,"SDV\n");
				break;

			case 4:	/* long-dash vectors */
				fprintf(stdout,"LDV\n");
				break;

			default:	/* error */
				if (debug) fprintf(stderr,"Unknown beam selector 0x%02x\n", (char) c);
				break;
			}
		} else {
			switch ( c ) {
			case ( FF ):	/* erase */
				if (pr_on_er) {
					fprintf(stdout,"showpage\nNP\n");
					if (scale_factor != 1.0) {
						fprintf(stdout,"%f %f scale\n", scale_factor, scale_factor);
					}
				}
				fprintf(stdout, "%d DP\n", tsizey);
				cx = 0; cy = YDIM - tsizey;
				break;

			case ( '8' ):
				/* default size */
				fprintf(stdout, "FntH setfont\n");
				tsizex = CHUGEX; tsizey = CHUGEY;
				break;

			case ( '9' ):
				if (! used_large ) {
					used_large = TRUE;
					fprintf(stdout,"DFntL\n");
				}
				fprintf(stdout, "FntL setfont\n");
				tsizex = CLARGEX; tsizey = CLARGEY;
				break;

			case ( ':' ):
				tsizex = CMEDX; tsizey = CMEDY;
				if (! used_med ) {
					used_med = TRUE;
					fprintf(stdout,"DFntM\n");
				}
				fprintf(stdout, "FntM setfont\n");
				break;

			case ( ';' ):
				tsizex = CSMALLX; tsizey = CSMALLY;
				if (! used_small ) {
					used_small = TRUE;
					fprintf(stdout,"DFntS\n");
				}
				fprintf(stdout, "FntS setfont\n");
				break;

			case ( BS ):
				if ((cx -= tsizex) < 0) {
					cx = XDIM;
				}
				break;

			case ( HT ):
			case ( SPACE ):
				if ((cx += tsizex) > XDIM) {
					/* new line or margin */
					if((cy -= tsizey) < 0) {
						cy = YDIM - tsizey;
						leftmargin = leftmargin ? 0 : XDIM/2;
					}
					cx = leftmargin;
				}
				break;

			case ( VT ):
				if ((cy += tsizey) > YDIM) {
					cy = 0;
				}
				break;

			case ( GS ):
				mode = GRAPH;
				dark_vector = TRUE;
				break;

			case ( LF ):
			case ( CR ):
			case ( DEL ):
			case ( NUL ):
			case ( ESC ):
			case ( BEL ):
			case ( SYN ):
				ungetc( (char) ESC, infile);
				break;

			case ( ETB ):	/* make copy: print & start new page */
				if (pr_on_er) {
					fprintf(stdout, "showpage\nNP\n");
					if (scale_factor != 1.0) {
						fprintf(stdout,"%f %f scale\n", scale_factor, scale_factor);
					}
					cx = 0; cy = YDIM - tsizey;
				} else {
					fprintf(stdout, "copypage\n");
				}
				break;

			case ( SO ):
			case ( SI ):
				if (debug) fprintf(stderr, "No alternate character set implemented\n");
				break;

			case ( CAN ):
			case ( SUB ):
			case ( ENQ ):
				if (debug) fprintf(stderr, "GIN and BYPASS  modes not implemented\n");
				break;

			case ( '?' ):
				ungetc( (char) DEL, infile);
				break;

			default :
				if (debug) fprintf(stderr, "Unknown LCE mode character 0x%02x ignored\n", c);
				break;
			}	/* end of LCE mode switch */
		}
	mode = oldmode;
	break;

	default:
		if (debug) fprintf(stderr, "Unknown major mode %d\n", mode);
		break;
	} /* end of mode switch */
	} /* end of main input loop */
	fprintf(stdout,"showpage\n");
	} while ( ++optind < argc );
}

@//E*O*F tek2ps.c//
 
echo x - tek2ps.doc
sed 's/^@//' > "tek2ps.doc" <<'@//E*O*F tek2ps.doc//'
@.EV
@.T1 tek2ps 1
@.SH NAME
tek2ps \- convert Tektronix\*R 4014 commands to PostScript\*R
@.SH SYNTAX
tek2ps [-p \fIprologfile\fR] [-e] [-s \fIscale] files
@.SH OPTIONS
@.IP -p
will use the named prologfile instead of the default (currently
/mnt/cmb/msf/tek2ps/tekps.pro).
@.IP -e
will make `erase' (ESC FF) and `hardcopy' (ESC ETB) synonymous: each will
print the current page and then erase it.  The default will erase without
printing or print without erasing.
@.IP -s
will scale the entire plot by the per centage specified.  The default maps
the Tektronix screen to an 8.5 x 11 inch sheet of paper.
@.SH DESCRIPTION
@.PP
Tek2ps will take any output designed to be displayed on a Tektronix 4014
terminal and translate it to PostScript suitable for sending to any PostScript
device.  Some manual editing will probably be required if the PostScript
is to be included in another file.  Tek2ps supports 10 and 12-bit addressing
modes, incremental plots, and defocussed Z axis.  It does
@.I not
support crosshair (GIN) mode or special point plot (6-bit greyscale).
@.PP
Multiple
@.I files
may be specified; if none are listed standard input is read.  Output is to
standard output.
@.SH EXAMPLE
plot -T4014 myplot | tek2ps | lpr -Plw
@.SH SPECIAL CONSIDERATIONS
@.PP
If you place multiple screens in a single file, you must either use the
hardcopy command followed by an erase to prevent overwriting, or the
-e option.
@.SH FILES
/mnt/cmb/msf/tek2ps/tekps.pro
@.SH SEE ALSO
graph(1), plot(1)
@.SH BUGS
Tek2ps will not support crosshair (GIN) mode or special point plot
(6-bit greyscale).
@.SH AUTHOR
Michael Fischbein

@//E*O*F tek2ps.doc//
 
exit 0