Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
/*
2
 *
3
 * postmd - matrix display program for PostScript printers.
4
 *
5
 * A simple program that can be used to display a matrix as a gray scale image on
6
 * a PostScript printer using the image operator. Much of the code was borrowed
7
 * from postdmd, the bitmap display program DMD screen dumps. May help if you have
8
 * a large matix (of floating point numbers) and want a simple way to look for
9
 * patterns.
10
 *
11
 * Matrix elements are a series of floating point numbers arranged in the input
12
 * file in row major order. The actual matrix elements can be preceeded by a simple
13
 * header that sets things like the matrix dimensions, interval list, and possibly
14
 * a window into the matrix that we'll use for display. The dimension statement is
15
 * perhaps the most important. If present it determines the number of rows and
16
 * columns in the matrix. For example, either of the following defines a 50x50
17
 * matrix,
18
 *
19
 *		dimension	50
20
 *		dimension	50x50
21
 *
22
 * If no dimension statement appears in the input file, the matrix is assumed to
23
 * be square, and the number of rows (and columns) is set to the square root of
24
 * the number of elements in the input file.
25
 *
26
 * Each matrix element is mapped into an integer in the range 0 to 255 (actually
27
 * 254) and PostScript's image operator then maps that number into a gray scale
28
 * appropriate for the particular printer. The mapping from the floating point
29
 * matrix elements to integers is accomplished using an interval list that can be
30
 * set using the -i option. The format of the interval string is,
31
 *
32
 *		num1,num2,num3,...,numn
33
 *
34
 * where each num is a floating point number. The list must be given in increasing
35
 * numerical order. A list of n numbers partitions the real line into 2n+1 regions
36
 * given as,
37
 *
38
 *		region1		element < num1
39
 *		region2		element = num1
40
 *		region3		element < num2
41
 *		region4		element = num2
42
 *		   .
43
 *		   .
44
 *		   .
45
 *		region2n	element = numn
46
 *		region2n+1	element > numn
47
 *
48
 * Every number in a region is mapped one integer in the range 0 to 254, and that
49
 * number, when displayed on a printer using the image operator, prints as a square
50
 * filled with a gray shade that reflects the integer that was chosen. 0 maps to
51
 * black and 255 maps to white (which by default will not be used).
52
 *
53
 * The default gray scale gets darker as the region number increases, but can be
54
 * changed by supplying a gray scale list with the -g option or in the optional
55
 * matrix header. The color map is again a comman or space separated list that
56
 * looks like,
57
 *
58
 *		color1,color2, ... ,color2n+1
59
 *
60
 * where color1 applies to region 1 and color2n+1 applies to region2n+1. Each
61
 * number in the list should be an integer between 0 and 255. If less than 2n+1
62
 * colors are given default assignments will be used for missing regions.
63
 *
64
 * The size of the matrix that we can display reasonably well is a function of the
65
 * number of elements in the interval list, paper size, and printer resolution.
66
 * For example a 300dpi printer using 8.5x11 inch paper gives us an image area of
67
 * about 2400x2400 pixels. An interval list of two numbers generates five separate
68
 * regions and will therefore need that many different shades of gray. Since we're
69
 * not using white we'll need to partion our image area into 4x4 pixel squares,
70
 * and that means a 600x600 matrix is about as big as we can go. In practice that's
71
 * optimistic, but the argument illustrates some of the limitations.
72
 *
73
 * A submatrix can be selected to display by windowing into the matrix. The window
74
 * list can be given using the -w option or can be set in the optional header that
75
 * can preceed each matrix.  The list should be a comma or space separated list
76
 * that looks like,
77
 *
78
 *		lower-column, lower-row, upper-column, upper-row
79
 *
80
 * where each element in the list must be a positive integer. Rows and columns in
81
 * the input matrix start at 1. The dimension of the displayed window will be from
82
 * lower-column to upper-column and from lower-row to upper-row inclusive.
83
 *
84
 * The encoding produced by the program is essentially identical to what's done
85
 * by postdmd. See the comments at the beginning of that program if you need more
86
 * details. The prologue also shares much of the same code. 
87
 *
88
 * The PostScript prologue is copied from *prologue before any of the input files
89
 * are translated. The program expects that the following PostScript procedures
90
 * are defined in that file:
91
 *
92
 *	setup
93
 *
94
 *	  mark ... setup -
95
 *
96
 *	    Handles special initialization stuff that depends on how this program
97
 *	    was called. Expects to find a mark followed by key/value pairs on the
98
 *	    stack. The def operator is applied to each pair up to the mark, then
99
 *	    the default state is set up.
100
 *
101
 *	pagesetup
102
 *
103
 *	  page pagesetup -
104
 *
105
 *	    Does whatever is needed to set things up for the next page. Expects
106
 *	    to find the current page number on the stack.
107
 *
108
 *	bitmap
109
 *
110
 *	  columns rows bitmap -
111
 *
112
 *	    Prints the image that's read as a hex string from standard input. The
113
 *	    image consists of rows lines, each of which includes columns elements.
114
 *	    Eight bits per pixel are used to encode the matrix elements.
115
 *
116
 *	labelmatrix
117
 *
118
 *	  matrixname matrixlimits labelmatrix -
119
 *
120
 *	    Prints string matrixname just below the lower left corner of the image
121
 *	    and prints string martixlimits near the lower right corner. Outlines
122
 *	    the entire image with a (one pixel wide) box and then draws tick marks
123
 *	    along the top and left sides of the image. One tick mark is printed
124
 *	    for every ten elements.
125
 *
126
 *	legend
127
 *
128
 *	  n1 ... nN N c1 m1 ... cM mM total regions legend -
129
 *
130
 *	    Prints the legend as a bar graph below the matrix image. n1 ... nN are
131
 *	    strings that represent the interval list. c1 m1 ... cm mM are pairs
132
 *	    that consist of a region's color and the statistics count. Actually
133
 *	    the c's are trivial procedures that just leave a one character string
134
 *	    on the stack when they're executed by image - which is the way the
135
 *	    bar graph is drawn.
136
 *
137
 *	done
138
 *
139
 *	  done
140
 *
141
 *	    Makes sure the last page is printed. Only needed when we're printing
142
 *	    more than one page on each sheet of paper.
143
 *
144
 * Many default values, like the magnification and orientation, are defined in 
145
 * the prologue, which is where they belong. If they're changed (by options), an
146
 * appropriate definition is made after the prologue is added to the output file.
147
 * The -P option passes arbitrary PostScript through to the output file. Among
148
 * other things it can be used to set (or change) values that can't be accessed by
149
 * other options.
150
 *
151
 */
152
 
153
#include <stdio.h>
154
#include <signal.h>
155
#include <ctype.h>
156
#ifdef plan9
157
#define	isascii(c)	((unsigned char)(c)<=0177)
158
#endif
159
#include <sys/types.h>
160
#include <fcntl.h>
161
#include <string.h>
162
 
163
#include "comments.h"			/* PostScript file structuring comments */
164
#include "gen.h"			/* general purpose definitions */
165
#include "path.h"			/* for the prologue */
166
#include "ext.h"			/* external variable declarations */
167
#include "postmd.h"			/* special matrix display definitions */
168
 
169
char	*optnames = "a:b:c:d:g:i:m:n:o:p:w:x:y:A:C:E:J:L:P:R:DI";
170
 
171
char	*prologue = POSTMD;		/* default PostScript prologue */
172
char	*formfile = FORMFILE;		/* stuff for multiple pages per sheet */
173
char	*temp_dir = TEMPDIR;		/* temp directory for copying stdin */
174
 
175
int	formsperpage = 1;		/* page images on each piece of paper */
176
int	copies = 1;			/* and this many copies of each sheet */
177
int	bytespp = 6;			/* bytes per pattern - on output */
178
 
179
int	dostats = ON;			/* permanent statistics flag */
180
int	nxtstat = ON;			/* and the one for the next matrix */
181
 
182
char	*interval = DFLTILIST;		/* string representations of the interval */
183
char	*colormap = NULL;		/* color map */
184
char	*window = NULL;			/* and window lists */
185
char	*matrixname = "pipe.end";	/* name for the next plot */
186
 
187
Ilist	ilist[128];			/* active interval list and color map */
188
int	next = 0;			/* one past the last element in ilist[] */
189
int	regions;			/* an index assigned to the last region */
190
int	wlist[4];			/* upper left and lower right corners */
191
 
192
int	page = 0;			/* last page we worked on */
193
int	printed = 0;			/* and the number of pages printed */
194
 
195
int	dfltrows = 0;			/* default rows */
196
int	dfltcols = 0;			/* and columns - changed by -d option */
197
int	rows;				/* real number of rows */
198
int	columns;			/* and columns in the matrix */
199
int	patcount = 0;			/* will be set to columns * rows */
200
 
201
double	element;			/* next matrix element */
202
 
203
char	*raster = NULL;			/* next raster line */
204
char	*rptr;				/* next free byte in raster */
205
char	*eptr;				/* one past the last byte in raster */
206
 
207
FILE	*fp_in = stdin;			/* read from this file */
208
FILE	*fp_out = stdout;		/* and write stuff here */
209
FILE	*fp_acct = NULL;		/* for accounting data */
210
 
211
/*****************************************************************************/
212
 
213
main(agc, agv)
214
 
215
    int		agc;
216
    char	*agv[];
217
 
218
{
219
 
220
/*
221
 *
222
 * Bitmap display program for matrices. Only one matrix is allowed per input file,
223
 * and each one will be displayed on a page by itself. Input files consist of an
224
 * optional header followed by floating point numbers that represent the matrix
225
 * elements - in row major order.
226
 *
227
 */
228
 
229
    argc = agc;				/* other routines may want them */
230
    argv = agv;
231
 
232
    prog_name = argv[0];		/* really just for error messages */
233
 
234
    init_signals();			/* sets up interrupt handling */
235
    header();				/* PostScript header comments */
236
    options();				/* handle the command line options */
237
    setup();				/* for PostScript */
238
    arguments();			/* followed by each input file */
239
    done();				/* print the last page etc. */
240
    account();				/* job accounting data */
241
 
242
    exit(x_stat);			/* not much could be wrong */
243
 
244
}   /* End of main */
245
 
246
/*****************************************************************************/
247
 
248
init_signals()
249
 
250
{
251
 
252
/*
253
 *
254
 * Make sure we handle interrupts.
255
 *
256
 */
257
 
258
    if ( signal(SIGINT, interrupt) == SIG_IGN )  {
259
	signal(SIGINT, SIG_IGN);
260
	signal(SIGQUIT, SIG_IGN);
261
	signal(SIGHUP, SIG_IGN);
262
    } else {
263
	signal(SIGHUP, interrupt);
264
	signal(SIGQUIT, interrupt);
265
    }   /* End else */
266
 
267
    signal(SIGTERM, interrupt);
268
    signal(SIGFPE, interrupt);
269
 
270
}   /* End of init_signals */
271
 
272
/*****************************************************************************/
273
 
274
header()
275
 
276
{
277
 
278
    int		ch;			/* return value from getopt() */
279
    int		old_optind = optind;	/* for restoring optind - should be 1 */
280
 
281
/*
282
 *
283
 * Scans the option list looking for things, like the prologue file, that we need
284
 * right away but could be changed from the default. Doing things this way is an
285
 * attempt to conform to Adobe's latest file structuring conventions. In particular
286
 * they now say there should be nothing executed in the prologue, and they have
287
 * added two new comments that delimit global initialization calls. Once we know
288
 * where things really are we write out the job header, follow it by the prologue,
289
 * and then add the ENDPROLOG and BEGINSETUP comments.
290
 *
291
 */
292
 
293
    while ( (ch = getopt(argc, argv, optnames)) != EOF )
294
	if ( ch == 'L' )
295
	    prologue = optarg;
296
	else if ( ch == '?' )
297
	    error(FATAL, "");
298
 
299
    optind = old_optind;		/* get ready for option scanning */
300
 
301
    fprintf(stdout, "%s", CONFORMING);
302
    fprintf(stdout, "%s %s\n", VERSION, PROGRAMVERSION);
303
    fprintf(stdout, "%s %s\n", DOCUMENTFONTS, ATEND);
304
    fprintf(stdout, "%s %s\n", PAGES, ATEND);
305
    fprintf(stdout, "%s", ENDCOMMENTS);
306
 
307
    if ( cat(prologue) == FALSE )
308
	error(FATAL, "can't read %s", prologue);
309
 
310
    fprintf(stdout, "%s", ENDPROLOG);
311
    fprintf(stdout, "%s", BEGINSETUP);
312
    fprintf(stdout, "mark\n");
313
 
314
}   /* End of header */
315
 
316
/*****************************************************************************/
317
 
318
options()
319
 
320
{
321
 
322
    int		ch;			/* return value from getopt() */
323
 
324
/*
325
 *
326
 * Reads and processes the command line options. Added the -P option so arbitrary
327
 * PostScript code can be passed through. Expect it could be useful for changing
328
 * definitions in the prologue for which options have not been defined.
329
 *
330
 */
331
 
332
    while ( (ch = getopt(argc, argv, optnames)) != EOF )  {
333
	switch ( ch )  {
334
	    case 'a':			/* aspect ratio */
335
		    fprintf(stdout, "/aspectratio %s def\n", optarg);
336
		    break;
337
 
338
	    case 'b':			/* bytes per pattern - on output */
339
		    bytespp = atoi(optarg);
340
		    break;
341
 
342
	    case 'c':			/* copies */
343
		    copies = atoi(optarg);
344
		    fprintf(stdout, "/#copies %s store\n", optarg);
345
		    break;
346
 
347
	    case 'd':			/* default matrix dimensions */
348
		    sscanf(optarg, "%dx%d", &dfltrows, &dfltcols);
349
		    break;
350
 
351
	    case 'g':			/* set the colormap (ie. grayscale) */
352
		    colormap = optarg;
353
		    break;
354
 
355
	    case 'i':			/* matrix element interval list */
356
		    interval = optarg;
357
		    break;
358
 
359
	    case 'm':			/* magnification */
360
		    fprintf(stdout, "/magnification %s def\n", optarg);
361
		    break;
362
 
363
	    case 'n':			/* forms per page */
364
		    formsperpage = atoi(optarg);
365
		    fprintf(stdout, "%s %s\n", FORMSPERPAGE, optarg);
366
		    fprintf(stdout, "/formsperpage %s def\n", optarg);
367
		    break;
368
 
369
	    case 'o':			/* output page list */
370
		    out_list(optarg);
371
		    break;
372
 
373
	    case 'p':			/* landscape or portrait mode */
374
		    if ( *optarg == 'l' )
375
			fprintf(stdout, "/landscape true def\n");
376
		    else fprintf(stdout, "/landscape false def\n");
377
		    break;
378
 
379
	    case 'w':			/* set the window */
380
		    window = optarg;
381
		    break;
382
 
383
	    case 'x':			/* shift things horizontally */
384
		    fprintf(stdout, "/xoffset %s def\n", optarg);
385
		    break;
386
 
387
	    case 'y':			/* and vertically on the page */
388
		    fprintf(stdout, "/yoffset %s def\n", optarg);
389
		    break;
390
 
391
	    case 'A':			/* force job accounting */
392
	    case 'J':
393
		    if ( (fp_acct = fopen(optarg, "a")) == NULL )
394
			error(FATAL, "can't open accounting file %s", optarg);
395
		    break;
396
 
397
	    case 'C':			/* copy file straight to output */
398
		    if ( cat(optarg) == FALSE )
399
			error(FATAL, "can't read %s", optarg);
400
		    break;
401
 
402
	    case 'E':			/* text font encoding */
403
		    fontencoding = optarg;
404
		    break;
405
 
406
	    case 'L':			/* PostScript prologue file */
407
		    prologue = optarg;
408
		    break;
409
 
410
	    case 'P':			/* PostScript pass through */
411
		    fprintf(stdout, "%s\n", optarg);
412
		    break;
413
 
414
	    case 'R':			/* special global or page level request */
415
		    saverequest(optarg);
416
		    break;
417
 
418
	    case 'D':			/* debug flag */
419
		    debug = ON;
420
		    break;
421
 
422
	    case 'I':			/* ignore FATAL errors */
423
		    ignore = ON;
424
		    break;
425
 
426
	    case '?':			/* don't understand the option */
427
		    error(FATAL, "");
428
		    break;
429
 
430
	    default:			/* don't know what to do for ch */
431
		    error(FATAL, "missing case for option %c\n", ch);
432
		    break;
433
	}   /* End switch */
434
    }   /* End while */
435
 
436
    argc -= optind;			/* get ready for non-option args */
437
    argv += optind;
438
 
439
}   /* End of options */
440
 
441
/*****************************************************************************/
442
 
443
setup()
444
 
445
{
446
 
447
/*
448
 *
449
 * Handles things that must be done after the options are read but before the
450
 * input files are processed.
451
 *
452
 */
453
 
454
    writerequest(0, stdout);		/* global requests eg. manual feed */
455
    setencoding(fontencoding);
456
    fprintf(stdout, "setup\n");
457
 
458
    if ( formsperpage > 1 )  {
459
	if ( cat(formfile) == FALSE )
460
	    error(FATAL, "can't read %s", formfile);
461
	fprintf(stdout, "%d setupforms\n", formsperpage);
462
    }	/* End if */
463
 
464
    fprintf(stdout, "%s", ENDSETUP);
465
 
466
}   /* End of setup */
467
 
468
/*****************************************************************************/
469
 
470
arguments()
471
 
472
{
473
 
474
/*
475
 *
476
 * Makes sure all the non-option command line arguments are processed. If we get
477
 * here and there aren't any arguments left, or if '-' is one of the input files
478
 * we'll process stdin.
479
 *
480
 */
481
 
482
    if ( argc < 1 )
483
	matrix();
484
    else  {				/* at least one argument is left */
485
	while ( argc > 0 )  {
486
	    matrixname = *argv;
487
	    if ( strcmp(*argv, "-") == 0 )  {
488
		fp_in = stdin;
489
		matrixname = "pipe.end";
490
	    } else if ( (fp_in = fopen(*argv, "r")) == NULL )
491
		error(FATAL, "can't open %s", *argv);
492
	    matrix();
493
	    if ( fp_in != stdin )
494
		fclose(fp_in);
495
	    argc--;
496
	    argv++;
497
	}   /* End while */
498
    }   /* End else */
499
 
500
}   /* End of arguments */
501
 
502
/*****************************************************************************/
503
 
504
done()
505
 
506
{
507
 
508
/*
509
 *
510
 * Finished with all the input files, so mark the end of the pages, make sure the
511
 * last page is printed, and restore the initial environment.
512
 *
513
 */
514
 
515
    fprintf(stdout, "%s", TRAILER);
516
    fprintf(stdout, "done\n");
517
    fprintf(stdout, "%s %d\n", PAGES, printed);
518
 
519
    if ( temp_file != NULL )
520
	unlink(temp_file);
521
 
522
}   /* End of done */
523
 
524
/*****************************************************************************/
525
 
526
account()
527
 
528
{
529
 
530
/*
531
 *
532
 * Writes an accounting record to *fp_acct provided it's not NULL. Accounting
533
 * is requested using the -A or -J options.
534
 *
535
 */
536
 
537
    if ( fp_acct != NULL )
538
	fprintf(fp_acct, " print %d\n copies %d\n", printed, copies);
539
 
540
}   /* End of account */
541
 
542
/*****************************************************************************/
543
 
544
matrix()
545
 
546
{
547
 
548
    int		count;			/* pattern repeats this many times */
549
    long	total;			/* expect this many patterns */
550
 
551
/*
552
 *
553
 * Reads a matrix from *fp_in, translates it into a PostScript gray scale image,
554
 * and writes the result on stdout. For now only one matrix is allowed per input
555
 * file. Matrix elements are floating point numbers arranged in row major order
556
 * in the input file. In addition each input file may contain an optional header
557
 * that defines special things like the dimension of the matrix, a window into
558
 * the matrix that will be displayed, and an interval list.
559
 *
560
 * If we're reading from stdin we first make a copy in a temporary file so we can
561
 * can properly position ourselves after we've looked for the header. Originally
562
 * wasn't always making a copy of stdin, but I've added a few things to what's
563
 * accepted in the header and this simplifies the job. An alternative would be
564
 * to always require a header and mark the end of it by some string. Didn't like
565
 * that approach much - may fix things up later.
566
 *
567
 */
568
 
569
    if ( fp_in == stdin )		/* make a copy so we can seek etc. */
570
	copystdin();
571
 
572
    rows = dfltrows;			/* new dimensions for the next matrix */
573
    columns = dfltcols;
574
 
575
    buildilist(interval);		/* build the default ilist[] */
576
    addcolormap(colormap);		/* add the colormap - if not NULL */
577
    setwindow(window);			/* and setup the initial matrix window */
578
    nxtstat = dostats;			/* want statistics? */
579
    getheader();			/* matrix dimensions at the very least */
580
    dimensions();			/* make sure we have the dimensions etc. */
581
 
582
    patcount = 0;
583
    total = rows * columns;
584
 
585
    eptr = rptr + (wlist[2] - wlist[0] + 1);
586
 
587
    redirect(++page);
588
 
589
    fprintf(fp_out, "%s %d %d\n", PAGE, page, printed+1);
590
    fprintf(fp_out, "/saveobj save def\n");
591
    writerequest(printed+1, fp_out);
592
    fprintf(fp_out, "%d %d bitmap\n", wlist[2] - wlist[0] + 1, wlist[3] - wlist[1] + 1);
593
 
594
    while ( patcount != total && fscanf(fp_in, "%f", &element) != EOF )  {
595
	if ( inwindow() ) *rptr++ = mapfloat(element);
596
	if ( ++patcount % columns == 0 )
597
	    if ( inrange() )
598
		putrow();
599
    }	/* End while */
600
 
601
    if ( total != patcount )
602
	error(FATAL, "matrix format error");
603
 
604
    labelmatrix();
605
 
606
    if ( fp_out == stdout ) printed++;
607
 
608
    fprintf(fp_out, "showpage\n");
609
    fprintf(fp_out, "saveobj restore\n");
610
    fprintf(fp_out, "%s %d %d\n", ENDPAGE, page, printed);
611
 
612
}   /* End of matrix */
613
 
614
/*****************************************************************************/
615
 
616
copystdin()
617
 
618
{
619
 
620
    int		fd_out;			/* for the temporary file */
621
    int		fd_in;			/* for stdin */
622
    int		buf[512];		/* buffer for reads and writes */
623
    int		count;			/* number of bytes put in buf */
624
 
625
/*
626
 *
627
 * If we're reading the matrix from stdin and the matrix dimension isn't set by
628
 * a dimension statement at the beginning of the file we'll copy stdin to a
629
 * temporary file and reset *fp_in so reads come from the temp file. Simplifies
630
 * reading the header (if present), but is expensive.
631
 *
632
 */
633
 
634
    if ( temp_file != NULL )		/* been here already */
635
	unlink(temp_file);
636
 
637
    if ( (temp_file = tempnam(temp_dir, "post")) == NULL )
638
	error(FATAL, "can't generate temp file name");
639
 
640
    if ( (fd_out = creat(temp_file, 0660)) == -1 )
641
	error(FATAL, "can't create %s", temp_file);
642
 
643
    fd_in = fileno(stdin);
644
 
645
    while ( (count = read(fd_in, buf, sizeof(buf))) > 0 )
646
	if ( write(fd_out, buf, count) != count )
647
	    error(FATAL, "error writing to %s", temp_file);
648
 
649
    close(fd_out);
650
 
651
    if ( (fp_in = fopen(temp_file, "r")) == NULL )
652
	error(FATAL, "can't open %s", temp_file);
653
 
654
}   /* End of copystdin */
655
 
656
/*****************************************************************************/
657
 
658
getheader()
659
 
660
{
661
 
662
    char	buf[512];		/* temporary string space */
663
    char	*cmap = NULL;		/* remember header colormap list */
664
    long	pos;			/* for seeking back to first element */
665
 
666
/*
667
 *
668
 * Looks for the optional header information at the beginning of the input file,
669
 * reads it if it's there, and sets *fp_in to be just past the header. That should
670
 * be the beginning of the matrix element list. The recognized header keywords are
671
 * dimension, interval, colormap (or grayscale), window, name, and statistics. All
672
 * are optional, but may be useful in a spooling environment when the user doesn't
673
 * doesn't actually run the translator.
674
 *
675
 * The dimension statement specifies the number of rows and columns. For example
676
 * either of the following two lines define a 50 by 50 element matrix,
677
 *
678
 *	dimension	50
679
 *	dimension	50x50
680
 *
681
 * The first integer is the number of rows and the second, if given, is the number
682
 * of columns. If columns are missing from the dimension statement we assume the
683
 * matrix is square.
684
 *
685
 * interval can be used to redefine the interval list used for mapping floating
686
 * point numbers into integers in the range 0 to 254. The string following the
687
 * interval keyword has the same format as the -i option. For example to set the
688
 * interval list to -1, 0, and 1 you can add the line,
689
 *
690
 *	interval	-1,0,1
691
 *
692
 * The numbers are floats given in increasing order, and separated by commas or
693
 * blanks. The last interval list in a header takes precedence.
694
 *
695
 * colormap can be used to redefine the grayscale list.  The string following
696
 * the colormap keyword has the same format as the -g option.  For example
697
 *
698
 *	colormap	0,50,100,150,200,250
699
 * or	grayscale	0,50,100,150,200,250
700
 *
701
 * The window keyword can be used to select a submatrix. The numbers following
702
 * window are the upper left and lower right matix coordinates. May not be
703
 * implemented yet but shouldn't be difficult. For example
704
 *
705
 *	window		10 10 40 40
706
 *
707
 * selects the submatrix with corners at (10, 10) and (40, 40). The edges of the
708
 * window are included in the display.
709
 *
710
 * The name keyword can be used to define the title of the display.  For example,
711
 *
712
 *	name		Plot Of Matrix 1
713
 *
714
 * prints the string "Plot Of Matrix 1" at the top of the page. Everything up to
715
 * the next newline is taken as the name string.
716
 *
717
 */
718
 
719
    pos = ftell(fp_in);
720
 
721
    while ( fscanf(fp_in, "%s", buf) != EOF )  {
722
	if ( strncmp(buf, "dimension", strlen("dimension")) == 0 )
723
	    fscanf(fp_in, "%dx%d", &rows, &columns);
724
	else if ( strncmp(buf, "window", strlen("window")) == 0 )  {
725
	    fgets(buf, sizeof(buf), fp_in);
726
	    setwindow(buf);
727
	} else if ( strncmp(buf, "name", strlen("name")) == 0 )  {
728
	    fgets(buf, sizeof(buf), fp_in);
729
	    matrixname = savestring(buf);
730
	} else if ( strncmp(buf, "colormap", strlen("colormap")) == 0 )  {
731
	    fgets(buf, sizeof(buf), fp_in);
732
	    cmap = savestring(buf);
733
	} else if ( strncmp(buf, "grayscale", strlen("grayscale")) == 0 )  {
734
	    fgets(buf, sizeof(buf), fp_in);
735
	    cmap = savestring(buf);
736
	} else if ( strncmp(buf, "interval", strlen("interval")) == 0 )  {
737
	    fgets(buf, sizeof(buf), fp_in);
738
	    buildilist(buf);
739
	} else if ( strncmp(buf, "statistics", strlen("statistics")) == 0 )  {
740
	    fscanf(fp_in, "%s", buf);
741
	    if ( strcmp(buf, "on") == 0 || strcmp(buf, "ON") == 0 )
742
		nxtstat = ON;
743
	    else nxtstat = OFF;
744
	} else break;
745
	pos = ftell(fp_in);
746
    }	/* End while */
747
 
748
    addcolormap(cmap);			/* must happen last */
749
    fseek(fp_in, pos, 0);		/* back to the start of the matrix */
750
 
751
}   /* End of getheader */
752
 
753
/*****************************************************************************/
754
 
755
dimensions()
756
 
757
{
758
 
759
    char	buf[100];		/* temporary storage for the elements */
760
    long	count = 0;		/* number of elements in the matrix */
761
    long	pos;			/* matrix elements start here */
762
 
763
/*
764
 *
765
 * Need to know the dimensions of the matrix before we can go any farther. If
766
 * rows and columns are still 0 we'll read the entire input file, starting from
767
 * the current position, count the number of elements, take the square root of it,
768
 * and use it as the number of rows and columns. Then we seek back to the start
769
 * of the real matrix, make sure columns is set, and allocate enough memory for
770
 * storing each raster line. After we're certain we've got the number of rows and
771
 * columns we check the window coordinates, and if they're not legitimate they're
772
 * reset to cover the entire matrix.
773
 *
774
 */
775
 
776
    if ( rows == 0 )  {
777
	pos = ftell(fp_in);
778
	while ( fscanf(fp_in, "%s", buf) != EOF )
779
	    count++;
780
	rows = sqrt((double) count);
781
	fseek(fp_in, pos, 0);
782
    }	/* End if */
783
 
784
    if ( columns <= 0 ) columns = rows;
785
 
786
    if ( raster != NULL ) free(raster);
787
 
788
    if ( (rptr = raster = malloc(columns)) == NULL )
789
	error(FATAL, "no memory");
790
 
791
    eptr = rptr + columns;
792
 
793
    if ( rows <= 0 || columns <= 0 )
794
	error(FATAL, "bad matrix dimensions");
795
 
796
    if ( wlist[0] > wlist[2] || wlist[1] > wlist[3] )  {
797
	wlist[0] = wlist[1] = 1;
798
	wlist[2] = columns;
799
	wlist[3] = rows;
800
    }	/* End if */
801
 
802
}   /* End of dimensions */
803
 
804
/*****************************************************************************/
805
 
806
buildilist(list)
807
 
808
    char	*list;			/* use this as the interval list */
809
 
810
{
811
 
812
    static char	*templist = NULL;	/* a working copy of the list */
813
    char	*ptr;			/* next number in *templist */
814
    int		i;			/* loop index - for checking the list */
815
 
816
/*
817
 *
818
 * Reads string *list and builds up the ilist[] that will be used in the next
819
 * matrix. Since strtok() modifies the string it's parsing we make a copy first.
820
 * The format of the interval list is described in detail in the comments at the
821
 * beginning of this program. Basically consists of a comma or space separated
822
 * list of floating point numbers that must be given in increasing numerical order.
823
 * The list determines how floating point numbers are mapped into integers in the
824
 * range 0 to 254.
825
 *
826
 */
827
 
828
    if ( templist != NULL )		/* free the space used by the last list */
829
	free(templist);
830
 
831
    while ( isascii(*list) && isspace(*list) )
832
	list++;
833
 
834
    for ( ptr = list, regions = 3; *ptr != '\0'; ptr++ )  {
835
	if ( *ptr == ',' || *ptr == '/' || isspace(*ptr) )
836
	    regions += 2;
837
	while ( isascii(*ptr) && isspace(*ptr) ) ptr++;
838
    }	/* End for */
839
 
840
    next = 0;
841
    templist = savestring(list);
842
 
843
    ptr = strtok(templist, ",/ \t\n");
844
    while ( ptr != NULL )  {
845
	ilist[next].count = 0;
846
	ilist[next++].color = 254 * (regions - 1 - next) / (regions - 1);
847
	ilist[next].val = atof(ptr);
848
	ilist[next].count = 0;
849
	ilist[next++].color = 254 * (regions - 1 - next) / (regions - 1);
850
	ptr = strtok(NULL, ",/ \t\n");
851
    }	/* End while */
852
 
853
    ilist[next].count = 0;
854
    ilist[next].color = 254 * (regions - 1 - next) / (regions - 1);
855
 
856
    if ( next == 0 )			/* make sure we have a list */
857
	error(FATAL, "missing interval list");
858
 
859
    for ( i = 3; i < next; i += 2 )	/* that's in increasing numerical order */
860
	if ( ilist[i].val <= ilist[i-2].val )
861
	    error(FATAL, "bad interval list");
862
 
863
}   /* End of buildilist */
864
 
865
/*****************************************************************************/
866
 
867
addcolormap(list)
868
 
869
    char	*list;			/* use this color map */
870
 
871
{
872
 
873
    static char	*templist = NULL;	/* a working copy of the color list */
874
    char	*ptr;			/* next color in *templist */
875
    int		i = 0;			/* assigned to this region in ilist[] */
876
 
877
/*
878
 *
879
 * Assigns the integers in *list to the color field for the regions defined in
880
 * ilist[]. Assumes ilist[] has already been setup.
881
 *
882
 */
883
 
884
    if ( list != NULL )  {
885
	if ( templist != NULL )
886
	    free(templist);
887
	templist = savestring(list);
888
 
889
	ptr = strtok(templist, ",/ \t\n");
890
	while ( ptr != NULL )  {
891
	    ilist[i++].color = atoi(ptr) % 256;
892
	    ptr = strtok(NULL, ",/ \t\n");
893
	}   /* End while */
894
    }	/* End if */
895
 
896
}   /* End of addcolormap */
897
 
898
/*****************************************************************************/
899
 
900
setwindow(list)
901
 
902
    char	*list;			/* corners of window into the matrix */
903
 
904
{
905
 
906
    static char	*templist = NULL;	/* a working copy of the window list */
907
    char	*ptr;			/* next window coordinate in *templist */
908
    int		i = 0;			/* assigned to this region in wlist[] */
909
 
910
/*
911
 *
912
 * Sets up an optional window into the matrix.
913
 *
914
 */
915
 
916
    wlist[0] = wlist[1] = 1;
917
    wlist[2] = wlist[3] = 0;
918
 
919
    if ( list != NULL )  {
920
	if ( templist != NULL )
921
	    free(templist);
922
	templist = savestring(list);
923
 
924
	ptr = strtok(templist, ",/ \t\n");
925
	while ( ptr != NULL )  {
926
	    wlist[i++] = atoi(ptr);
927
	    ptr = strtok(NULL, ",/ \t\n");
928
	}   /* End while */
929
    }	/* End if */
930
 
931
}   /* End of setwindow */
932
 
933
/*****************************************************************************/
934
 
935
inwindow()
936
 
937
{
938
 
939
    int		r;			/* row of the patcount element */
940
    int		c;			/* column of the patcount element */
941
 
942
/*
943
 *
944
 * Checks if the patcount element of the matrix is in the window.
945
 *
946
 */
947
 
948
    r = (patcount/columns) + 1;
949
    c = (patcount%columns) + 1;
950
 
951
    return((c >= wlist[0]) && (r >= wlist[1]) && (c <= wlist[2]) && (r <= wlist[3]));
952
 
953
}   /* End of inwindow */
954
 
955
/*****************************************************************************/
956
 
957
inrange()
958
 
959
{
960
 
961
/*
962
 *
963
 * Checks if the current row lies in the window. Used right before we output the
964
 * raster lines.
965
 *
966
 */
967
 
968
    return(((patcount/columns) >= wlist[1]) && ((patcount/columns) <= wlist[3]));
969
 
970
}   /* End of inrange */
971
 
972
/*****************************************************************************/
973
 
974
mapfloat(element)
975
 
976
    double	element;		/* floating point matrix element */
977
 
978
{
979
 
980
    int		i;			/* loop index */
981
 
982
/*
983
 *
984
 * Maps element into an integer in the range 0 to 255, and returns the result to
985
 * the caller. Mapping is done using the color map that was saved in ilist[]. Also
986
 * updates the count field for the region that contains element - not good!
987
 *
988
 */
989
 
990
    for ( i = 1; i < next && ilist[i].val < element; i += 2 ) ;
991
 
992
    if ( i > next || element < ilist[i].val )
993
	i--;
994
 
995
    ilist[i].count++;
996
    return(ilist[i].color);
997
 
998
}   /* End of mapfloat */
999
 
1000
/*****************************************************************************/
1001
 
1002
putrow()
1003
 
1004
{
1005
 
1006
    char	*p1, *p2;		/* starting and ending columns */
1007
    int		n;			/* set to bytes per pattern */
1008
    int		i;			/* loop index */
1009
 
1010
/*
1011
 *
1012
 * Takes the scanline that's been saved in *raster, encodes it according to the
1013
 * value that's been assigned to bytespp, and writes the result to *fp_out. Each
1014
 * line in the output bitmap is terminated by a 0 on a line by itself.
1015
 *
1016
 */
1017
 
1018
    n = (bytespp <= 0) ? columns : bytespp;
1019
 
1020
    for ( p1 = raster, p2 = raster + n; p1 < eptr; p1 = p2 )
1021
	if ( patncmp(p1, n) == TRUE )  {
1022
	    while ( patncmp(p2, n) == TRUE ) p2 += n;
1023
	    p2 += n;
1024
	    fprintf(fp_out, "%d ", n);
1025
	    for ( i = 0; i < n; i++, p1++ )
1026
		fprintf(fp_out, "%.2X", ((int) *p1) & 0377);
1027
	    fprintf(fp_out, " %d\n", (p2 - p1) / n);
1028
	} else {
1029
	    while ( p2 < eptr && patncmp(p2, n) == FALSE ) p2 += n;
1030
	    if ( p2 > eptr ) p2 = eptr;
1031
	    fprintf(fp_out, "%d ", p2 - p1);
1032
	    while ( p1 < p2 )
1033
		fprintf(fp_out, "%.2X", ((int) *p1++) & 0377);
1034
	    fprintf(fp_out, " 0\n");
1035
	}   /* End else */
1036
 
1037
    fprintf(fp_out, "0\n");
1038
 
1039
    rptr = raster;
1040
 
1041
}   /* End of putrow */
1042
 
1043
/*****************************************************************************/
1044
 
1045
labelmatrix()
1046
 
1047
{
1048
 
1049
    int		total;			/* number of elements in the window */
1050
    int		i;			/* loop index */
1051
 
1052
/*
1053
 *
1054
 * Responsible for generating the PostScript calls that label the matrix, generate
1055
 * the legend, and print the matrix name.
1056
 *
1057
 */
1058
 
1059
    fprintf(fp_out, "(%s) ((%d, %d) to (%d, %d)) labelmatrix\n", matrixname,
1060
			wlist[0], wlist[1], wlist[2], wlist[3]);
1061
 
1062
    total = (wlist[2] - wlist[0] + 1) * (wlist[3] - wlist[1] + 1);
1063
 
1064
    if ( nxtstat == OFF )
1065
	for ( i = 0; i < regions; i++ )
1066
	    ilist[i].count = 0;
1067
 
1068
    for ( i = 1; i < next; i += 2 )
1069
	fprintf(fp_out, "(%g) ", ilist[i].val);
1070
    fprintf(fp_out, "%d ", (regions - 1) / 2);
1071
 
1072
    for ( i = regions - 1; i >= 0; i-- )
1073
	fprintf(fp_out, "{(\\%.3o)} %d ", ilist[i].color, ilist[i].count);
1074
    fprintf(fp_out, "%d %d legend\n", total, regions);
1075
 
1076
}   /* End of labelmatrix */
1077
 
1078
/*****************************************************************************/
1079
 
1080
patncmp(p1, n)
1081
 
1082
    char	*p1;			/* first patterns starts here */
1083
    int		n;			/* and extends this many bytes */
1084
 
1085
{
1086
 
1087
    char	*p2;			/* address of the second pattern */
1088
 
1089
/*
1090
 *
1091
 * Compares the two n byte patterns *p1 and *(p1+n). FALSE if returned is they're
1092
 * different or extend past the end of the current raster line.
1093
 *
1094
 */
1095
 
1096
    p2 = p1 + n;
1097
 
1098
    for ( ; n > 0; n--, p1++, p2++ )
1099
	if ( p2 >= eptr || *p1 != *p2 )
1100
	    return(FALSE);
1101
 
1102
    return(TRUE);
1103
 
1104
}   /* End of patncmp */
1105
 
1106
/*****************************************************************************/
1107
 
1108
char *savestring(str)
1109
 
1110
    char	*str;			/* save this string */
1111
 
1112
{
1113
 
1114
    char	*ptr = NULL;		/* at this address */
1115
 
1116
/*
1117
 *
1118
 * Copies string *str to a permanent place and returns the address to the caller.
1119
 *
1120
 */
1121
 
1122
    if ( str != NULL && *str != '\0' )  {
1123
	if ( (ptr = malloc(strlen(str) + 1)) == NULL )
1124
	    error(FATAL, "no memory available for string %s", str);
1125
	strcpy(ptr, str);
1126
    }	/* End if */
1127
 
1128
    return(ptr);
1129
 
1130
}   /* End of savestring */
1131
 
1132
/*****************************************************************************/
1133
 
1134
redirect(pg)
1135
 
1136
    int		pg;			/* next page we're printing */
1137
 
1138
{
1139
 
1140
    static FILE	*fp_null = NULL;	/* if output is turned off */
1141
 
1142
/*
1143
 *
1144
 * If we're not supposed to print page pg, fp_out will be directed to /dev/null,
1145
 * otherwise output goes to stdout.
1146
 *
1147
 */
1148
 
1149
    if ( pg >= 0 && in_olist(pg) == ON )
1150
	fp_out = stdout;
1151
    else if ( (fp_out = fp_null) == NULL )
1152
	fp_out = fp_null = fopen("/dev/null", "w");
1153
 
1154
}   /* End of redirect */
1155
 
1156
/*****************************************************************************/
1157