xref: /plan9/sys/src/cmd/gview.c (revision 162f803d803e7af3469d29bde78fb975a68a2888)
1 #include	<u.h>
2 #include	<libc.h>
3 #include	<ctype.h>
4 #include	<draw.h>
5 #include	<event.h>
6 #include	<cursor.h>
7 #include	<stdio.h>
8 
9 #define Never	0xffffffff	/* Maximum ulong */
10 #define LOG2  0.301029995664
11 #define Button_bit(b)	(1 << ((b)-1))
12 
13 enum {
14 	But1	= Button_bit(1),/* mouse buttons for events */
15 	But2	= Button_bit(2),
16 	But3	= Button_bit(3),
17 };
18 int cantmv = 1;			/* disallow rotate and move? 0..1 */
19 int plotdots;			/* plot dots instead of lines */
20 int top_border, bot_border, lft_border, rt_border;
21 int lft_border0;		/* lft_border for y-axis labels >0 */
22 int top_left, top_right;	/* edges of top line free space */
23 int Mv_delay = 400;		/* msec for button click vs. button hold down */
24 int Dotrad = 2;			/* dot radius in pixels */
25 int framewd=1;			/* line thickness for frame (pixels) */
26 int framesep=1;			/* distance between frame and surrounding text */
27 int outersep=1;			/* distance: surrounding text to screen edge */
28 Point sdigit;			/* size of a digit in the font */
29 Point smaxch;			/* assume any character in font fits in this */
30 double underscan = .05;		/* fraction of frame initially unused per side */
31 double fuzz = 6;		/* selection tolerance in pixels */
32 int tick_len = 15;		/* length of axis label tick mark in pixels */
33 FILE* logfil = 0;		/* dump selected points here if nonzero */
34 
35 #define labdigs  3		/* allow this many sig digits in axis labels */
36 #define digs10pow 1000		/* pow(10,labdigs) */
37 #define axis_color  clr_im(DLtblue)
38 
39 
40 
41 
42 /********************************* Utilities  *********************************/
43 
44 /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
45    necessary and using a space to separate s from the rest of buf[].
46 */
str_insert(char * buf,char * s,int n)47 char* str_insert(char* buf, char* s, int n)
48 {
49 	int blen, slen = strlen(s) + 1;
50 	if (slen >= n)
51 		{strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
52 	blen = strlen(buf);
53 	if (blen >= n-slen)
54 		buf[blen=n-slen-1] = '\0';
55 	memmove(buf+slen, buf, slen+blen+1);
56 	memcpy(buf, s, slen-1);
57 	buf[slen-1] = ' ';
58 	return buf;
59 }
60 
61 /* Alter string smain (without lengthening it) so as to remove the first occurrence of
62    ssub, assuming ssub is ASCII.  Return nonzero (true) if string smain had to be changed.
63    In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
64 */
remove_substr(char * smain,char * ssub)65 int remove_substr(char* smain, char* ssub)
66 {
67 	char *ss, *s = strstr(smain, ssub);
68 	int n = strlen(ssub);
69 	if (s==0)
70 		return 0;
71 	if (islower(s[n]))
72 		s[0] ^= 32;			/* probably tolower(s[0]) or toupper(s[0]) */
73 	else {
74 		for (ss=s+n; *ss!=0; s++, ss++)
75 			*s = *ss;
76 		*s = '\0';
77 	}
78 	return 1;
79 }
80 
adjust_border(Font * f)81 void adjust_border(Font* f)
82 {
83 	int sep = framesep + outersep;
84 	sdigit = stringsize(f, "8");
85 	smaxch = stringsize(f, "MMMg");
86 	smaxch.x = (smaxch.x + 3)/4;
87 	lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
88 	rt_border = (lft_border0 - sep)/2 + outersep;
89 	bot_border = sdigit.y + framewd + sep;
90 	top_border = smaxch.y + framewd + sep;
91 	lft_border = lft_border0;		/* this gets reset later */
92 }
93 
94 
is_off_screen(Point p)95 int is_off_screen(Point p)
96 {
97 	const Rectangle* r = &(screen->r);
98 	return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
99 		|| p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
100 }
101 
102 
103 Cursor	bullseye =
104 {
105 	{-7, -7},
106 	{
107 		0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
108 	 	0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
109 	 	0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
110 	 	0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
111 	},
112 	{
113 		0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
114 		0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
115 		0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
116 		0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
117 	}
118 };
119 
120 /* Wait for a mouse click and return 0 for failue if not button but (curs can be 0) */
get_1click(int but,Mouse * m,Cursor * curs)121 int get_1click(int but, Mouse* m, Cursor* curs)
122 {
123 	if (curs)
124 		esetcursor(curs);
125 	while (m->buttons==0)
126 		*m = emouse();
127 	if (curs)
128 		esetcursor(0);
129 	return (m->buttons==Button_bit(but));
130 }
131 
132 
133 /* Wait for a mouse click or keyboard event from the string of expected characters.  Return
134    the character code or -1 for a button-but mouse event or 0 for wrong button.
135 */
get_click_or_kbd(int but,Mouse * m,const char * expected)136 int get_click_or_kbd(int but, Mouse* m, const char* expected)
137 {
138 	Event ev;
139 	ulong expbits[4], ty;
140 	expbits[0] = expbits[1] = expbits[2] = expbits[3];
141 	for (; *expected!=0; expected++)
142 		expbits[((*expected)>>5)&3] |= 1 << (*expected&31);
143 	do ty = eread(Emouse|Ekeyboard, &ev);
144 	while ((ty&Emouse) ? ev.mouse.buttons==0
145 		: (ev.kbdc&~127) || !(expbits[(ev.kbdc>>5)&3] & (1<<(ev.kbdc&31))) );
146 	if (ty&Ekeyboard)
147 		return ev.kbdc;
148 	*m = ev.mouse;
149 	return (ev.mouse.buttons==Button_bit(but)) ? -1 : 0;
150 }
151 
152 
153 /* Wait until but goes up or until a mouse event's msec passes tlimit.
154    Return a boolean result that tells whether the button went up.
155 */
lift_button(int but,Mouse * m,int tlimit)156 int lift_button(int but, Mouse* m, int tlimit)
157 {
158 	do {	*m = emouse();
159 		if (m->msec >= tlimit)
160 			return 0;
161 	} while (m->buttons & Button_bit(but));
162 	return 1;
163 }
164 
165 
166 /* Set *m to the last pending mouse event, or the first one where but is up.
167    If no mouse events are pending, wait for the next one.
168 */
latest_mouse(int but,Mouse * m)169 void latest_mouse(int but, Mouse* m)
170 {
171 	int bbit = Button_bit(but);
172 	do {	*m = emouse();
173 	} while ((m->buttons & bbit) && ecanmouse());
174 }
175 
176 
177 
178 /*********************************** Colors ***********************************/
179 
180 enum {	DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
181 	DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
182 	DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
183 	/* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
184 		DCyan, DMagenta, DWhite */
185 };
186 
187 
188 typedef struct thick_color {
189 	int thick;			/* use 1+2*thick pixel wide lines */
190 	Image* clr;			/* Color to use when drawing this */
191 } thick_color;
192 
193 
194 typedef struct color_ref {
195 	ulong c;			/* RGBA pixel color */
196 	char* nam;			/* ASCII name (matched to input, used in output)*/
197 	int nam1;			/* single-letter version of color name */
198 	Image* im;			/* replicated solid-color image */
199 } color_ref;
200 
201 color_ref clrtab[] = {
202 	DRed,		"Red",		'R', 0,
203 	DPink,		"Pink",		'P', 0,
204 	DDkred,		"Dkred",	'r', 0,
205 	DOrange,	"Orange",	'O', 0,
206 	DYellow,	"Yellow",	'Y', 0,
207 	DDkyellow,	"Dkyellow",	'y', 0,
208 	DGreen,		"Green",	'G', 0,
209 	DDkgreen,	"Dkgreen",	'g', 0,
210 	DCyan,		"Cyan",		'C', 0,
211 	DBlue,		"Blue",		'B', 0,
212 	DLtblue,	"Ltblue",	'b', 0,
213 	DMagenta,	"Magenta",	'M', 0,
214 	DViolet,	"Violet",	'V', 0,
215 	Dgray,		"Gray",		'A', 0,
216 	DBlack,		"Black",	'K', 0,
217 	DWhite,		"White",	'W', 0,
218 	DNofill,	0,		0,   0	/* DNofill means "end of data" */
219 };
220 
221 short nam1_idx[128];			/* the clrtab[] index for each nam1, else -1 */
222 
223 
init_clrtab(void)224 void  init_clrtab(void)
225 {
226 	int i;
227 	Rectangle r = Rect(0,0,1,1);
228 	memset(&nam1_idx[0], -1, sizeof(nam1_idx));
229 	for (i=0; clrtab[i].c!=DNofill; i++) {
230 		clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
231 		/* should check for 0 result? */
232 		nam1_idx[clrtab[i].nam1] = i;
233 	}
234 }
235 
236 
clrim_id(Image * clr)237 int clrim_id(Image* clr)
238 {
239 	int i;
240 	for (i=0; clrtab[i].im!=clr; i++)
241 		if (clrtab[i].c==DNofill)
242 			exits("bad image color");
243 	return i;
244 }
245 
clr_id(int clr)246 int clr_id(int clr)
247 {
248 	int i;
249 	for (i=0; clrtab[i].c!=clr; i++)
250 		if (clrtab[i].c==DNofill)
251 			exits("bad color");
252 	return i;
253 }
254 
255 
256 #define clr_im(clr)	clrtab[clr_id(clr)].im
257 #define is_Multi  -2			/* dummy clrtab[] less than -1 */
258 
259 
tc_default(thick_color * buf)260 thick_color* tc_default(thick_color *buf)
261 {
262 	buf[0].thick = 1;
263 	buf[1].clr = clr_im(DBlack);
264 	buf[1].thick = 0;
265 	return buf;
266 }
267 
268 
269 /* Return an allocated array that describes the color letters (clrtab[].nam1 values,
270    optionally prefixed by 'T') in the string that starts at c0 and ends just before
271    fin.  The first entry is a dummy whose thick field tells how many data entries follow.
272    If buf!=0, it should point to an array of length 2 that is to hold the output
273    (truncated to a dummy and one data entry).  The error indication is 1 data entry
274    of default color and thickness; e.g., "Multi(xxbadxx)" in a label prevents gview
275    from recognizing anything that follows.
276 */
parse_color_chars(const char * c0,const char * fin,thick_color * buf)277 thick_color* parse_color_chars(const char* c0, const char* fin, thick_color *buf)
278 {
279 	thick_color* tc;		/* Pending return value */
280 	int i, j, n=fin-c0;		/* n is an upper bound on how many data members */
281 	const char* c;
282 	for (c=c0; c<fin-1; c++)
283 		if (*c=='T')
284 			n--;
285 	if (buf==0)
286 		tc = (thick_color*) malloc((n+1)*sizeof(thick_color));
287 	else {tc=buf; n=1;}
288 	i = 0;
289 	for (c=c0; c<fin && i<n; c++) {
290 		tc[++i].thick = 0;
291 		if (*c=='T')
292 			if (++c==fin)
293 				return tc_default(tc);
294 			else tc[i].thick=1;
295 		j = (*c&~127) ? -1 : nam1_idx[*c];
296 		if (j < 0)
297 			return tc_default(tc);
298 		tc[i].clr = clrtab[j].im;
299 	}
300 	tc[0].thick = i;
301 	return tc;
302 }
303 
304 
305 /* Decide what color and thickness to use for a polyline based on the label it has in the
306    input file.  The winner is whichever color name comes first, or otherwise black; and
307    thickness is determined by the presence of "Thick" in the string.  Place the result
308    in *r1 and return 0 unless a Multi(...) spec is found, in which case the result is
309    an allocated array of alternative color and thickness values.  A nonzero idxdest
310    requests the clrtab[] index in *idxdest and no allocated array.
311 */
nam2thclr(const char * nam,thick_color * r1,int * idxdest)312 thick_color* nam2thclr(const char* nam, thick_color *r1, int *idxdest)
313 {
314 	char *c, *cbest=0, *rp=0;
315 	int i, ibest=-1;
316 	thick_color* tc = 0;
317 	thick_color buf[2];
318 	if (*nam!=0) {
319 		c = strstr(nam, "Multi(");
320 		if (c!=0 && (rp=strchr(c+6,')'))!=0)
321 			{ibest=is_Multi; cbest=c;}
322 		for (i=0; clrtab[i].nam!=0; i++) {
323 			c = strstr(nam,clrtab[i].nam);
324 			if (c!=0 && (ibest==-1 || c<cbest))
325 				{ibest=i; cbest=c;}
326 		}
327 	}
328 	if (ibest==is_Multi) {
329 		tc = parse_color_chars(cbest+6, rp, (idxdest==0 ? 0 : &buf[0]));
330 		ibest = clrim_id(tc[1].clr);
331 	}
332 	if (idxdest!=0)
333 		*idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
334 	r1->clr = (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
335 	r1->thick = (tc!=0) ? tc[1].thick : (strstr(nam,"Thick")==0 ? 0 : 1);
336 	return tc;
337 }
338 
339 
340 /* Alter string nam so that nam2thick() and nam2clr() agree with *tc, using
341    buf[] (a buffer of length bufn) to store the result if it differs from nam.
342    We go to great pains to perform this alteration in a manner that will seem natural
343    to the user, i.e., we try removing a suitably isolated color name before inserting
344    a new one.
345 */
nam_with_thclr(char * nam,const thick_color * tc,char * buf,int bufn)346 char* nam_with_thclr(char* nam, const thick_color *tc, char* buf, int bufn)
347 {
348 	thick_color c0;
349 	int clr0i;
350 	nam2thclr(nam, &c0, &clr0i);
351 	char *clr0s;
352 	if (c0.thick==tc->thick && c0.clr==tc->clr)
353 		return nam;
354 	clr0s = clrtab[clr0i].nam;
355 	if (strlen(nam)<bufn) strcpy(buf,nam);
356 	else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
357 	if (c0.clr != tc->clr)
358 		remove_substr(buf, clr0s);
359 	if (c0.thick > tc->thick)
360 		while (remove_substr(buf, "Thick"))
361 			/* do nothing */;
362 	nam2thclr(nam, &c0, &clr0i);
363 	if (c0.clr != tc->clr)
364 		str_insert(buf, clrtab[clrim_id(tc->clr)].nam, bufn);
365 	if (c0.thick < tc->thick)
366 		str_insert(buf, "Thick", bufn);
367 	return buf;
368 }
369 
370 
371 
372 /****************************** Data structures  ******************************/
373 
374 Image* mv_bkgd;				/* Background image (usually 0) */
375 
376 typedef struct fpoint {
377 	double x, y;
378 } fpoint;
379 
380 typedef struct frectangle {
381 	fpoint min, max;
382 } frectangle;
383 
384 frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
385 
386 
387 /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
388 */
fintersects(const frectangle * r1,const frectangle * r2,double slant)389 int fintersects(const frectangle* r1, const frectangle* r2, double slant)
390 {
391 	double x2min=r2->min.x, x2max=r2->max.x;
392 	if (r1->max.x <= x2min || x2max <= r1->min.x)
393 		return 0;
394 	if (slant >=0)
395 		{x2min*=slant; x2max*=slant;}
396 	else	{double t=x2min*slant; x2min=x2max*slant; x2max=t;}
397 	return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
398 }
399 
fcontains(const frectangle * r,fpoint p)400 int fcontains(const frectangle* r, fpoint p)
401 {
402 	return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
403 }
404 
405 
grow_bb(frectangle * dest,const frectangle * r)406 void grow_bb(frectangle* dest, const frectangle* r)
407 {
408 	if (r->min.x < dest->min.x) dest->min.x=r->min.x;
409 	if (r->min.y < dest->min.y) dest->min.y=r->min.y;
410 	if (r->max.x > dest->max.x) dest->max.x=r->max.x;
411 	if (r->max.y > dest->max.y) dest->max.y=r->max.y;
412 }
413 
414 
slant_frect(frectangle * r,double sl)415 void slant_frect(frectangle *r, double sl)
416 {
417 	r->min.y += sl*r->min.x;
418 	r->max.y += sl*r->max.x;
419 }
420 
421 
fcenter(const frectangle * r)422 fpoint fcenter(const frectangle* r)
423 {
424 	fpoint c;
425 	c.x = .5*(r->max.x + r->min.x);
426 	c.y = .5*(r->max.y + r->min.y);
427 	return c;
428 }
429 
430 
431 typedef struct fpolygon {
432 	fpoint* p;			/* a malloc'ed array */
433 	int n;				/* p[] has n elements: p[0..n] */
434 	frectangle bb;			/* bounding box */
435 	char* nam;			/* name of this polygon (malloc'ed) */
436 	thick_color c;			/* the current color and line thickness */
437 	thick_color* ct;		/* 0 or malloc'ed color schemes, ct[1..ct->thick] */
438 	struct fpolygon* link;
439 } fpolygon;
440 
441 typedef struct fpolygons {
442 	fpolygon* p;			/* the head of a linked list */
443 	frectangle bb;			/* overall bounding box */
444 	frectangle disp;		/* part being mapped onto screen->r */
445 	double slant_ht;		/* controls how disp is slanted */
446 } fpolygons;
447 
448 
449 fpolygons univ = {			/* everything there is to display */
450 	0,
451 	1e30, 1e30, -1e30, -1e30,
452 	0, 0, 0, 0,
453 	2*1e30
454 };
455 
456 
free_fp_etc(fpolygon * fp)457 void free_fp_etc(fpolygon* fp)
458 {
459 	if (fp->ct != 0)
460 		free(fp->ct);
461 	free(fp->p);
462 	free(fp);
463 }
464 
465 
set_default_clrs(fpolygons * fps,fpolygon * fpstop)466 void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
467 {
468 	fpolygon* fp;
469 	for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link)
470 		fp->ct = nam2thclr(fp->nam, &fp->c, 0);
471 }
472 
473 
fps_invert(fpolygons * fps)474 void fps_invert(fpolygons* fps)
475 {
476 	fpolygon *p, *r=0;
477 	for (p=fps->p; p!=0;) {
478 		fpolygon* q = p;
479 		p = p->link;
480 		q->link = r;
481 		r = q;
482 	}
483 	fps->p = r;
484 }
485 
486 
fp_remove(fpolygons * fps,fpolygon * fp)487 void fp_remove(fpolygons* fps, fpolygon* fp)
488 {
489 	fpolygon *q, **p = &fps->p;
490 	while (*p!=fp)
491 		if (*p==0)
492 			return;
493 		else	p = &(*p)->link;
494 	*p = fp->link;
495 	fps->bb = empty_frect;
496 	for (q=fps->p; q!=0; q=q->link)
497 		grow_bb(&fps->bb, &q->bb);
498 }
499 
500 
501 /* The transform maps abstract fpoint coordinates (the ones used in the input)
502    to the current screen coordinates.  The do_untransform() macros reverses this.
503    If univ.slant_ht is not the height of univ.disp, the actual region in the
504    abstract coordinates is a parallelogram inscribed in univ.disp with two
505    vertical edges and two slanted slanted edges: slant_ht>0 means that the
506    vertical edges have height slant_ht and the parallelogram touches the lower
507    left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
508    of height -slant_ht that touches the other two corners of univ.disp.
509    NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
510    already been subtracted from yy.
511 */
512 typedef struct transform {
513 	double sl;
514 	fpoint o, sc;		/* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
515 } transform;
516 
517 #define do_transform(d,tr,s)	((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x,  \
518 				(d)->y = (tr)->o.y + (tr)->sc.y*(s)->y    \
519 					+ (tr)->sl*(s)->x)
520 #define do_untransform(d,tr,s)	((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x,    \
521 				(d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
522 					/(tr)->sc.y)
523 #define xtransform(tr,xx)	((tr)->o.x + (tr)->sc.x*(xx))
524 #define ytransform(tr,yy)	((tr)->o.y + (tr)->sc.y*(yy))
525 #define dxuntransform(tr,xx)	((xx)/(tr)->sc.x)
526 #define dyuntransform(tr,yy)	((yy)/(tr)->sc.y)
527 
528 
cur_trans(void)529 transform cur_trans(void)
530 {
531 	transform t;
532 	Rectangle d = screen->r;
533 	const frectangle* s = &univ.disp;
534 	double sh = univ.slant_ht;
535 	d.min.x += lft_border;
536 	d.min.y += top_border;
537 	d.max.x -= rt_border;
538 	d.max.y -= bot_border;
539 	t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
540 	t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
541 	if (sh > 0) {
542 		t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
543 		t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
544 	} else {
545 		t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
546 		t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
547 	}
548 	t.o.x = d.min.x - t.sc.x*s->min.x;
549 	return t;
550 }
551 
552 
u_slant_amt(fpolygons * u)553 double u_slant_amt(fpolygons *u)
554 {
555 	double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
556 	double dx = u->disp.max.x - u->disp.min.x;
557 	return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
558 }
559 
560 
561 /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
562    *u says to display, where sl is the amount of slant.
563 */
set_unslanted_y(fpolygons * u,double * y0,double * y1)564 double set_unslanted_y(fpolygons *u, double *y0, double *y1)
565 {
566 	double yy1, sl=u_slant_amt(u);
567 	if (u->slant_ht > 0) {
568 		*y0 = u->disp.min.y - sl*u->disp.min.x;
569 		yy1 = *y0 + u->slant_ht;
570 	} else {
571 		yy1 = u->disp.max.y - sl*u->disp.min.x;
572 		*y0 = yy1 + u->slant_ht;
573 	}
574 	if (y1 != 0)
575 		*y1 = yy1;
576 	return sl;
577 }
578 
579 
580 
581 
582 /*************************** The region to display ****************************/
583 
nontrivial_interval(double * lo,double * hi)584 void nontrivial_interval(double *lo, double *hi)
585 {
586 	if (*lo >= *hi) {
587 		double mid = .5*(*lo + *hi);
588 		double tweak = 1e-6 + 1e-6*fabs(mid);
589 		*lo = mid - tweak;
590 		*hi = mid + tweak;
591 	}
592 }
593 
594 
init_disp(void)595 void init_disp(void)
596 {
597 	double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
598 	double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
599 	univ.disp.min.x = univ.bb.min.x - dw;
600 	univ.disp.min.y = univ.bb.min.y - dh;
601 	univ.disp.max.x = univ.bb.max.x + dw;
602 	univ.disp.max.y = univ.bb.max.y + dh;
603 	nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
604 	nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
605 	univ.slant_ht = univ.disp.max.y - univ.disp.min.y;	/* means no slant */
606 }
607 
608 
recenter_disp(Point c)609 void recenter_disp(Point c)
610 {
611 	transform tr = cur_trans();
612 	fpoint cc, off;
613 	do_untransform(&cc, &tr, &c);
614 	off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
615 	off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
616 	univ.disp.min.x += off.x;
617 	univ.disp.min.y += off.y;
618 	univ.disp.max.x += off.x;
619 	univ.disp.max.y += off.y;
620 }
621 
622 
623 /* Find the upper-left and lower-right corners of the bounding box of the
624    parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
625    in screen coordinates), and return the height of the parallelogram (negated
626    if it slopes downward).
627 */
untransform_corners(double rminx,double rminy,double rmaxx,double rmaxy,fpoint * ul,fpoint * lr)628 double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
629 		fpoint *ul, fpoint *lr)
630 {
631 	fpoint r_ur, r_ul, r_ll, r_lr;	/* corners of the given recangle */
632 	fpoint ur, ll;			/* untransformed versions of r_ur, r_ll */
633 	transform tr = cur_trans();
634 	double ht;
635 	r_ur.x=rmaxx;  r_ur.y=rminy;
636 	r_ul.x=rminx;  r_ul.y=rminy;
637 	r_ll.x=rminx;  r_ll.y=rmaxy;
638 	r_lr.x=rmaxx;  r_lr.y=rmaxy;
639 	do_untransform(ul, &tr, &r_ul);
640 	do_untransform(lr, &tr, &r_lr);
641 	do_untransform(&ur, &tr, &r_ur);
642 	do_untransform(&ll, &tr, &r_ll);
643 	ht = ur.y - lr->y;
644 	if (ll.x < ul->x)
645 		ul->x = ll.x;
646 	if (ur.y > ul->y)
647 		ul->y = ur.y;
648 	else	ht = -ht;
649 	if (ur.x > lr->x)
650 		lr->x = ur.x;
651 	if (ll.y < lr->y)
652 		lr->y = ll.y;
653 	return ht;
654 }
655 
656 
disp_dozoom(double rminx,double rminy,double rmaxx,double rmaxy)657 void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
658 {
659 	fpoint ul, lr;
660 	double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
661 	if (ul.x==lr.x || ul.y==lr.y)
662 		return;
663 	univ.slant_ht = sh;
664 	univ.disp.min.x = ul.x;
665 	univ.disp.max.y = ul.y;
666 	univ.disp.max.x = lr.x;
667 	univ.disp.min.y = lr.y;
668 	nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
669 	nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
670 }
671 
672 
disp_zoomin(Rectangle r)673 void disp_zoomin(Rectangle r)
674 {
675 	disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
676 }
677 
678 
disp_zoomout(Rectangle r)679 void disp_zoomout(Rectangle r)
680 {
681 	double qminx, qminy, qmaxx, qmaxy;
682 	double scx, scy;
683 	Rectangle s = screen->r;
684 	if (r.min.x==r.max.x || r.min.y==r.max.y)
685 		return;
686 	s.min.x += lft_border;
687 	s.min.y += top_border;
688 	s.max.x -= rt_border;
689 	s.max.y -= bot_border;
690 	scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
691 	scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
692 	qminx = s.min.x + scx*(s.min.x - r.min.x);
693 	qmaxx = s.max.x + scx*(s.max.x - r.max.x);
694 	qminy = s.min.y + scy*(s.min.y - r.min.y);
695 	qmaxy = s.max.y + scy*(s.max.y - r.max.y);
696 	disp_dozoom(qminx, qminy, qmaxx, qmaxy);
697 }
698 
699 
expand2(double * a,double * b,double f)700 void expand2(double* a, double* b, double f)
701 {
702 	double mid = .5*(*a + *b);
703 	*a = mid + f*(*a - mid);
704 	*b = mid + f*(*b - mid);
705 }
706 
disp_squareup(void)707 void disp_squareup(void)
708 {
709 	double dx = univ.disp.max.x - univ.disp.min.x;
710 	double dy = univ.disp.max.y - univ.disp.min.y;
711 	dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
712 	dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
713 	if (dx > dy)
714 		expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
715 	else	expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
716 	univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
717 }
718 
719 
720 /* Slant so that p and q appear at the same height on the screen and the
721    screen contains the smallest possible superset of what its previous contents.
722 */
slant_disp(fpoint p,fpoint q)723 void slant_disp(fpoint p, fpoint q)
724 {
725 	double yll, ylr, yul, yur;	/* corner y coords of displayed parallelogram */
726 	double sh, dy;
727 	if (p.x == q.x)
728 		return;
729 	sh = univ.slant_ht;
730 	if (sh > 0) {
731 		yll=yul=univ.disp.min.y;  yul+=sh;
732 		ylr=yur=univ.disp.max.y;  ylr-=sh;
733 	} else {
734 		yll=yul=univ.disp.max.y;  yll+=sh;
735 		ylr=yur=univ.disp.min.y;  yur-=sh;
736 	}
737 	dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
738 	dy -= ylr - yll;
739 	if (dy > 0)
740 		{yll-=dy; yur+=dy;}
741 	else	{yul-=dy; ylr+=dy;}
742 	if (ylr > yll) {
743 		univ.disp.min.y = yll;
744 		univ.disp.max.y = yur;
745 		univ.slant_ht = yur - ylr;
746 	} else {
747 		univ.disp.max.y = yul;
748 		univ.disp.min.y = ylr;
749 		univ.slant_ht = ylr - yur;
750 	}
751 }
752 
753 
754 
755 
756 /******************************** Ascii input  ********************************/
757 
set_fbb(fpolygon * fp)758 void set_fbb(fpolygon* fp)
759 {
760 	fpoint lo=fp->p[0], hi=fp->p[0];
761 	const fpoint *q, *qtop;
762 	for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
763 		if (q->x < lo.x) lo.x=q->x;
764 		if (q->y < lo.y) lo.y=q->y;
765 		if (q->x > hi.x) hi.x=q->x;
766 		if (q->y > hi.y) hi.y=q->y;
767 	}
768 	fp->bb.min = lo;
769 	fp->bb.max = hi;
770 }
771 
mystrdup(char * s)772 char* mystrdup(char* s)
773 {
774 	char *r, *t = strrchr(s,'"');
775 	if (t==0) {
776 		t = s + strlen(s);
777 		while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
778 			t--;
779 	}
780 	r = malloc(1+(t-s));
781 	memcpy(r, s, t-s);
782 	r[t-s] = 0;
783 	return r;
784 }
785 
is_valid_label(char * lab)786 int is_valid_label(char* lab)
787 {
788 	char* t;
789 	if (lab[0]=='"')
790 		return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
791 	return strcspn(lab," \t")==strlen(lab);
792 }
793 
794 /* Read a polyline and update the number of lines read.  A zero result indicates bad
795    syntax if *lineno increases; otherwise it indicates end of file.
796 */
rd_fpoly(FILE * fin,int * lineno)797 fpolygon* rd_fpoly(FILE* fin, int *lineno)
798 {
799 	char buf[4096], junk[2];
800 	fpoint q;
801 	fpolygon* fp;
802 	int allocn;
803 	if (!fgets(buf,4096,fin))
804 		return 0;
805 	(*lineno)++;
806 	if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
807 		return 0;
808 	fp = malloc(sizeof(fpolygon));
809 	allocn = 4;
810 	fp->p = malloc(allocn*sizeof(fpoint));
811 	fp->p[0] = q;
812 	fp->n = 0;
813 	fp->nam = "";
814 	fp->c.thick = 0;
815 	fp->c.clr = clr_im(DBlack);
816 	fp->ct = 0;
817 	while (fgets(buf,4096,fin)) {
818 		(*lineno)++;
819 		if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
820 			if (!is_valid_label(buf))
821 				{free_fp_etc(fp); return 0;}
822 			fp->nam = (buf[0]=='"') ? buf+1 : buf;
823 			break;
824 		}
825 		if (++(fp->n) == allocn)
826 			fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
827 		fp->p[fp->n] = q;
828 	}
829 	fp->nam = mystrdup(fp->nam);
830 	set_fbb(fp);
831 	fp->link = 0;
832 	return fp;
833 }
834 
835 
836 /* Read input into *fps and return 0 or a line number where there's a syntax error */
rd_fpolys(FILE * fin,fpolygons * fps)837 int rd_fpolys(FILE* fin, fpolygons* fps)
838 {
839 	fpolygon *fp, *fp0=fps->p;
840 	int lineno=0, ok_upto=0;
841 	while ((fp=rd_fpoly(fin,&lineno)) != 0) {
842 		ok_upto = lineno;
843 		fp->link = fps->p;
844 		fps->p = fp;
845 		grow_bb(&fps->bb, &fp->bb);
846 	}
847 	set_default_clrs(fps, fp0);
848 	return (ok_upto==lineno) ? 0 : lineno;
849 }
850 
851 
852 /* Read input from file fnam and return an error line no., -1 for "can't open"
853    or 0 for success.
854 */
doinput(char * fnam)855 int doinput(char* fnam)
856 {
857 	FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
858 	int errline_or0;
859 	if (fin==0)
860 		return -1;
861 	errline_or0 = rd_fpolys(fin, &univ);
862 	fclose(fin);
863 	return errline_or0;
864 }
865 
866 
867 
868 /******************************** Ascii output ********************************/
869 
fp_reverse(fpolygon * fp)870 fpolygon* fp_reverse(fpolygon* fp)
871 {
872 	fpolygon* r = 0;
873 	while (fp!=0) {
874 		fpolygon* q = fp->link;
875 		fp->link = r;
876 		r = fp;
877 		fp = q;
878 	}
879 	return r;
880 }
881 
wr_fpoly(FILE * fout,const fpolygon * fp)882 void wr_fpoly(FILE* fout, const fpolygon* fp)
883 {
884 	char buf[256];
885 	int i;
886 	for (i=0; i<=fp->n; i++)
887 		fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
888 	fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, &fp->c, buf, 256));
889 }
890 
wr_fpolys(FILE * fout,fpolygons * fps)891 void wr_fpolys(FILE* fout, fpolygons* fps)
892 {
893 	fpolygon* fp;
894 	fps->p = fp_reverse(fps->p);
895 	for (fp=fps->p; fp!=0; fp=fp->link)
896 		wr_fpoly(fout, fp);
897 	fps->p = fp_reverse(fps->p);
898 }
899 
900 
dooutput(char * fnam)901 int dooutput(char* fnam)
902 {
903 	FILE* fout = fopen(fnam, "w");
904 	if (fout==0)
905 		return 0;
906 	wr_fpolys(fout, &univ);
907 	fclose(fout);
908 	return 1;
909 }
910 
911 
912 
913 
914 /************************ Clipping to screen rectangle ************************/
915 
916 /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
917    or return 0 to indicate no such t values exist.  If returning 1, set *t0 and
918    *t1 to delimit the t interval.
919 */
do_xory(double x0,double x1,double xlo,double xhi,double * t0,double * t1)920 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
921 {
922 	*t1 = 1.0;
923 	if (x0<xlo) {
924 		if (x1<xlo) return 0;
925 		*t0 = (xlo-x0)/(x1-x0);
926 		if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
927 	} else if (x0>xhi) {
928 		if (x1>xhi) return 0;
929 		*t0 = (xhi-x0)/(x1-x0);
930 		if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
931 	} else {
932 		*t0 = 0.0;
933 		if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
934 		else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
935 		else *t1 = 1.0;
936 	}
937 	return 1;
938 }
939 
940 
941 /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
942    outside of *r?  Note that the edge could start outside *r, pass through *r,
943    and wind up outside again.
944 */
frac_outside(const fpoint * p,const fpoint * q,const frectangle * r,double slope)945 double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
946 		double slope)
947 {
948 	double t0, t1, tt0, tt1;
949 	double px=p->x, qx=q->x;
950 	if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
951 		return 1;
952 	if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
953 		return 1;
954 	if (tt0 > t0)
955 		t0 = tt0;
956 	if (t1<=t0 || tt1<=t0)
957 		return 1;
958 	return t0;
959 }
960 
961 
962 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
963    the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
964    Coordinates are transformed by y=y-x*slope before testing against r.
965 */
in_length(const fpoint * p0,const fpoint * pn,frectangle r,double slope)966 double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
967 {
968 	const fpoint* p = p0;
969 	double px, py;
970 	do if (++p > pn)
971 		return pn - p0;
972 	while (r.min.x<=(px=p->x) && px<=r.max.x
973 			&& r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
974 	return (p - p0) - frac_outside(p, p-1, &r, slope);
975 }
976 
977 
978 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
979    the maximum tt such that F(0..tt) is all outside of *r.  Coordinates are
980    transformed by y=y-x*slope before testing against r.
981 */
out_length(const fpoint * p0,const fpoint * pn,frectangle r,double slope)982 double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
983 {
984 	const fpoint* p = p0;
985 	double fr;
986 	do {	if (p->x < r.min.x)
987 			do if (++p>pn) return pn-p0;
988 			while (p->x <= r.min.x);
989 		else if (p->x > r.max.x)
990 			do if (++p>pn) return pn-p0;
991 			while (p->x >= r.max.x);
992 		else if (p->y-slope*p->x < r.min.y)
993 			do if (++p>pn) return pn-p0;
994 			while (p->y-slope*p->x <= r.min.y);
995 		else if (p->y-slope*p->x > r.max.y)
996 			do if (++p>pn) return pn-p0;
997 			while (p->y-slope*p->x >= r.max.y);
998 		else return p - p0;
999 	} while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
1000 	return (p - p0) + fr-1;
1001 }
1002 
1003 
1004 
1005 /*********************** Drawing frame and axis labels  ***********************/
1006 
1007 #define Nthous  7
1008 #define Len_thous  30			/* bound on strlen(thous_nam[i]) */
1009 char* thous_nam[Nthous] = {
1010 	"one", "thousand", "million", "billion",
1011 	"trillion", "quadrillion", "quintillion",
1012 };
1013 
1014 
1015 typedef struct lab_interval {
1016 	double sep;			/* separation between tick marks */
1017 	double unit;		/* power of 1000 divisor */
1018 	int logunit;		/* log base 1000 of of this divisor */
1019 	double off;			/* offset to subtract before dividing */
1020 } lab_interval;
1021 
1022 
abbrev_num(double x,const lab_interval * iv)1023 char* abbrev_num(double x, const lab_interval* iv)
1024 {
1025 	static char buf[16];
1026 	double dx = x - iv->off;
1027 	dx = iv->sep * floor(dx/iv->sep + .5);
1028 	sprintf(buf,"%g", dx/iv->unit);
1029 	return buf;
1030 }
1031 
1032 
lead_digits(double n,double r)1033 double lead_digits(double n, double r)	/* n truncated to power of 10 above r */
1034 {
1035 	double rr = pow(10, ceil(log10(r)));
1036 	double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
1037 	if (n+r-nn >= digs10pow) {
1038 		rr /= 10;
1039 		nn = (n<rr) ? 0.0 : rr*floor(n/rr);
1040 	}
1041 	return nn;
1042 }
1043 
1044 
next_larger(double s0,double xlo,double xhi)1045 lab_interval next_larger(double s0, double xlo, double xhi)
1046 {
1047 	double nlo, nhi;
1048 	lab_interval r;
1049 	r.logunit = (int) floor(log10(s0) + LOG2);
1050 	r.unit = pow(10, r.logunit);
1051 	nlo = xlo/r.unit;
1052 	nhi = xhi/r.unit;
1053 	if (nhi >= digs10pow)
1054 		r.off = r.unit*lead_digits(nlo, nhi-nlo);
1055 	else if (nlo <= -digs10pow)
1056 		r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
1057 	else	r.off = 0;
1058 	r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
1059 	switch (r.logunit%3) {
1060 	case 1:	r.unit*=.1; r.logunit--;
1061 		break;
1062 	case -1: case 2:
1063 		r.unit*=10; r.logunit++;
1064 		break;
1065 	case -2: r.unit*=100; r.logunit+=2;
1066 	}
1067 	r.logunit /= 3;
1068 	return r;
1069 }
1070 
1071 
min_hsep(const transform * tr)1072 double min_hsep(const transform* tr)
1073 {
1074 	double s = (2+labdigs)*sdigit.x;
1075 	double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
1076 	return dxuntransform(tr, ss);
1077 }
1078 
1079 
mark_x_axis(const transform * tr)1080 lab_interval mark_x_axis(const transform* tr)
1081 {
1082 	fpoint p = univ.disp.min;
1083 	Point q, qtop, qbot, tmp;
1084 	double x0=univ.disp.min.x, x1=univ.disp.max.x;
1085 	double seps0, nseps, seps;
1086 	lab_interval iv = next_larger(min_hsep(tr), x0, x1);
1087 	set_unslanted_y(&univ, &p.y, 0);
1088 	q.y = ytransform(tr, p.y) + .5;
1089 	qtop.y = q.y - tick_len;
1090 	qbot.y = q.y + framewd + framesep;
1091 	seps0 = ceil(x0/iv.sep);
1092 	for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1093 		char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
1094 		Font* f = display->defaultfont;
1095 		q.x = qtop.x = qbot.x = xtransform(tr, p.x);
1096 		line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
1097 		tmp = stringsize(f, num);
1098 		qbot.x -= tmp.x/2;
1099 		string(screen, qbot, display->black, qbot, f, num);
1100 	}
1101 	return iv;
1102 }
1103 
1104 
mark_y_axis(const transform * tr)1105 lab_interval mark_y_axis(const transform* tr)
1106 {
1107 	Font* f = display->defaultfont;
1108 	fpoint p = univ.disp.min;
1109 	Point q, qrt, qlft;
1110 	double y0, y1, seps0, nseps, seps;
1111 	lab_interval iv;
1112 	set_unslanted_y(&univ, &y0, &y1);
1113 	iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
1114 	q.x = xtransform(tr, p.x) - .5;
1115 	qrt.x = q.x + tick_len;
1116 	qlft.x = q.x - (framewd + framesep);
1117 	seps0 = ceil(y0/iv.sep);
1118 	for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1119 		char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
1120 		Point qq = stringsize(f, num);
1121 		q.y = qrt.y = qlft.y = ytransform(tr, p.y);
1122 		line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
1123 		qq.x = qlft.x - qq.x;
1124 		qq.y = qlft.y - qq.y/2;
1125 		string(screen, qq, display->black, qq, f, num);
1126 	}
1127 	return iv;
1128 }
1129 
1130 
lab_iv_info(const lab_interval * iv,double slant,char * buf,int * n)1131 void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
1132 {
1133 	if (iv->off > 0)
1134 		(*n) += sprintf(buf+*n,"-%.12g",iv->off);
1135 	else if (iv->off < 0)
1136 		(*n) += sprintf(buf+*n,"+%.12g",-iv->off);
1137 	if (slant>0)
1138 		(*n) += sprintf(buf+*n,"-%.6gx", slant);
1139 	else if (slant<0)
1140 		(*n) += sprintf(buf+*n,"+%.6gx", -slant);
1141 	if (abs(iv->logunit) >= Nthous)
1142 		(*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
1143 	else if (iv->logunit > 0)
1144 		(*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
1145 	else if (iv->logunit < 0)
1146 		(*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
1147 }
1148 
1149 
draw_xy_ranges(const lab_interval * xiv,const lab_interval * yiv)1150 void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
1151 {
1152 	Point p;
1153 	char buf[2*(19+Len_thous+8)+50];
1154 	int bufn = 0;
1155 	buf[bufn++] = 'x';
1156 	lab_iv_info(xiv, 0, buf, &bufn);
1157 	bufn += sprintf(buf+bufn, "; y");
1158 	lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
1159 	buf[bufn] = '\0';
1160 	p = stringsize(display->defaultfont, buf);
1161 	top_left = screen->r.min.x + lft_border;
1162 	p.x = top_right = screen->r.max.x - rt_border - p.x;
1163 	p.y = screen->r.min.y + outersep;
1164 	string(screen, p, display->black, p, display->defaultfont, buf);
1165 }
1166 
1167 
draw_frame(void)1168 transform draw_frame(void)
1169 {
1170 	lab_interval x_iv, y_iv;
1171 	transform tr;
1172 	Rectangle r = screen->r;
1173 	lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
1174 	tr = cur_trans();
1175 	r.min.x += lft_border;
1176 	r.min.y += top_border;
1177 	r.max.x -= rt_border;
1178 	r.max.y -= bot_border;
1179 	border(screen, r, -framewd, axis_color, r.min);
1180 	x_iv = mark_x_axis(&tr);
1181 	y_iv = mark_y_axis(&tr);
1182 	draw_xy_ranges(&x_iv, &y_iv);
1183 	return tr;
1184 }
1185 
1186 
1187 
1188 /*************************** Finding the selection  ***************************/
1189 
1190 typedef struct pt_on_fpoly {
1191 	fpoint p;			/* the point */
1192 	fpolygon* fp;			/* the fpolygon it lies on */
1193 	double t;			/* how many knots from the beginning */
1194 } pt_on_fpoly;
1195 
1196 
1197 static double myx, myy;
1198 #define mydist(p,o,sl,xwt,ywt)	(myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y,	\
1199 					xwt*myx*myx + ywt*myy*myy)
1200 
1201 /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
1202    minimized?
1203 */
closest_time(const fpoint * p0,const fpoint * ctr,double slant,double xwt,double ywt)1204 double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
1205 		double xwt, double ywt)
1206 {
1207 	double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
1208 	double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
1209 	double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
1210 	double bot = xwt*dx*dx + ywt*dy*dy;
1211 	if (bot==0)
1212 		return 0;
1213 	return -(xwt*x0*dx + ywt*y0*dy)/bot;
1214 }
1215 
1216 
1217 /* Scan the polygonal path of length len knots starting at p0, and find the
1218    point that the transformation y=y-x*slant makes closest to the center of *r,
1219    where *r itself defines the distance metric.  Knots get higher priority than
1220    points between knots.  If psel->t is negative, always update *psel; otherwise
1221    update *psel only if the scan can improve it.  Return a boolean that says
1222    whether *psel was updated.
1223      Note that *r is a very tiny rectangle (tiny when converted screen pixels)
1224    such that anything in *r is considered close enough to match the mouse click.
1225    The purpose of this routine is to be careful in case there is a lot of hidden
1226    detail in the tiny rectangle *r.
1227 */
improve_pt(fpoint * p0,double len,const frectangle * r,double slant,pt_on_fpoly * psel)1228 int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
1229 		pt_on_fpoly* psel)
1230 {
1231 	fpoint ctr = fcenter(r);
1232 	double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
1233 	double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
1234 	double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
1235 	double tt, dbest0 = dbest;
1236 	fpoint pp;
1237 	int ilen = (int) len;
1238 	if (len==0 || ilen>0) {
1239 		int i;
1240 		for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
1241 			d = mydist(p0[i], ctr, slant, xwt, ywt);
1242 			if (d < dbest)
1243 				{psel->p=p0[i]; psel->t=i; dbest=d;}
1244 		}
1245 		return (dbest < dbest0);
1246 	}
1247 	tt = closest_time(p0, &ctr, slant, xwt, ywt);
1248 	if (tt > len)
1249 		tt = len;
1250 	pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
1251 	pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
1252 	if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
1253 		psel->p = pp;
1254 		psel->t = tt;
1255 		return 1;
1256 	}
1257 	return 0;
1258 }
1259 
1260 
1261 /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
1262 */
select_in_fpoly(fpolygon * fp,const frectangle * r,double slant,pt_on_fpoly * psel)1263 void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
1264 		pt_on_fpoly* psel)
1265 {
1266 	fpoint *p0=fp->p, *pn=fp->p+fp->n;
1267 	double l1, l2;
1268 	if (p0==pn)
1269 		{improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
1270 	while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
1271 		fpoint p0sav;
1272 		int i1 = (int) l1;
1273 		p0+=i1; l1-=i1;
1274 		p0sav = *p0;
1275 		p0[0].x += l1*(p0[1].x - p0[0].x);
1276 		p0[0].y += l1*(p0[1].y - p0[0].y);
1277 		l2 = in_length(p0, pn, *r, slant);
1278 		if (improve_pt(p0, l2, r, slant, psel)) {
1279 			if (l1==0 && psel->t!=((int) psel->t)) {
1280 				psel->t = 0;
1281 				psel->p = *p0;
1282 			} else if (psel->t < 1)
1283 				psel->t += l1*(1 - psel->t);
1284 			psel->t += p0 - fp->p;
1285 			psel->fp = fp;
1286 		}
1287 		*p0 = p0sav;
1288 		p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1289 	}
1290 }
1291 
1292 
1293 /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
1294    the resulting selection, if any.
1295 */
select_in_univ(const frectangle * r,double slant)1296 pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
1297 {
1298 	static pt_on_fpoly answ;
1299 	fpolygon* fp;
1300 	answ.t = -1;
1301 	for (fp=univ.p; fp!=0; fp=fp->link)
1302 		if (fintersects(r, &fp->bb, slant))
1303 			select_in_fpoly(fp, r, slant, &answ);
1304 	if (answ.t < 0)
1305 		return 0;
1306 	return &answ;
1307 }
1308 
1309 
1310 
1311 /**************************** Using the selection  ****************************/
1312 
1313 pt_on_fpoly cur_sel;			/* current selection if cur_sel.t>=0 */
1314 pt_on_fpoly prev_sel;			/* previous selection if prev_sel.t>=0 (for slant) */
1315 Image* sel_bkg = 0;			/* what's behind the red dot */
1316 
1317 
clear_txt(void)1318 void clear_txt(void)
1319 {
1320 	Rectangle r;
1321 	r.min = screen->r.min;
1322 	r.min.x += lft_border;
1323 	r.min.y += outersep;
1324 	r.max.x = top_left;
1325 	r.max.y = r.min.y + smaxch.y;
1326 	draw(screen, r, display->white, display->opaque, r.min);
1327 	top_left = r.min.x;
1328 }
1329 
1330 
sel_dot_box(const transform * tr)1331 Rectangle sel_dot_box(const transform* tr)
1332 {
1333 	Point ctr;
1334 	Rectangle r;
1335 	if (tr==0)
1336 		ctr.x = ctr.y = Dotrad;
1337 	else	do_transform(&ctr, tr, &cur_sel.p);
1338 	r.min.x=ctr.x-Dotrad;  r.max.x=ctr.x+Dotrad+1;
1339 	r.min.y=ctr.y-Dotrad;  r.max.y=ctr.y+Dotrad+1;
1340 	return r;
1341 }
1342 
1343 
unselect(const transform * tr)1344 void unselect(const transform* tr)
1345 {
1346 	transform tra;
1347 	if (sel_bkg==0)
1348 		sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
1349 	clear_txt();
1350 	if (cur_sel.t < 0)
1351 		return;
1352 	prev_sel = cur_sel;
1353 	if (tr==0)
1354 		{tra=cur_trans(); tr=&tra;}
1355 	draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
1356 	cur_sel.t = -1;
1357 }
1358 
1359 
1360 /* Text at top right is written first and this low-level routine clobbers it if
1361    the new top-left text would overwrite it.  However, users of this routine should
1362    try to keep the new text short enough to avoid this.
1363 */
show_mytext(char * msg)1364 void show_mytext(char* msg)
1365 {
1366 	Point tmp, pt = screen->r.min;
1367 	int siz;
1368 	tmp = stringsize(display->defaultfont, msg);
1369 	siz = tmp.x;
1370 	pt.x=top_left;  pt.y+=outersep;
1371 	if (top_left+siz > top_right) {
1372 		Rectangle r;
1373 		r.min.y = pt.y;
1374 		r.min.x = top_right;
1375 		r.max.y = r.min.y + smaxch.y;
1376 		r.max.x = top_left+siz;
1377 		draw(screen, r, display->white, display->opaque, r.min);
1378 		top_right = top_left+siz;
1379 	}
1380 	string(screen, pt, display->black, ZP, display->defaultfont, msg);
1381 	top_left += siz;
1382 }
1383 
1384 
rnd(double x,double tol)1385 double rnd(double x, double tol)	/* round to enough digits for accuracy tol */
1386 {
1387 	double t = pow(10, floor(log10(tol)));
1388 	return t * floor(x/t + .5);
1389 }
1390 
t_tol(double xtol,double ytol)1391 double t_tol(double xtol, double ytol)
1392 {
1393 	int t = (int) floor(cur_sel.t);
1394 	fpoint* p = cur_sel.fp->p;
1395 	double dx, dy;
1396 	if (t==cur_sel.t)
1397 		return 1;
1398 	dx = fabs(p[t+1].x - p[t].x);
1399 	dy = fabs(p[t+1].y - p[t].y);
1400 	xtol /= (xtol>dx) ? xtol : dx;
1401 	ytol /= (ytol>dy) ? ytol : dy;
1402 	return (xtol<ytol) ? xtol : ytol;
1403 }
1404 
say_where(const transform * tr)1405 void say_where(const transform* tr)
1406 {
1407 	double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
1408 	char buf[100];
1409 	int n, nmax = (top_right - top_left)/smaxch.x;
1410 	if (nmax >= 100)
1411 		nmax = 100-1;
1412 	n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
1413 			rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
1414 			rnd(cur_sel.t, t_tol(xtol,ytol)));
1415 	if (cur_sel.fp->nam[0] != 0)
1416 		sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
1417 	show_mytext(buf);
1418 }
1419 
1420 
reselect(const transform * tr)1421 void reselect(const transform* tr)	/* uselect(); set cur_sel; call this */
1422 {
1423 	Point pt2, pt3;
1424 	fpoint p2;
1425 	transform tra;
1426 	if (cur_sel.t < 0)
1427 		return;
1428 	if (tr==0)
1429 		{tra=cur_trans(); tr=&tra;}
1430 	do_transform(&p2, tr, &cur_sel.p);
1431 	if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
1432 		{cur_sel.t= -1; return;}
1433 	pt3.x=pt2.x-Dotrad;  pt3.y=pt2.y-Dotrad;
1434 	draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
1435 	fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
1436 	say_where(tr);
1437 }
1438 
1439 
do_select(Point pt)1440 void do_select(Point pt)
1441 {
1442 	transform tr = cur_trans();
1443 	fpoint pt1, pt2, ctr;
1444 	frectangle r;
1445 	double slant;
1446 	pt_on_fpoly* psel;
1447 	unselect(&tr);
1448 	do_untransform(&ctr, &tr, &pt);
1449 	pt1.x=pt.x-fuzz;  pt1.y=pt.y+fuzz;
1450 	pt2.x=pt.x+fuzz;  pt2.y=pt.y-fuzz;
1451 	do_untransform(&r.min, &tr, &pt1);
1452 	do_untransform(&r.max, &tr, &pt2);
1453 	slant = u_slant_amt(&univ);
1454 	slant_frect(&r, -slant);
1455 	psel = select_in_univ(&r, slant);
1456 	if (psel==0)
1457 		return;
1458 	if (logfil!=0) {
1459 		fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
1460 		fflush(logfil);
1461 	}
1462 	cur_sel = *psel;
1463 	reselect(&tr);
1464 }
1465 
1466 
1467 /***************************** Prompting for text *****************************/
1468 
unshow_mytext(char * msg)1469 void unshow_mytext(char* msg)
1470 {
1471 	Rectangle r;
1472 	Point siz = stringsize(display->defaultfont, msg);
1473 	top_left -= siz.x;
1474 	r.min.y = screen->r.min.y + outersep;
1475 	r.min.x = top_left;
1476 	r.max.y = r.min.y + siz.y;
1477 	r.max.x = r.min.x + siz.x;
1478 	draw(screen, r, display->white, display->opaque, r.min);
1479 }
1480 
1481 
1482 /* Show the given prompt and read a line of user input.  The text appears at the
1483    top left.  If it runs into the top right text, we stop echoing but let the user
1484    continue typing blind if he wants to.
1485 */
prompt_text(char * prompt)1486 char* prompt_text(char* prompt)
1487 {
1488 	static char buf[200];
1489 	int n0, n=0, nshown=0;
1490 	Rune c;
1491 	unselect(0);
1492 	show_mytext(prompt);
1493 	while (n<200-1-UTFmax && (c=ekbd())!='\n') {
1494 		if (c=='\b') {
1495 			buf[n] = 0;
1496 			if (n > 0)
1497 				do n--;
1498 				while (n>0 && (buf[n-1]&0xc0)==0x80);
1499 			if (n < nshown)
1500 				{unshow_mytext(buf+n); nshown=n;}
1501 		} else {
1502 			n0 = n;
1503 			n += runetochar(buf+n, &c);
1504 			buf[n] = 0;
1505 			if (nshown==n0 && top_right-top_left >= smaxch.x)
1506 				{show_mytext(buf+n0); nshown=n;}
1507 		}
1508 	}
1509 	buf[n] = 0;
1510 	while (ecanmouse())
1511 		emouse();
1512 	return buf;
1513 }
1514 
1515 
1516 /**************************** Redrawing the screen ****************************/
1517 
1518 /* Let p0 and its successors define a piecewise-linear function of a paramter t,
1519    and draw the 0<=t<=n1 portion using transform *tr.
1520 */
draw_fpts(const fpoint * p0,double n1,const transform * tr,int thick,Image * clr)1521 void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
1522 		Image* clr)
1523 {
1524 	int n = (int) n1;
1525 	const fpoint* p = p0 + n;
1526 	fpoint pp;
1527 	Point qq, q;
1528 	if (n1 > n) {
1529 		pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
1530 		pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
1531 	} else	pp = *p--;
1532 	do_transform(&qq, tr, &pp);
1533 	if (n1==0)
1534 		fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
1535 	for (; p>=p0; p--) {
1536 		do_transform(&q, tr, p);
1537 		if(plotdots)
1538 			fillellipse(screen, q, Dotrad, Dotrad, clr, q);
1539 		else
1540 			line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
1541 		qq = q;
1542 	}
1543 }
1544 
draw_1fpoly(const fpolygon * fp,const transform * tr,Image * clr,const frectangle * udisp,double slant)1545 void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
1546 		const frectangle *udisp, double slant)
1547 {
1548 	fpoint *p0=fp->p, *pn=fp->p+fp->n;
1549 	double l1, l2;
1550 	if (p0==pn && fcontains(udisp,*p0))
1551 		{draw_fpts(p0, 0, tr, fp->c.thick, clr); return;}
1552 	while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
1553 		fpoint p0sav;
1554 		int i1 = (int) l1;
1555 		p0+=i1; l1-=i1;
1556 		p0sav = *p0;
1557 		p0[0].x += l1*(p0[1].x - p0[0].x);
1558 		p0[0].y += l1*(p0[1].y - p0[0].y);
1559 		l2 = in_length(p0, pn, *udisp, slant);
1560 		draw_fpts(p0, l2, tr, fp->c.thick, clr);
1561 		*p0 = p0sav;
1562 		p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1563 	}
1564 }
1565 
1566 
get_clip_data(const fpolygons * u,frectangle * r)1567 double get_clip_data(const fpolygons *u, frectangle *r)
1568 {
1569 	double slant = set_unslanted_y(u, &r->min.y, &r->max.y);
1570 	r->min.x = u->disp.min.x;
1571 	r->max.x = u->disp.max.x;
1572 	return slant;
1573 }
1574 
1575 
draw_fpoly(const fpolygon * fp,const transform * tr,Image * clr)1576 void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
1577 {
1578 	frectangle r;
1579 	double slant = get_clip_data(&univ, &r);
1580 	draw_1fpoly(fp, tr, clr, &r, slant);
1581 }
1582 
1583 
eresized(int new)1584 void eresized(int new)
1585 {
1586 	transform tr;
1587 	fpolygon* fp;
1588 	frectangle clipr;
1589 	double slant;
1590 	if(new && getwindow(display, Refmesg) < 0) {
1591 		fprintf(stderr,"can't reattach to window\n");
1592 		exits("reshap");
1593 	}
1594 	draw(screen, screen->r, display->white, display->opaque, screen->r.min);
1595 	tr = draw_frame();
1596 	slant = get_clip_data(&univ, &clipr);
1597 	for (fp=univ.p; fp!=0; fp=fp->link)
1598 		if (fintersects(&clipr, &fp->bb, slant))
1599 			draw_1fpoly(fp, &tr, fp->c.clr, &clipr, slant);
1600 	reselect(0);
1601 	if (mv_bkgd!=0 && mv_bkgd->repl==0) {
1602 		freeimage(mv_bkgd);
1603 		mv_bkgd = display->white;
1604 	}
1605 	flushimage(display, 1);
1606 }
1607 
1608 
1609 
1610 
1611 /********************************* Recoloring *********************************/
1612 
draw_palette(int n)1613 int draw_palette(int n)		/* n is number of colors; returns patch dy */
1614 {
1615 	int y0 = screen->r.min.y + top_border;
1616 	int dy = (screen->r.max.y - bot_border - y0)/n;
1617 	Rectangle r;
1618 	int i;
1619 	r.min.y = y0;
1620 	r.min.x = screen->r.max.x - rt_border + framewd;
1621 	r.max.y = y0 + dy;
1622 	r.max.x = screen->r.max.x;
1623 	for (i=0; i<n; i++) {
1624 		draw(screen, r, clrtab[i].im, display->opaque, r.min);
1625 		r.min.y = r.max.y;
1626 		r.max.y += dy;
1627 	}
1628 	return dy;
1629 }
1630 
1631 
palette_color(Point pt,int dy,int n)1632 Image* palette_color(Point pt, int dy, int n)
1633 {				/* mouse at pt, patch size dy, n colors */
1634 	int yy;
1635 	if (screen->r.max.x - pt.x > rt_border - framewd)
1636 		return 0;
1637 	yy = pt.y - (screen->r.min.y + top_border);
1638 	if (yy<0 || yy>=n*dy)
1639 		return 0;
1640 	return clrtab[yy/dy].im;
1641 }
1642 
1643 
all_set_clr(fpolygons * fps,Image * clr)1644 void all_set_clr(fpolygons* fps, Image* clr)
1645 {
1646 	fpolygon* p;
1647 	for (p=fps->p; p!=0; p=p->link)
1648 		p->c.clr = clr;
1649 }
1650 
1651 
all_set_scheme(fpolygons * fps,int scheme)1652 void all_set_scheme(fpolygons* fps, int scheme)
1653 {
1654 	fpolygon* p;
1655 	for (p=fps->p; p!=0; p=p->link)
1656 		if (p->ct!=0 && scheme <= p->ct[0].thick)
1657 			p->c = p->ct[scheme];
1658 }
1659 
1660 
do_recolor(int but,Mouse * m,int alluniv)1661 void do_recolor(int but, Mouse* m, int alluniv)
1662 {
1663 	int sel, clkk, nclr = clr_id(DWhite);
1664 	int dy = draw_palette(nclr);
1665 	Image* clr;
1666 	clkk = get_click_or_kbd(but, m, "123456789abcdefghijklmnopqrstuvwxyz");
1667 	if (clkk < 0) {
1668 		clr = palette_color(m->xy, dy, nclr);
1669 		if (clr != 0) {
1670 			if (alluniv)
1671 				all_set_clr(&univ, clr);
1672 			else cur_sel.fp->c.clr = clr;
1673 		}
1674 		eresized(0);
1675 		lift_button(but, m, Never);
1676 	} else if (clkk > 0) {
1677 		sel = ('0'<clkk&&clkk<='9') ? 0 : 10+(clkk-'a')*10;
1678 		while (!('0'<=clkk&&clkk<='9'))
1679 			clkk = ekbd();
1680 		sel += clkk-'0';
1681 		if (alluniv)
1682 			all_set_scheme(&univ, sel);
1683 		else if (sel <= cur_sel.fp->ct[0].thick)
1684 			cur_sel.fp->c = cur_sel.fp->ct[sel];
1685 	}
1686 	eresized(0);
1687 }
1688 
1689 
1690 /****************************** Move and rotate  ******************************/
1691 
prepare_mv(const fpolygon * fp)1692 void prepare_mv(const fpolygon* fp)
1693 {
1694 	Rectangle r = screen->r;
1695 	Image* scr0;
1696 	int dt = 1 + fp->c.thick;
1697 	r.min.x+=lft_border-dt;  r.min.y+=top_border-dt;
1698 	r.max.x-=rt_border-dt;   r.max.y-=bot_border-dt;
1699 	if (mv_bkgd!=0 && mv_bkgd->repl==0)
1700 		freeimage(mv_bkgd);
1701 	mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
1702 	if (mv_bkgd==0)
1703 		mv_bkgd = display->white;
1704 	else {	transform tr = cur_trans();
1705 		draw(mv_bkgd, r, screen, display->opaque, r.min);
1706 		draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
1707 		scr0 = screen;
1708 		screen = mv_bkgd;
1709 		draw_fpoly(fp, &tr, display->white);
1710 		screen = scr0;
1711 	}
1712 }
1713 
1714 
move_fp(fpolygon * fp,double dx,double dy)1715 void move_fp(fpolygon* fp, double dx, double dy)
1716 {
1717 	fpoint *p, *pn=fp->p+fp->n;
1718 	for (p=fp->p; p<=pn; p++) {
1719 		(p->x) += dx;
1720 		(p->y) += dy;
1721 	}
1722 	(fp->bb.min.x)+=dx;  (fp->bb.min.y)+=dy;
1723 	(fp->bb.max.x)+=dx;  (fp->bb.max.y)+=dy;
1724 }
1725 
1726 
rotate_fp(fpolygon * fp,fpoint o,double theta)1727 void rotate_fp(fpolygon* fp, fpoint o, double theta)
1728 {
1729 	double s=sin(theta), c=cos(theta);
1730 	fpoint *p, *pn=fp->p+fp->n;
1731 	for (p=fp->p; p<=pn; p++) {
1732 		double x=p->x-o.x, y=p->y-o.y;
1733 		(p->x) = o.x + c*x - s*y;
1734 		(p->y) = o.y + s*x + c*y;
1735 	}
1736 	set_fbb(fp);
1737 }
1738 
1739 
1740 /* Move the selected fpolygon so the selected point tracks the mouse, and return
1741    the total amount of movement.  Button but has already been held down for at
1742    least Mv_delay milliseconds and the mouse might have moved some distance.
1743 */
do_move(int but,Mouse * m)1744 fpoint do_move(int but, Mouse* m)
1745 {
1746 	transform tr = cur_trans();
1747 	int bbit = Button_bit(but);
1748 	fpolygon* fp = cur_sel.fp;
1749 	fpoint loc, loc0=cur_sel.p;
1750 	double tsav = cur_sel.t;
1751 	unselect(&tr);
1752 	do {	latest_mouse(but, m);
1753 		(fp->c.thick)++;		/* line() DISAGREES WITH ITSELF */
1754 		draw_fpoly(fp, &tr, mv_bkgd);
1755 		(fp->c.thick)--;
1756 		do_untransform(&loc, &tr, &m->xy);
1757 		move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
1758 		cur_sel.p = loc;
1759 		draw_fpoly(fp, &tr, fp->c.clr);
1760 	} while (m->buttons & bbit);
1761 	cur_sel.t = tsav;
1762 	reselect(&tr);
1763 	loc.x -= loc0.x;
1764 	loc.y -= loc0.y;
1765 	return loc;
1766 }
1767 
1768 
dir_angle(const Point * pt,const transform * tr)1769 double dir_angle(const Point* pt, const transform* tr)
1770 {
1771 	fpoint p;
1772 	double dy, dx;
1773 	do_untransform(&p, tr, pt);
1774 	dy=p.y-cur_sel.p.y;  dx=p.x-cur_sel.p.x;
1775 	return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
1776 }
1777 
1778 
1779 /* Rotate the selected fpolygon around the selection point so as to track the
1780    direction angle from the selected point to m->xy.  Stop when button but goes
1781    up and return the total amount of rotation in radians.
1782 */
do_rotate(int but,Mouse * m)1783 double do_rotate(int but, Mouse* m)
1784 {
1785 	transform tr = cur_trans();
1786 	int bbit = Button_bit(but);
1787 	fpolygon* fp = cur_sel.fp;
1788 	double theta0 = dir_angle(&m->xy, &tr);
1789 	double th, theta = theta0;
1790 	do {	latest_mouse(but, m);
1791 		(fp->c.thick)++;		/* line() DISAGREES WITH ITSELF */
1792 		draw_fpoly(fp, &tr, mv_bkgd);
1793 		(fp->c.thick)--;
1794 		th = dir_angle(&m->xy, &tr);
1795 		rotate_fp(fp, cur_sel.p, th-theta);
1796 		theta = th;
1797 		draw_fpoly(fp, &tr, fp->c.clr);
1798 	} while (m->buttons & bbit);
1799 	unselect(&tr);
1800 	cur_sel = prev_sel;
1801 	reselect(&tr);
1802 	return theta - theta0;
1803 }
1804 
1805 
1806 
1807 /********************************* Edit menu  *********************************/
1808 
1809 typedef enum e_index {
1810 		Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
1811 		Emove
1812 } e_index;
1813 
1814 char* e_items[Eoptions+1];
1815 
1816 Menu e_menu = {e_items, 0, 0};
1817 
1818 
1819 typedef struct e_action {
1820 	e_index typ;			/* What type of action */
1821 	fpolygon* fp;			/* fpolygon the action applies to */
1822 	Image* clr;			/* color to use if typ==Erecolor */
1823 	double amt;			/* rotation angle or line thickness */
1824 	fpoint pt;			/* movement vector or rotation center */
1825 	struct e_action* link;		/* next in a stack */
1826 } e_action;
1827 
1828 e_action* unact = 0;			/* heads a linked list of actions */
1829 e_action* do_undo(e_action*);		/* pop off an e_action and (un)do it */
1830 e_action* save_act(e_action*,e_index);	/* append new e_action for status quo */
1831 
1832 
save_mv(fpoint movement)1833 void save_mv(fpoint movement)
1834 {
1835 	unact = save_act(unact, Emove);
1836 	unact->pt = movement;
1837 }
1838 
1839 
init_e_menu(void)1840 void init_e_menu(void)
1841 {
1842 	char* u = "can't undo";
1843 	e_items[Erecolor] = "recolor";
1844 	e_items[Edelete] = "delete";
1845 	e_items[Erotate] = "rotate";
1846 	e_items[Eoptions-cantmv] = 0;
1847 	e_items[Ethick] = (cur_sel.fp->c.thick >0) ? "thin" : "thick";
1848 	if (unact!=0)
1849 		switch (unact->typ) {
1850 		case Erecolor: u="uncolor"; break;
1851 		case Ethick: u=(unact->fp->c.thick==0) ? "unthin" : "unthicken";
1852 			break;
1853 		case Edelete: u="undelete"; break;
1854 		case Emove: u="unmove"; break;
1855 		case Erotate: u="unrotate"; break;
1856 		}
1857 	e_items[Eundo] = u;
1858 }
1859 
1860 
do_emenu(int but,Mouse * m)1861 void do_emenu(int but, Mouse* m)
1862 {
1863 	int h;
1864 	if (cur_sel.t < 0)
1865 		return;
1866 	init_e_menu();
1867 	h = emenuhit(but, m, &e_menu);
1868 	switch(h) {
1869 	case Ethick: unact = save_act(unact, h);
1870 		cur_sel.fp->c.thick ^= 1;
1871 		eresized(0);
1872 		break;
1873 	case Edelete: unact = save_act(unact, h);
1874 		fp_remove(&univ, cur_sel.fp);
1875 		unselect(0);
1876 		eresized(0);
1877 		break;
1878 	case Erecolor: unact = save_act(unact, h);
1879 		do_recolor(but, m, 0);
1880 		break;
1881 	case Erotate: unact = save_act(unact, h);
1882 		prepare_mv(cur_sel.fp);
1883 		if (get_1click(but, m, 0)) {
1884 			unact->pt = cur_sel.p;
1885 			unact->amt = do_rotate(but, m);
1886 		}
1887 		break;
1888 	case Eundo: unact = do_undo(unact);
1889 		break;
1890 	}
1891 }
1892 
1893 
1894 
1895 /******************************* Undoing edits  *******************************/
1896 
save_act(e_action * a0,e_index typ)1897 e_action* save_act(e_action* a0, e_index typ)
1898 {					/* append new e_action for status quo */
1899 	e_action* a = malloc(sizeof(e_action));
1900 	a->link = a0;
1901 	a->pt.x = a->pt.y = 0.0;
1902 	a->amt = cur_sel.fp->c.thick;
1903 	a->clr = cur_sel.fp->c.clr;
1904 	a->fp = cur_sel.fp;
1905 	a->typ = typ;
1906 	return a;
1907 }
1908 
1909 
1910 /* This would be trivial except it's nice to preserve the selection in order to make
1911    it easy to undo a series of moves.  (There's no do_unrotate() because it's harder
1912    and less important to preserve the selection in that case.)
1913 */
do_unmove(e_action * a)1914 void do_unmove(e_action* a)
1915 {
1916 	double tsav = cur_sel.t;
1917 	unselect(0);
1918 	move_fp(a->fp, -a->pt.x, -a->pt.y);
1919 	if (a->fp == cur_sel.fp) {
1920 		cur_sel.p.x -= a->pt.x;
1921 		cur_sel.p.y -= a->pt.y;
1922 	}
1923 	cur_sel.t = tsav;
1924 	reselect(0);
1925 }
1926 
1927 
do_undo(e_action * a0)1928 e_action* do_undo(e_action* a0)		/* pop off an e_action and (un)do it */
1929 {
1930 	e_action* a = a0;
1931 	if (a==0)
1932 		return 0;
1933 	switch(a->typ) {
1934 	case Ethick: a->fp->c.thick = a->amt;
1935 		eresized(0);
1936 		break;
1937 	case Erecolor: a->fp->c.clr = a->clr;
1938 		eresized(0);
1939 		break;
1940 	case Edelete:
1941 		a->fp->link = univ.p;
1942 		univ.p = a->fp;
1943 		grow_bb(&univ.bb, &a->fp->bb);
1944 		eresized(0);
1945 		break;
1946 	case Emove:
1947 		do_unmove(a);
1948 		eresized(0);
1949 		break;
1950 	case Erotate:
1951 		unselect(0);
1952 		rotate_fp(a->fp, a->pt, -a->amt);
1953 		eresized(0);
1954 		break;
1955 	}
1956 	a0 = a->link;
1957 	free(a);
1958 	return a0;
1959 }
1960 
1961 
1962 
1963 /********************************* Main menu  *********************************/
1964 
1965 enum m_index {     Mzoom_in,  Mzoom_out,  Munzoom,  Mslant,    Munslant,
1966 		Msquare_up,  Mrecenter,  Mrecolor,  Mrestack,  Mread,
1967 		Mwrite,      Mexit};
1968 char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant",   "unslant",
1969 		"square up", "recenter", "recolor", "restack", "read",
1970 		"write",     "exit", 0};
1971 
1972 Menu m_menu = {m_items, 0, 0};
1973 
1974 
do_mmenu(int but,Mouse * m)1975 void do_mmenu(int but, Mouse* m)
1976 {
1977 	int e, h = emenuhit(but, m, &m_menu);
1978 	switch (h) {
1979 	case Mzoom_in:
1980 		disp_zoomin(egetrect(but,m));
1981 		eresized(0);
1982 		break;
1983 	case Mzoom_out:
1984 		disp_zoomout(egetrect(but,m));
1985 		eresized(0);
1986 		break;
1987 	case Msquare_up:
1988 		disp_squareup();
1989 		eresized(0);
1990 		break;
1991 	case Munzoom:
1992 		init_disp();
1993 		eresized(0);
1994 		break;
1995 	case Mrecenter:
1996 		if (get_1click(but, m, &bullseye)) {
1997 			recenter_disp(m->xy);
1998 			eresized(0);
1999 			lift_button(but, m, Never);
2000 		}
2001 		break;
2002 	case Mslant:
2003 		if (cur_sel.t>=0 && prev_sel.t>=0) {
2004 			slant_disp(prev_sel.p, cur_sel.p);
2005 			eresized(0);
2006 		}
2007 		break;
2008 	case Munslant:
2009 		univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
2010 		eresized(0);
2011 		break;
2012 	case Mrecolor:
2013 		do_recolor(but, m, 1);
2014 		break;
2015 	case Mrestack:
2016 		fps_invert(&univ);
2017 		eresized(0);
2018 		break;
2019 	case Mread:
2020 		e = doinput(prompt_text("File:"));
2021 		if (e==0)
2022 			eresized(0);
2023 		else if (e<0)
2024 			show_mytext(" - can't read");
2025 		else {
2026 			char ebuf[80];
2027 			snprintf(ebuf, 80, " - error line %d", e);
2028 			show_mytext(ebuf);
2029 		}
2030 		break;
2031 	case Mwrite:
2032 		if (!dooutput(prompt_text("File:")))
2033 			show_mytext(" - can't write");
2034 		break;
2035 	case Mexit:
2036 		exits("");
2037 	}
2038 }
2039 
2040 
2041 
2042 /****************************** Handling events  ******************************/
2043 
doevent(void)2044 void doevent(void)
2045 {
2046 	ulong etype;
2047 	int mobile;
2048 	ulong mvtime;
2049 	Event	ev;
2050 
2051 	etype = eread(Emouse|Ekeyboard, &ev);
2052 	if(etype & Emouse) {
2053 		if (ev.mouse.buttons & But1) {
2054 			do_select(ev.mouse.xy);
2055 			mvtime = Never;
2056 			mobile = !cantmv && cur_sel.t>=0;
2057 			if (mobile) {
2058 				mvtime = ev.mouse.msec + Mv_delay;
2059 				prepare_mv(cur_sel.fp);
2060 			}
2061 			if (!lift_button(1, &ev.mouse, mvtime) && mobile)
2062 				save_mv(do_move(1, &ev.mouse));
2063 		} else if (ev.mouse.buttons & But2)
2064 			do_emenu(2, &ev.mouse);
2065 		else if (ev.mouse.buttons & But3)
2066 			do_mmenu(3, &ev.mouse);
2067 	} else if (etype & Ekeyboard) {
2068 		if (ev.kbdc=='\n' && cur_sel.t>=0 && logfil!=0) {
2069 			fprintf(logfil,"%s\n", cur_sel.fp->nam);
2070 			fflush(logfil);
2071 		}
2072 	}
2073 }
2074 
2075 
2076 
2077 /******************************** Main program ********************************/
2078 
2079 extern char* argv0;
2080 
usage(void)2081 void usage(void)
2082 {
2083 	int i;
2084 	fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
2085 	fprintf(stderr,
2086 "option ::= -l logfile | -m | -p\n"
2087 "\n"
2088 "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
2089 "by spaces with a label after each polyline), and view it interactively.  Use\n"
2090 "standard input if no infile is specified.\n"
2091 "Option -l specifies a file in which to log the coordinates of each point selected.\n"
2092 "(Clicking a point with button one selects it and displays its coordinates and\n"
2093 "the label of its polylone.)  Option -m allows polylines to be moved and rotated.\n"
2094 "The -p option plots only the vertices of the polygons.\n"
2095 "The polyline labels can use the following color names:"
2096 	);
2097 	for (i=0; clrtab[i].c!=DNofill; i++)
2098 		fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : "  "), clrtab[i].nam);
2099 	fputc('\n', stderr);
2100 	exits("usage");
2101 }
2102 
main(int argc,char * argv[])2103 void main(int argc, char *argv[])
2104 {
2105 	int e;
2106 	char err[ERRMAX];
2107 
2108 	ARGBEGIN {
2109 	case 'm':
2110 		cantmv=0;
2111 		break;
2112 	case 'l':
2113 		logfil = fopen(ARGF(),"w");
2114 		break;
2115 	case 'p':
2116 		plotdots++;
2117 		break;
2118 	default:
2119 		usage();
2120 	} ARGEND;
2121 
2122 	if(initdraw(0, 0, "gview") < 0)
2123 		exits("initdraw");
2124 	einit(Emouse|Ekeyboard);
2125 
2126 	do {
2127 		e = doinput(*argv ? *argv : "-");
2128 		if (e < 0) {
2129 			rerrstr(err, sizeof err);
2130 			fprintf(stderr, "%s: cannot read %s: %s\n",
2131 				argv0, *argv, err);
2132 			exits("no valid input file");
2133 		} else if (e > 0) {
2134 			fprintf(stderr, "%s: %s:%d: bad data syntax\n",
2135 				argv0, (*argv ? *argv : "-"), e);
2136 			exits("bad syntax in input");
2137 		}
2138 	} while (*argv && *++argv);
2139 	init_disp();
2140 	init_clrtab();
2141 	set_default_clrs(&univ, 0);
2142 	adjust_border(display->defaultfont);
2143 	cur_sel.t = prev_sel.t = -1;
2144 	eresized(0);
2145 	for(;;)
2146 		doevent();
2147 }
2148