wdvi

network DVI viewer
Log | Files | Refs

special.c (28781B)


      1 /*========================================================================*\
      2 
      3 Copyright (c) 1990-2002  Paul Vojta
      4 
      5 Permission is hereby granted, free of charge, to any person obtaining a copy
      6 of this software and associated documentation files (the "Software"), to
      7 deal in the Software without restriction, including without limitation the
      8 rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
      9 sell copies of the Software, and to permit persons to whom the Software is
     10 furnished to do so, subject to the following conditions:
     11 
     12 The above copyright notice and this permission notice shall be included in
     13 all copies or substantial portions of the Software.
     14 
     15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
     16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
     17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
     18 PAUL VOJTA BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
     19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
     20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
     21 
     22 NOTE:
     23 	This module is based on prior work as noted below.
     24 
     25 \*========================================================================*/
     26 
     27 /*
     28  * Support drawing routines for TeXsun and TeX
     29  *
     30  *      Copyright, (C) 1987, 1988 Tim Morgan, UC Irvine
     31  *	Adapted for xdvi by Jeffrey Lee, U. of Toronto
     32  *
     33  * At the time these routines are called, the values of hh and vv should
     34  * have been updated to the upper left corner of the graph (the position
     35  * the \special appears at in the dvi file).  Then the coordinates in the
     36  * graphics commands are in terms of a virtual page with axes oriented the
     37  * same as the Imagen and the SUN normally have:
     38  *
     39  *                      0,0
     40  *                       +-----------> +x
     41  *                       |
     42  *                       |
     43  *                       |
     44  *                      \ /
     45  *                       +y
     46  *
     47  * Angles are measured in the conventional way, from +x towards +y.
     48  * Unfortunately, that reverses the meaning of "counterclockwise"
     49  * from what it's normally thought of.
     50  *
     51  * A lot of floating point arithmetic has been converted to integer
     52  * arithmetic for speed.  In some places, this is kind-of kludgy, but
     53  * it's worth it.
     54  */
     55 
     56 #include <ctype.h>
     57 #include <err.h>
     58 #include <limits.h>	/* abs() */
     59 #include <math.h>	/* sqrt(), sin(), cos() */
     60 #include <stdlib.h>	/* abs() */
     61 #include <string.h>	/* memcmp() */
     62 
     63 #include "dvi-init.h"	/* page_info */
     64 #include "dvi-draw.h"	/* PXL_V, PXL_H */
     65 #include "events.h"	/* do_color_change() */
     66 #include "special.h"
     67 #include "xdvi.h"
     68 #include "util.h"	/* xmalloc() */
     69 
     70 #define	rint(x)	floor((x) + 0.5)
     71 
     72 
     73 #define	MAXPOINTS	300	/* Max points in a path */
     74 #define	MAX_PEN_SIZE	7	/* Max pixels of pen width */
     75 #define	TWOPI		(3.14159265359 * 2.0)
     76 
     77 
     78 static	int	xx[MAXPOINTS], yy[MAXPOINTS];	/* Path in milli-inches */
     79 static	int	path_len = 0;	/* # points in current path */
     80 static	int	pen_size = 1;	/* Pixel width of lines drawn */
     81 
     82 static	Boolean	whiten = False;
     83 static	Boolean	shade = False;
     84 static	Boolean	blacken = False;
     85 
     86 /* Unfortunately, these values also appear in dvisun.c */
     87 #define	xRESOLUTION	(pixels_per_inch/shrink_factor)
     88 #define	yRESOLUTION	(pixels_per_inch/shrink_factor)
     89 
     90 
     91 /*
     92  *	X drawing routines
     93  */
     94 
     95 #define	toint(x)	((int) ((x) + 0.5))
     96 #define	xconv(x)	(toint(tpic_conv*(x))/shrink_factor + PXL_H)
     97 #define	yconv(y)	(toint(tpic_conv*(y))/shrink_factor + PXL_V)
     98 
     99 /*
    100  *	Draw a line from (fx,fy) to (tx,ty).
    101  *	Right now, we ignore pen_size.
    102  */
    103 static	void
    104 line_btw(int fx, int fy, int tx, int ty)
    105 {
    106 	int	fcx = xconv(fx);
    107 	int	tcx = xconv(tx);
    108 	int	fcy = yconv(fy);
    109 	int	tcy = yconv(ty);
    110 
    111 	if ((fcx >= max_x && tcx >= max_x) || (fcx < min_x && tcx < min_x) ||
    112 	    (fcy >= max_y && tcy >= max_y) || (fcy < min_y && tcy < min_y))
    113 		return;
    114 	if (fg_active != fg_current)
    115 		do_color_change();
    116 
    117 	XDrawLine(DISP, currwin.win, ruleGC,
    118 	      fcx - currwin.base_x, fcy - currwin.base_y,
    119 	      tcx - currwin.base_x, tcy - currwin.base_y);
    120 }
    121 
    122 /*
    123  *	Draw a dot at (x,y)
    124  */
    125 static	void
    126 dot_at(int x, int y)
    127 {
    128 	int	cx = xconv(x);
    129 	int	cy = yconv(y);
    130 
    131 	if (cx >= max_x || cx < min_x || cy >= max_y || cy < min_y)
    132 		return;
    133 	if (fg_active != fg_current)
    134 		do_color_change();
    135 
    136 	XDrawPoint(DISP, currwin.win, ruleGC,
    137 	           cx - currwin.base_x, cy - currwin.base_y);
    138 }
    139 
    140 /*
    141  *	Apply the requested attributes to the last path (box) drawn.
    142  *	Attributes are reset.
    143  *	(Not currently implemented.)
    144  */
    145 
    146 static	void
    147 do_attribute_path(int last_min_x, int last_max_x, int last_min_y, int last_max_y)
    148 {
    149 }
    150 
    151 /*
    152  *	Set the size of the virtual pen used to draw in milli-inches
    153  */
    154 
    155 static	void
    156 set_pen_size(char *cp)
    157 {
    158 	int	ps;
    159 
    160 	if (sscanf(cp, " %d ", &ps) != 1) {
    161 		warnx("invalid .ps command format: %s", cp);
    162 		return;
    163 	}
    164 
    165 	pen_size = (ps * (xRESOLUTION + yRESOLUTION) + 1000) / 2000;
    166 	if (pen_size < 1)
    167 		pen_size = 1;
    168 	else if (pen_size > MAX_PEN_SIZE)
    169 		pen_size = MAX_PEN_SIZE;
    170 }
    171 
    172 
    173 /*
    174  *	Print the line defined by previous path commands
    175  */
    176 
    177 static	void
    178 flush_path(void)
    179 {
    180 	int	i;
    181 	int	last_min_x, last_max_x, last_min_y, last_max_y;
    182 
    183 	last_min_x = 30000;
    184 	last_min_y = 30000;
    185 	last_max_x = -30000;
    186 	last_max_y = -30000;
    187 	for (i = 1; i < path_len; i++) {
    188 	    if (xx[i] > last_max_x) last_max_x = xx[i];
    189 	    if (xx[i] < last_min_x) last_min_x = xx[i];
    190 	    if (yy[i] > last_max_y) last_max_y = yy[i];
    191 	    if (yy[i] < last_min_y) last_min_y = yy[i];
    192 	    line_btw(xx[i], yy[i], xx[i+1], yy[i+1]);
    193 	}
    194 	if (xx[path_len] > last_max_x) last_max_x = xx[path_len];
    195 	if (xx[path_len] < last_min_x) last_min_x = xx[path_len];
    196 	if (yy[path_len] > last_max_y) last_max_y = yy[path_len];
    197 	if (yy[path_len] < last_min_y) last_min_y = yy[path_len];
    198 	path_len = 0;
    199 	do_attribute_path(last_min_x, last_max_x, last_min_y, last_max_y);
    200 }
    201 
    202 
    203 /*
    204  *	Print a dashed line along the previously defined path, with
    205  *	the dashes/inch defined.
    206  */
    207 
    208 static	void
    209 flush_dashed(char *cp, Boolean dotted)
    210 {
    211 	int	i;
    212 	int	numdots;
    213 	int	lx0, ly0, lx1, ly1;
    214 	int	cx0, cy0, cx1, cy1;
    215 	float	inchesperdash;
    216 	double	d, spacesize, a, b, dx, dy, milliperdash;
    217 
    218 	if (sscanf(cp, " %f ", &inchesperdash) != 1) {
    219 	    warnx("invalid format for dotted/dashed line: %s", cp);
    220 	    return;
    221 	}
    222 	if (path_len <= 1 || inchesperdash <= 0.0) {
    223 	    warnx("invalid conditions for dotted/dashed line");
    224 	    return;
    225 	}
    226 	milliperdash = inchesperdash * 1000.0;
    227 	lx0 = xx[1];	ly0 = yy[1];
    228 	lx1 = xx[2];	ly1 = yy[2];
    229 	dx = lx1 - lx0;
    230 	dy = ly1 - ly0;
    231 	if (dotted) {
    232 	    numdots = sqrt(dx*dx + dy*dy) / milliperdash + 0.5;
    233 	    if (numdots == 0) numdots = 1;
    234 	    for (i = 0; i <= numdots; i++) {
    235 		a = (float) i / (float) numdots;
    236 		cx0 = lx0 + a * dx + 0.5;
    237 		cy0 = ly0 + a * dy + 0.5;
    238 		dot_at(cx0, cy0);
    239 	    }
    240 	}
    241 	else {
    242 	    d = sqrt(dx*dx + dy*dy);
    243 	    numdots = d / (2.0 * milliperdash) + 1.0;
    244 	    if (numdots <= 1)
    245 		line_btw(lx0, ly0, lx1, ly1);
    246 	    else {
    247 		spacesize = (d - numdots * milliperdash) / (numdots - 1);
    248 		for (i = 0; i < numdots - 1; i++) {
    249 		    a = i * (milliperdash + spacesize) / d;
    250 		    b = a + milliperdash / d;
    251 		    cx0 = lx0 + a * dx + 0.5;
    252 		    cy0 = ly0 + a * dy + 0.5;
    253 		    cx1 = lx0 + b * dx + 0.5;
    254 		    cy1 = ly0 + b * dy + 0.5;
    255 		    line_btw(cx0, cy0, cx1, cy1);
    256 		    b += spacesize / d;
    257 		}
    258 		cx0 = lx0 + b * dx + 0.5;
    259 		cy0 = ly0 + b * dy + 0.5;
    260 		line_btw(cx0, cy0, lx1, ly1);
    261 	    }
    262 	}
    263 	path_len = 0;
    264 }
    265 
    266 
    267 /*
    268  *	Add a point to the current path
    269  */
    270 
    271 static	void
    272 add_path(char *cp)
    273 {
    274 	int	pathx, pathy;
    275 
    276 	if (++path_len >= MAXPOINTS)
    277 		errx(1, "Too many points");
    278 	if (sscanf(cp, " %d %d ", &pathx, &pathy) != 2)
    279 		errx(1, "Malformed path command");
    280 	xx[path_len] = pathx;
    281 	yy[path_len] = pathy;
    282 }
    283 
    284 
    285 /*
    286  *	Draw to a floating point position
    287  */
    288 
    289 static void
    290 im_fdraw(double x, double y)
    291 {
    292 	if (++path_len >= MAXPOINTS)
    293 		errx(1, "Too many arc points");
    294 	xx[path_len] = x + 0.5;
    295 	yy[path_len] = y + 0.5;
    296 }
    297 
    298 
    299 /*
    300  *	Draw an ellipse with the indicated center and radices.
    301  */
    302 
    303 static	void
    304 draw_ellipse(int xc, int yc, int xr, int yr)
    305 {
    306 	double	angle, theta;
    307 	int	n;
    308 	int	px0, py0, px1, py1;
    309 
    310 	angle = (xr + yr) / 2.0;
    311 	theta = sqrt(1.0 / angle);
    312 	n = TWOPI / theta + 0.5;
    313 	if (n < 12) n = 12;
    314 	else if (n > 80) n = 80;
    315 	n /= 2;
    316 	theta = TWOPI / n;
    317 
    318 	angle = 0.0;
    319 	px0 = xc + xr;		/* cos(0) = 1 */
    320 	py0 = yc;		/* sin(0) = 0 */
    321 	while ((angle += theta) <= TWOPI) {
    322 	    px1 = xc + xr*cos(angle) + 0.5;
    323 	    py1 = yc + yr*sin(angle) + 0.5;
    324 	    line_btw(px0, py0, px1, py1);
    325 	    px0 = px1;
    326 	    py0 = py1;
    327 	}
    328 	line_btw(px0, py0, xc + xr, yc);
    329 }
    330 
    331 /*
    332  *	Draw an arc
    333  */
    334 
    335 static	void
    336 arc(char *cp, Boolean invis)
    337 {
    338 	int	xc, yc, xrad, yrad, n;
    339 	float	start_angle, end_angle, angle, theta, r;
    340 	double	xradius, yradius, xcenter, ycenter;
    341 
    342 	if (sscanf(cp, " %d %d %d %d %f %f ", &xc, &yc, &xrad, &yrad,
    343 		&start_angle, &end_angle) != 6) {
    344 	    warnx("invalid arc specification: %s", cp);
    345 	    return;
    346 	}
    347 
    348 	if (invis) return;
    349 
    350 	/* We have a specialized fast way to draw closed circles/ellipses */
    351 	if (start_angle <= 0.0 && end_angle >= 6.282) {
    352 	    draw_ellipse(xc, yc, xrad, yrad);
    353 	    return;
    354 	}
    355 	xcenter = xc;
    356 	ycenter = yc;
    357 	xradius = xrad;
    358 	yradius = yrad;
    359 	r = (xradius + yradius) / 2.0;
    360 	theta = sqrt(1.0 / r);
    361 	n = 0.3 * TWOPI / theta + 0.5;
    362 	if (n < 12) n = 12;
    363 	else if (n > 80) n = 80;
    364 	n /= 2;
    365 	theta = TWOPI / n;
    366 	flush_path();
    367 	im_fdraw(xcenter + xradius * cos(start_angle),
    368 	    ycenter + yradius * sin(start_angle));
    369 	angle = start_angle + theta;
    370 	if (end_angle < start_angle) end_angle += TWOPI;
    371 	while (angle < end_angle) {
    372 	    im_fdraw(xcenter + xradius * cos(angle),
    373 		ycenter + yradius * sin(angle));
    374 	    angle += theta;
    375 	}
    376 	im_fdraw(xcenter + xradius * cos(end_angle),
    377 	    ycenter + yradius * sin(end_angle));
    378 	flush_path();
    379 }
    380 
    381 
    382 /*
    383  *	APPROXIMATE integer distance between two points
    384  */
    385 
    386 #define	dist(x0, y0, x1, y1)	(abs(x0 - x1) + abs(y0 - y1))
    387 
    388 
    389 /*
    390  *	Draw a spline along the previously defined path
    391  */
    392 
    393 static	void
    394 flush_spline(void)
    395 {
    396 	int	xp, yp;
    397 	int	N;
    398 	int	lastx, lasty;
    399 	Boolean	lastvalid = False;
    400 	int	t1, t2, t3;
    401 	int	steps;
    402 	int	j;
    403 	int	i, w;
    404 
    405 	N = path_len + 1;
    406 	xx[0] = xx[1];
    407 	yy[0] = yy[1];
    408 	xx[N] = xx[N-1];
    409 	yy[N] = yy[N-1];
    410 	for (i = 0; i < N - 1; i++) {	/* interval */
    411 	    steps = (dist(xx[i], yy[i], xx[i+1], yy[i+1]) +
    412 		dist(xx[i+1], yy[i+1], xx[i+2], yy[i+2])) / 80;
    413 	    for (j = 0; j < steps; j++) {	/* points within */
    414 		w = (j * 1000 + 500) / steps;
    415 		t1 = w * w / 20;
    416 		w -= 500;
    417 		t2 = (750000 - w * w) / 10;
    418 		w -= 500;
    419 		t3 = w * w / 20;
    420 		xp = (t1*xx[i+2] + t2*xx[i+1] + t3*xx[i] + 50000) / 100000;
    421 		yp = (t1*yy[i+2] + t2*yy[i+1] + t3*yy[i] + 50000) / 100000;
    422 		if (lastvalid) line_btw(lastx, lasty, xp, yp);
    423 		lastx = xp;
    424 		lasty = yp;
    425 		lastvalid = True;
    426 	    }
    427 	}
    428 	path_len = 0;
    429 }
    430 
    431 
    432 /*
    433  *	Shade the last box, circle, or ellipse
    434  */
    435 
    436 static	void
    437 shade_last(void)
    438 {
    439 	blacken = whiten = False;
    440 	shade = True;
    441 }
    442 
    443 
    444 /*
    445  *	Make the last box, circle, or ellipse, white inside (shade with white)
    446  */
    447 
    448 static	void
    449 whiten_last(void)
    450 {
    451 	whiten = True;
    452 	blacken = shade = False;
    453 }
    454 
    455 
    456 /*
    457  *	Make last box, etc, black inside
    458  */
    459 
    460 static	void
    461 blacken_last(void)
    462 {
    463 	blacken = True;
    464 	whiten = shade = False;
    465 }
    466 
    467 
    468 void
    469 init_prescan(void)
    470 {
    471 	scanned_page = scanned_page_color = scanned_page_reset = -1;
    472 
    473 	if (!resource._use_color)
    474 	    scanned_page_color = total_pages + 1;
    475 }
    476 
    477 
    478 /*
    479  *	Table of dvips color names.  Produced by passing the table in
    480  *	dvipsk/color.lpro through the following perl script, and then
    481  *	sorting by most common colors.
    482  *
    483  *	#! /usr/bin/perl -w
    484  *	
    485  *	sub cvpart {
    486  *	    $val = $_[0] < 1.0 ? 1.0 - $_[0] : 0.0;
    487  *	    return int($val * 65535 + 0.5)
    488  *	}
    489  *	
    490  *	$pat = "^\\/(\\w+)\\s*\\{\\s*([0-9.]+)\\s*([0-9.]+)\\s*([0-9.]+)"
    491  *	  . "\\s*([0-9.]+)\\s*setcmykcolor\\s*\\}\\s*DC\\s*\$";
    492  *	while (<STDIN>) {
    493  *	    chop;
    494  *	    if (/^%%/) {next;}
    495  *	    if (/$pat/o) {
    496  *		printf "\t{ \"%s\",\t { %g, %g, %g } },\n",
    497  *		  $1, cvpart($2 + $5), cvpart($3 + $5), cvpart($4 + $5);
    498  *	    }
    499  *	    else {
    500  *		print "Bad line: ", $_, "\n";
    501  *	    }
    502  *	}
    503  */
    504 
    505 struct dvipscolor {
    506 	const char	*name;
    507 	struct rgb	color;
    508 } colornames[] = {
    509 	{ "Black",	 { 0, 0, 0 } },
    510 	{ "White",	 { 65535, 65535, 65535 } },
    511 	{ "Gray",	 { 32768, 32768, 32768 } },
    512 	{ "Red",	 { 65535, 0, 0 } },
    513 	{ "Green",	 { 0, 65535, 0 } },
    514 	{ "Blue",	 { 0, 0, 65535 } },
    515 	{ "Yellow",	 { 65535, 65535, 0 } },
    516 	{ "Cyan",	 { 0, 65535, 65535 } },
    517 	{ "Magenta",	 { 65535, 0, 65535 } },
    518 	{ "GreenYellow", { 55705, 65535, 20316 } },
    519 	{ "Goldenrod",	 { 65535, 58982, 10486 } },
    520 	{ "Dandelion",	 { 65535, 46530, 10486 } },
    521 	{ "Apricot",	 { 65535, 44564, 31457 } },
    522 	{ "Peach",	 { 65535, 32768, 19661 } },
    523 	{ "Melon",	 { 65535, 35389, 32768 } },
    524 	{ "YellowOrange", { 65535, 38010, 0 } },
    525 	{ "Orange",	 { 65535, 25559, 8520 } },
    526 	{ "BurntOrange", { 65535, 32112, 0 } },
    527 	{ "Bittersweet", { 49807, 655, 0 } },
    528 	{ "RedOrange",	 { 65535, 15073, 8520 } },
    529 	{ "Mahogany",	 { 42598, 0, 0 } },
    530 	{ "Maroon",	 { 44564, 0, 0 } },
    531 	{ "BrickRed",	 { 47185, 0, 0 } },
    532 	{ "OrangeRed",	 { 65535, 0, 32768 } },
    533 	{ "RubineRed",	 { 65535, 0, 57015 } },
    534 	{ "WildStrawberry", { 65535, 2621, 39976 } },
    535 	{ "Salmon",	 { 65535, 30801, 40632 } },
    536 	{ "CarnationPink", { 65535, 24248, 65535 } },
    537 	{ "VioletRed",	 { 65535, 12452, 65535 } },
    538 	{ "Rhodamine",	 { 65535, 11796, 65535 } },
    539 	{ "Mulberry",	 { 41942, 5243, 64224 } },
    540 	{ "RedViolet",	 { 38666, 0, 43253 } },
    541 	{ "Fuchsia",	 { 29491, 655, 60292 } },
    542 	{ "Lavender",	 { 65535, 34078, 65535 } },
    543 	{ "Thistle",	 { 57671, 26869, 65535 } },
    544 	{ "Orchid",	 { 44564, 23593, 65535 } },
    545 	{ "DarkOrchid",	 { 39321, 13107, 52428 } },
    546 	{ "Purple",	 { 36044, 9175, 65535 } },
    547 	{ "Plum",	 { 32768, 0, 65535 } },
    548 	{ "Violet",	 { 13762, 7864, 65535 } },
    549 	{ "RoyalPurple", { 16384, 6553, 65535 } },
    550 	{ "BlueViolet",	 { 6553, 3277, 62914 } },
    551 	{ "Periwinkle",	 { 28180, 29491, 65535 } },
    552 	{ "CadetBlue",	 { 24903, 28180, 50462 } },
    553 	{ "CornflowerBlue", { 22937, 57015, 65535 } },
    554 	{ "MidnightBlue", { 0, 28835, 37355 } },
    555 	{ "NavyBlue",	 { 3932, 30146, 65535 } },
    556 	{ "RoyalBlue",	 { 0, 32768, 65535 } },
    557 	{ "Cerulean",	 { 3932, 58326, 65535 } },
    558 	{ "ProcessBlue", { 2621, 65535, 65535 } },
    559 	{ "SkyBlue",	 { 24903, 65535, 57671 } },
    560 	{ "Turquoise",	 { 9830, 65535, 52428 } },
    561 	{ "TealBlue",	 { 7864, 64224, 41942 } },
    562 	{ "Aquamarine",	 { 11796, 65535, 45875 } },
    563 	{ "BlueGreen",	 { 9830, 65535, 43908 } },
    564 	{ "Emerald",	 { 0, 65535, 32768 } },
    565 	{ "JungleGreen", { 655, 65535, 31457 } },
    566 	{ "SeaGreen",	 { 20316, 65535, 32768 } },
    567 	{ "ForestGreen", { 0, 57671, 0 } },
    568 	{ "PineGreen",	 { 0, 49151, 10486 } },
    569 	{ "LimeGreen",	 { 32768, 65535, 0 } },
    570 	{ "YellowGreen", { 36700, 65535, 17039 } },
    571 	{ "SpringGreen", { 48496, 65535, 15728 } },
    572 	{ "OliveGreen",	 { 0, 39321, 0 } },
    573 	{ "RawSienna",	 { 36044, 0, 0 } },
    574 	{ "Sepia",	 { 19661, 0, 0 } },
    575 	{ "Brown",	 { 26214, 0, 0 } },
    576 	{ "Tan",	 { 56360, 38010, 28835 } },
    577 };
    578 
    579 static void
    580 reverse_color(struct rgb *rgbp)
    581 {
    582 	if (resource.reverse == False)
    583 		return;
    584 
    585 	rgbp->r = 65535 - rgbp->r;
    586 	rgbp->g = 65535 - rgbp->g;
    587 	rgbp->b = 65535 - rgbp->b;
    588 }
    589 
    590 static	Boolean
    591 parse_color(const char *cp0, const char *cp, struct rgb *rgbp)
    592 {
    593 	double		r, g, b;
    594 
    595 	while (*cp == ' ')
    596 		++cp;
    597 
    598 	if (strncasecmp(cp, "rgb ", 4) == 0) {
    599 	    if (sscanf(cp + 3, "%lf %lf %lf", &r, &g, &b) == 3
    600 	      && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1) {
    601 		rgbp->r = r * 65535 + 0.5;
    602 		rgbp->g = g * 65535 + 0.5;
    603 		rgbp->b = b * 65535 + 0.5;
    604 		reverse_color(rgbp);
    605 		return True;
    606 	    }
    607 	}
    608 	else if (strncasecmp(cp, "gray ", 5) == 0) {
    609 	    if (sscanf(cp + 4, "%lf", &r) == 1 && r >= 0 && r <= 1) {
    610 		rgbp->r = rgbp->g = rgbp->b = r * 65535 + 0.5;
    611 		reverse_color(rgbp);
    612 		return True;
    613 	    }
    614 	}
    615 	else if (strncasecmp(cp, "cmyk ", 5) == 0) {
    616 	    double k;
    617 
    618 	    if (sscanf(cp + 4, "%lf %lf %lf %lf", &r, &g, &b, &k) == 4
    619 	      && r >= 0 && r <= 1 && g >= 0 && g <= 1 && b >= 0 && b <= 1
    620 	      && k >= 0 && k <= 1) {
    621 		r = 1.0 - r - k;	/* cyan --> red */
    622 		rgbp->r = (r < 0 ? 0 : r * 65535 + 0.5);
    623 		g = 1.0 - g - k;	/* magenta --> green */
    624 		rgbp->g = (g < 0 ? 0 : g * 65535 + 0.5);
    625 		b = 1.0 - b - k;	/* yellow --> blue */
    626 		rgbp->b = (b < 0 ? 0 : b * 65535 + 0.5);
    627 		reverse_color(rgbp);
    628 		return True;
    629 	    }
    630 	}
    631 	else if (strncasecmp(cp, "hsb ", 4) == 0) {
    632 	    double hue, sat, bri;
    633 
    634 	    if (sscanf(cp + 3, "%lf %lf %lf", &hue, &sat, &bri) == 3 && hue >= 0
    635 	      && hue <= 1 && sat >= 0 && sat <= 1 && bri >= 0 && bri <= 1) {
    636 		int	h	= (int) (6 * hue);
    637 		double	p	= bri * (1 - sat);
    638 		double	q	= bri * (1 - sat * (6 * hue - h));
    639 		double	t	= p - q + bri;
    640 
    641 		switch (h) {
    642 		    case 0:  r = bri; g = t; b = p; break; /* Red - Yel */
    643 		    case 1:  r = q; g = bri; b = p; break; /* Yel - Grn */
    644 		    case 2:  r = p; g = bri; b = t; break; /* Grn - Cyn */
    645 		    case 3:  r = p; g = q; b = bri; break; /* Cyn - Blu */
    646 		    case 4:  r = t; g = p; b = bri; break; /* Blu - Mag */
    647 		    case 5:  r = bri; g = p; b = q; break; /* Mag - Red */
    648 		    case 6:  r = bri; g = b = p;    break; /* Red */
    649 		}
    650 		rgbp->r = r * 65535 + 0.5;
    651 		rgbp->g = g * 65535 + 0.5;
    652 		rgbp->b = b * 65535 + 0.5;
    653 		reverse_color(rgbp);
    654 		return True;
    655 	    }
    656 	}
    657 	else {
    658 		/* check table of dvips color names */
    659 		for (int i = 0; i < XtNumber(colornames); i++) {
    660 			if (strcmp(cp, colornames[i].name) != 0)
    661 				continue;
    662 
    663 			*rgbp = colornames[i].color;
    664 			reverse_color(rgbp);
    665 			return True;
    666 		}
    667 	}
    668 
    669 	if (cp0 != NULL)
    670 	    warnx("Invalid color name in special `%s'", cp0);
    671 
    672 	return False;
    673 }
    674 
    675 
    676 /*
    677  *	Color stack used when scanning.  These records stay around, to ease
    678  *	the burden on xmalloc().  The first entry is a dummy entry, giving
    679  *	the default foreground color (as modified).
    680  */
    681 
    682 struct colorframe {
    683 	struct colorframe	*next, *prev;
    684 	struct rgb		color;
    685 };
    686 
    687 static	struct colorframe	scanstack_head;
    688 static	int			scanstack_len;
    689 static	struct colorframe	*scanstack_current;
    690 
    691 static	void
    692 init_page_colors(void)
    693 {
    694 	int	i;
    695 
    696 	page_colors = xmalloc(total_pages * sizeof *page_colors);
    697 
    698 	for (i = 0; i <= scanned_page + 1; ++i) {
    699 	    page_colors[i].bg = bg_initial;	/* from command line */
    700 	    page_colors[i].colorstack = &fg_initial;
    701 	    page_colors[i].stacksize = 1;
    702 	}
    703 	while (i < total_pages)
    704 	    page_colors[i++].colorstack = NULL;
    705 
    706 	scanstack_head.color = fg_initial;
    707 	scanstack_len = 1;			/* nothing yet */
    708 	scanstack_current = &scanstack_head;	/* stack position */
    709 }
    710 
    711 
    712 static	void
    713 scan_bg_color(const char *cp)
    714 {
    715 	if (!resource._use_color)
    716 	    return;
    717 
    718 	if (page_colors == NULL)
    719 	    init_page_colors();
    720 
    721 	(void) parse_color(cp, cp + 11, &page_colors[scanned_page + 1].bg);
    722 }
    723 
    724 static	void
    725 scan_color(const char *cp)
    726 {
    727 	const char	*cp1 = cp + 6;
    728 
    729 	if (!resource._use_color)
    730 	    return;
    731 
    732 	while (*cp1 == ' ') ++cp1;
    733 
    734 	if (page_colors == NULL)
    735 	    init_page_colors();
    736 
    737 	if (strncasecmp(cp1, "push ", 5) == 0) {
    738 	    if (scanstack_current->next == NULL) {	/* if at end */
    739 		scanstack_current->next = xmalloc(sizeof *scanstack_current);
    740 		scanstack_current->next->prev = scanstack_current;
    741 		scanstack_current->next->next = NULL;
    742 	    }
    743 	    scanstack_current = scanstack_current->next;
    744 	    ++scanstack_len;
    745 	    if (!parse_color(cp, cp1 + 5, &scanstack_current->color))
    746 		scanstack_current->color = scanstack_current->prev->color;
    747 	}
    748 	else if (strncasecmp(cp1, "pop", 3) == 0) {
    749 	    if (scanstack_len <= 1) {
    750 		  warnx("Color pop occurred with empty color stack.");
    751 	    }
    752 	    else {
    753 		scanstack_current = scanstack_current->prev;
    754 		--scanstack_len;
    755 	    }
    756 	}
    757 	else {
    758 	    (void) parse_color(cp, cp1, &scanstack_head.color);
    759 	    if (scanstack_len > 1) {
    760 		struct colorframe	*cfp;
    761 
    762 		warnx("Global color change occurred with non-empty color stack"
    763 		    ";\ncoping by setting all stack entries to that color.");
    764 		for (cfp = scanstack_head.next;; cfp = cfp->next) {
    765 		    cfp->color = scanstack_head.color;
    766 		    if (cfp == scanstack_current) break;
    767 		}
    768 	    }
    769 	}
    770 }
    771 
    772 void
    773 scan_color_eop(void)
    774 {
    775 	int			i;
    776 	const struct rgb	*prev;
    777 	struct colorframe	*cf;
    778 	const struct rgb	*p1;
    779 	struct rgb		*rgbp;
    780 
    781 	if (page_colors == NULL)
    782 	    return;
    783 
    784 	/* set background color for next page */
    785 	if (scanned_page + 1 < total_pages)
    786 	    page_colors[scanned_page + 1].bg = page_colors[scanned_page].bg;
    787 
    788 	/* save the stack contents */
    789 	page_colors[scanned_page].stacksize = scanstack_len;
    790 
    791 	prev = &fg_initial;
    792 	i = 1;
    793 	if (scanned_page > 0) {
    794 	    prev = page_colors[scanned_page - 1].colorstack;
    795 	    i = page_colors[scanned_page - 1].stacksize;
    796 	}
    797 	if (scanstack_len <= i) {
    798 	    /* try to reuse the previous array */
    799 	    p1 = prev;
    800 	    cf = &scanstack_head;
    801 	    for (i = 0;;) {
    802 		if (p1->r != cf->color.r || p1->g != cf->color.g
    803 		  || p1->b != cf->color.b)
    804 		    break;
    805 		if (++i >= scanstack_len) {	/* end loop; reuse memory */
    806 		    page_colors[scanned_page].colorstack = prev;
    807 		    return;	/* done */
    808 		}
    809 		++p1;
    810 		cf = cf->next;
    811 	    }
    812 	}
    813 	page_colors[scanned_page].colorstack = rgbp
    814 	  = xmalloc(scanstack_len * sizeof *rgbp);
    815 	cf = &scanstack_head;
    816 	for (i = 0; i < scanstack_len; ++i) {
    817 	    *rgbp++ = cf->color;
    818 	    cf = cf->next;
    819 	}
    820 }
    821 
    822 
    823 /*
    824  *	Page stack when displaying.  Again, the records stay around once
    825  *	allocated.  Bottom entries in the stack (inherited from previous
    826  *	pages) are stored in an array instead (see comments in xdvi.h).
    827  */
    828 
    829 static	struct colorframe	*rcs_head;
    830 
    831 
    832 /*
    833  *	We don't actually do any X calls yet to change colors; that can wait
    834  *	until a character or rule needs to be drawn.
    835  */
    836 
    837 void
    838 set_fg_color(const struct rgb *color)
    839 {
    840 	struct fgrec	**fgpp;
    841 
    842 	if (fg_current != NULL &&
    843 	    color->r == fg_current->color.r &&
    844 	    color->g == fg_current->color.g &&
    845 	    color->b == fg_current->color.b)
    846 		return;
    847 
    848 	for (fgpp = &bg_current->fg_head;;) {
    849 		fg_current = *fgpp;
    850 		if (fg_current == NULL) {	/* if color is not in list */
    851 			fg_current = *fgpp = xmalloc(sizeof *fg_current);
    852 			fg_current->next = NULL;
    853 			fg_current->color = *color;
    854 			fg_current->pixel_good = False;
    855 			break;
    856 	    	}
    857 		if (fg_current->color.r == color->r &&
    858 		    fg_current->color.g == color->g &&
    859 		    fg_current->color.b == color->b)
    860 			break;
    861 		fgpp = &fg_current->next;
    862 	}
    863 	if (debug & DBG_DVI)
    864 		printf("Changing fg color to %5d %5d %5d\n",
    865 		       color->r, color->g, color->b);
    866 }
    867 
    868 static	void
    869 color_special(const char *cp)
    870 {
    871 	if (!resource._use_color)
    872 	    return;
    873 
    874 	while (*cp == ' ') ++cp;
    875 
    876 	if (strncasecmp(cp, "push ", 5) == 0) {
    877 	    if (rcs_top == NULL) {
    878 		if (rcs_head == NULL) {
    879 		    rcs_head = xmalloc(sizeof *rcs_head);
    880 		    rcs_head->prev = rcs_head->next = NULL;
    881 		}
    882 		rcs_top = rcs_head;
    883 	    }
    884 	    else {
    885 		struct colorframe *rcs_next;
    886 
    887 		rcs_next = rcs_top->next;
    888 		if (rcs_next == NULL) {
    889 		    rcs_next = rcs_top->next = xmalloc(sizeof *rcs_next);
    890 		    rcs_next->prev = rcs_top;
    891 		    rcs_next->next = NULL;
    892 		}
    893 		rcs_top = rcs_next;
    894 	    }
    895 	    if (!parse_color(NULL, cp + 5, &rcs_top->color)) {
    896 		if (rcs_top->prev != NULL)
    897 		    rcs_top->color = rcs_top->prev->color;
    898 		else
    899 		    rcs_top->color = color_bottom[color_bot_size - 1];
    900 	    }
    901 	    set_fg_color(&rcs_top->color);
    902 	}
    903 	else if (strncasecmp(cp, "pop", 3) == 0) {
    904 	    /* Pop stack */
    905 	    if (rcs_top != NULL) {
    906 		if (color_bot_size > 0)
    907 		    rcs_top = rcs_top->prev;
    908 		else return;
    909 	    }
    910 	    else if (color_bot_size > 1)
    911 		--color_bot_size;
    912 	    else {
    913 		if (scanned_page_reset >= 0) {
    914 		    /* turn on scanning and redraw the page */
    915 		    scanned_page =
    916 		      scanned_page_color = scanned_page_reset = -1;
    917 		    ev_flags |= EV_NEWPAGE;		/* force a redraw */
    918 		}
    919 		return;
    920 	    }
    921 
    922 	    set_fg_color(rcs_top != NULL ? &rcs_top->color
    923 		: &color_bottom[color_bot_size - 1]);
    924 	}
    925 	else {
    926 	    struct rgb color;
    927 
    928 	    if (!parse_color(NULL, cp, &color)) return;
    929 
    930 	    /* clear stack */
    931 	    if (rcs_head == NULL) {
    932 		rcs_head = xmalloc(sizeof *rcs_head);
    933 		rcs_head->prev = rcs_head->next = NULL;
    934 	    }
    935 	    rcs_top = rcs_head;
    936 	    color_bot_size = 0;
    937 
    938 	    /* Change top of stack */
    939 	    rcs_top->color = color;
    940 
    941 	    set_fg_color(&color);
    942 	}
    943 }
    944 
    945 
    946 static int
    947 atopix(const float val, const char unit[2])
    948 {
    949 	double		factor;
    950 
    951 #if A4
    952 	factor = 1.0 / 2.54;	/* cm */
    953 #else
    954 	factor = 1.0;		/* inches */
    955 #endif
    956 	switch (unit[0] << 8 | unit[1]) {
    957 #if A4
    958 		case 'i' << 8 | 'n':  factor = 1.0;			break;
    959 #else
    960 		case 'c' << 8 | 'm':  factor = 1.0 / 2.54;		break;
    961 #endif
    962 		case 'm' << 8 | 'm':  factor = 1.0 / 25.4;		break;
    963 		case 'p' << 8 | 't':  factor = 1.0 / 72.27;		break;
    964 		case 'p' << 8 | 'c':  factor = 12.0 / 72.27;		break;
    965 		case 'b' << 8 | 'p':  factor = 1.0 / 72.0;		break;
    966 		case 'd' << 8 | 'd':  factor = 1238.0 / 1157.0 / 72.27;	break;
    967 		case 'c' << 8 | 'c':  factor = 12 * 1238.0 / 1157.0 / 72.27;
    968 									break;
    969 		case 's' << 8 | 'p':  factor = 1.0 / 72.27 / 65536;	break;
    970 	}
    971 
    972 	return (int) (factor * val * pixels_per_inch + 0.5);
    973 }
    974 
    975 static	void
    976 scan_papersize(const char *cp)
    977 {
    978 	float		wf, hf;
    979 	char		wu[3], hu[3];
    980 	unsigned int	w, h;
    981 
    982 	if (sscanf(cp, "%f%2s,%f%2s", &wf, wu, &hf, hu) != 4) {
    983 		warnx("error parsing papersize special '%s'", cp);
    984 		return;
    985 	}
    986 
    987 	w = atopix(wf, wu);
    988 	h = atopix(hf, hu);
    989 
    990 	if (w == 0 || h == 0) {
    991 		warnx("invalid papersize special '%s'", cp);
    992 		return;
    993 	}
    994 
    995 	page_info[scanned_page + 1].pw = page_info[scanned_page + 1].ww = w;
    996 	page_info[scanned_page + 1].ph = page_info[scanned_page + 1].wh = h;
    997 }
    998 
    999 
   1000 /*
   1001  *	The following copyright message applies to the rest of this file.  --PV
   1002  */
   1003 
   1004 /*
   1005  *	This program is Copyright (C) 1987 by the Board of Trustees of the
   1006  *	University of Illinois, and by the author Dirk Grunwald.
   1007  *
   1008  *	This program may be freely copied, as long as this copyright
   1009  *	message remaines affixed. It may not be sold, although it may
   1010  *	be distributed with other software which is sold. If the
   1011  *	software is distributed, the source code must be made available.
   1012  *
   1013  *	No warranty, expressed or implied, is given with this software.
   1014  *	It is presented in the hope that it will prove useful.
   1015  *
   1016  *	Hacked in ignorance and desperation by jonah@db.toronto.edu
   1017  */
   1018 
   1019 /*
   1020  *      The code to handle the \specials generated by tpic was modified
   1021  *      by Dirk Grunwald using the code Tim Morgan at Univ. of Calif, Irvine
   1022  *      wrote for TeXsun.
   1023  */
   1024 
   1025 static	char *
   1026 endofcommand(char *cp)
   1027 {
   1028 	while (isspace(*cp))
   1029 		++cp;
   1030 	if (*cp != '=')
   1031 		return NULL;
   1032 	do 
   1033 		++cp;
   1034 	while (isspace(*cp));
   1035 
   1036 	return cp;
   1037 }
   1038 
   1039 void
   1040 applicationDoSpecial(char *cp)
   1041 {
   1042 	if (debug & DBG_DVI)
   1043 		printf("          `%s'\n", cp);
   1044 
   1045 	/* Skip white space */
   1046 	while (isspace(*cp)) ++cp;
   1047 
   1048 	/* Ignore initial "xdvi:" */
   1049 	if (memcmp(cp, "xdvi:", 5) == 0) {
   1050 		cp += 5;
   1051 		while (isspace(*cp))
   1052 			++cp;
   1053 	}
   1054 
   1055 	if (strncasecmp(cp, "color ", 6) == 0) {
   1056 		color_special(cp + 6);
   1057 		return;
   1058 	}
   1059 
   1060 	/* these should have been scanned */
   1061 
   1062 	if (*cp == '!' ||
   1063 	    (strncasecmp(cp, "header", 6) == 0 && endofcommand(cp + 6) != NULL)) {
   1064 		return;
   1065 	}
   1066 
   1067 	if (strncasecmp(cp, "background ", 11) == 0) {
   1068 		if (resource._use_color && scanned_page_reset >= 0) {
   1069 			/* turn on scanning and redraw the page */
   1070 			scanned_page = scanned_page_color =
   1071 			    scanned_page_reset = -1;
   1072 			ev_flags |= EV_NEWPAGE;		/* force a redraw */
   1073 		}
   1074 		return;
   1075 	}
   1076 
   1077 	if (memcmp(cp, "papersize", 9) == 0 && endofcommand(cp + 9) != NULL) {
   1078 		if (scanned_page_reset >= 0) {
   1079 			/* turn on scanning and redraw the page */
   1080 			scanned_page = scanned_page_color =
   1081 			    scanned_page_reset = -1;
   1082 			ev_flags |= EV_NEWPAGE;		/* force a redraw */
   1083 		}
   1084 		return;
   1085 	}
   1086 
   1087 	/* tpic specials */
   1088 
   1089 	if (*cp >= 'a' && *cp <= 'z' && cp[1] >= 'a' && cp[1] <= 'z' &&
   1090 		(isspace(cp[2]) || cp[2] == '\0')) {
   1091 
   1092 #define	CMD(x, y)	((x) << 8 | (y))
   1093 	    switch (CMD(*cp, cp[1])) {
   1094 		case CMD('p','n'): set_pen_size(cp + 2); return;
   1095 		case CMD('f','p'): flush_path(); return;
   1096 		case CMD('d','a'): flush_dashed(cp + 2, False); return;
   1097 		case CMD('d','t'): flush_dashed(cp + 2, True); return;
   1098 		case CMD('p','a'): add_path(cp + 2); return;
   1099 		case CMD('a','r'): arc(cp + 2, False); return;
   1100 		case CMD('i','a'): arc(cp + 2, True); return;
   1101 		case CMD('s','p'): flush_spline(); return;
   1102 		case CMD('s','h'): shade_last(); return;
   1103 		case CMD('w','h'): whiten_last(); return;
   1104 		case CMD('b','k'): blacken_last(); return;
   1105 		case CMD('i','p'): /* throw away the path -- jansteen */
   1106 		    path_len = 0; return;
   1107 	    }
   1108 #undef	CMD
   1109 	}
   1110 
   1111 	/* We support hypertex */
   1112 	if (strncasecmp(cp, "html: ", 5) == 0)
   1113 		return;
   1114 
   1115 	warnx("special '%s' not implemented", cp);
   1116 }
   1117 
   1118 
   1119 void
   1120 scan_special(char *cp)
   1121 {
   1122 	char	*p;
   1123 
   1124 	if (debug & DBG_PS)
   1125 		printf("Scanning special `%s'.\n", cp);
   1126 
   1127 	/* Skip white space */
   1128 	while (isspace(*cp)) ++cp;
   1129 
   1130 	/* Ignore initial "xdvi:" */
   1131 	if (memcmp(cp, "xdvi:", 5) == 0) {
   1132 		cp += 5;
   1133 		while (isspace(*cp)) ++cp;
   1134 	}
   1135 
   1136 	if (strncasecmp(cp, "background ", 11) == 0) {
   1137 		scan_bg_color(cp);
   1138 		return;
   1139 	}
   1140 	else if (strncasecmp(cp, "color ", 6) == 0) {
   1141 		scan_color(cp);
   1142 		return;
   1143 	}
   1144 
   1145 	if (memcmp(cp, "papersize", 9) == 0 &&
   1146 	    (p = endofcommand(cp + 9)) != NULL) {
   1147 		scan_papersize(p);
   1148 		return;
   1149 	}
   1150 }