1 /* Copyright (C) 2000-2012 by George Williams */
2 /*
3  * Redistribution and use in source and binary forms, with or without
4  * modification, are permitted provided that the following conditions are met:
5 
6  * Redistributions of source code must retain the above copyright notice, this
7  * list of conditions and the following disclaimer.
8 
9  * Redistributions in binary form must reproduce the above copyright notice,
10  * this list of conditions and the following disclaimer in the documentation
11  * and/or other materials provided with the distribution.
12 
13  * The name of the author may not be used to endorse or promote products
14  * derived from this software without specific prior written permission.
15 
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include <fontforge-config.h>
29 
30 #include "cvruler.h"
31 #include "fontforgeui.h"
32 #include "splineutil.h"
33 #include "splineutil2.h"
34 #include "ustring.h"
35 
36 #include <math.h>
37 
38 int measuretoolshowhorizontolvertical = true;
39 Color measuretoollinecol = 0x000000;
40 Color measuretoolpointcol = 0xFF0000;
41 Color measuretoolpointsnappedcol = 0x00FF00;
42 Color measuretoolcanvasnumberscol = 0xFF0000;
43 Color measuretoolcanvasnumberssnappedcol = 0x00FF00;
44 Color measuretoolwindowforegroundcol = 0x000000;
45 Color measuretoolwindowbackgroundcol = 0xe0e0c0;
46 
47 BasePoint last_ruler_offset[2] = { {0,0}, {0,0} };
48 int infowindowdistance = 30;
49 
SlopeToBuf(char * buf,char * label,double dx,double dy)50 static void SlopeToBuf(char *buf,char *label,double dx, double dy) {
51     if ( dx==0 && dy==0 )
52 	sprintf( buf, _("%s No Slope"), label );
53     else if ( dx==0 )
54 	sprintf( buf, "%s dy/dx= ∞, %4g°", label, atan2(dy,dx)*180/FF_PI);
55     else
56 	sprintf( buf, "%s dy/dx= %4g, %4g°", label, dy/dx, atan2(dy,dx)*180/FF_PI);
57 }
58 
CurveToBuf(char * buf,CharView * cv,Spline * s,double t)59 static void CurveToBuf(char *buf,CharView *cv,Spline *s, double t) {
60     double kappa, emsize;
61 
62     kappa = SplineCurvature(s,t);
63     if ( kappa==CURVATURE_ERROR )
64 	strcpy(buf,_("No Curvature"));
65     else {
66 	emsize = cv->b.sc->parent->ascent + cv->b.sc->parent->descent;
67 	if ( kappa==0 )
68 	    sprintf(buf,_(" Curvature: %g"), kappa*emsize);
69 	else
70 	    sprintf(buf,_(" Curvature: %g  Radius: %g"), kappa*emsize, 1.0/kappa );
71     }
72 }
73 
RulerText(CharView * cv,unichar_t * ubuf,int line)74 static int RulerText(CharView *cv, unichar_t *ubuf, int line) {
75     char buf[80];
76     double len;
77     double dx, dy;
78     Spline *s;
79     double t;
80     BasePoint slope;
81     real xoff = cv->info.x-cv->p.cx, yoff = cv->info.y-cv->p.cy;
82 
83     buf[0] = '\0';
84     switch ( line ) {
85       case 0: {
86 	real len = sqrt(xoff*xoff+yoff*yoff);
87 
88 	if ( cv->autonomous_ruler_w ) {
89 	    xoff = last_ruler_offset[0].x;
90 	    yoff = last_ruler_offset[0].y;
91 	}
92 
93 	if ( !cv->autonomous_ruler_w && !cv->p.pressed )
94 	    /* Give current location accurately */
95 	    sprintf( buf, "%f,%f", (double) cv->info.x, (double) cv->info.y);
96 	else
97 	    sprintf( buf, "%f %.0f° (%f,%f)", (double) len,
98 		    atan2(yoff,xoff)*180/FF_PI,
99 		    (double) xoff,(double) yoff);
100       break; }
101       case 1:
102 	if ( cv->p.pressed ) {
103 	    if ( cv->p.spline!=NULL ||
104 		    (cv->p.sp!=NULL &&
105 		     ((cv->p.sp->next==NULL && cv->p.sp->prev!=NULL) ||
106 		      (cv->p.sp->prev==NULL && cv->p.sp->next!=NULL) ||
107 		      (cv->p.sp->next!=NULL && cv->p.sp->prev!=NULL &&
108 		        BpColinear(!cv->p.sp->noprevcp ? &cv->p.sp->prevcp : &cv->p.sp->prev->from->me,
109 				&cv->p.sp->me,
110 			        !cv->p.sp->nonextcp ? &cv->p.sp->nextcp : &cv->p.sp->next->to->me))) ) ) {
111 		Spline *spline;
112 		double t;
113 
114 		if ( cv->p.spline!=NULL ) {
115 		    spline = cv->p.spline;
116 		    t = cv->p.t;
117 		} else if ( cv->p.sp->next == NULL ) {
118 		    spline = cv->p.sp->prev;
119 		    t = 1;
120 		} else {
121 		    spline = cv->p.sp->next;
122 		    t = 0;
123 		}
124 		slope.x = (3*spline->splines[0].a*t + 2*spline->splines[0].b)*t + spline->splines[0].c;
125 		slope.y = (3*spline->splines[1].a*t + 2*spline->splines[1].b)*t + spline->splines[1].c;
126 		len = sqrt(slope.x*slope.x + slope.y*slope.y);
127 		if ( len!=0 ) {
128 		    slope.x /= len; slope.y /= len;
129 		    sprintf( buf, _("Normal Distance: %.2f Along Spline: %.2f"),
130 			    fabs(slope.y*xoff - slope.x*yoff),
131 			    slope.x*xoff + slope.y*yoff );
132 		}
133 	    }
134 	} else if ( cv->dv!=NULL || cv->b.gridfit!=NULL ) {
135 	    double scalex = (cv->b.sc->parent->ascent+cv->b.sc->parent->descent)/(rint(cv->ft_pointsizex*cv->ft_dpi/72.0));
136 	    double scaley = (cv->b.sc->parent->ascent+cv->b.sc->parent->descent)/(rint(cv->ft_pointsizey*cv->ft_dpi/72.0));
137 	    sprintf( buf, "%.2f,%.2f", (double) (cv->info.x/scalex), (double) (cv->info.y/scaley));
138 	} else if ( cv->p.spline!=NULL ) {
139 	    s = cv->p.spline;
140 	    t = cv->p.t;
141 	    sprintf( buf, _("Near (%f,%f)"),
142 		    (double) (((s->splines[0].a*t+s->splines[0].b)*t+s->splines[0].c)*t+s->splines[0].d),
143 		    (double) (((s->splines[1].a*t+s->splines[1].b)*t+s->splines[1].c)*t+s->splines[1].d) );
144 	} else if ( cv->p.sp!=NULL ) {
145 	    sprintf( buf, _("Near (%f,%f)"),(double) cv->p.sp->me.x,(double) cv->p.sp->me.y );
146 	} else
147 return( false );
148       break;
149       case 2:
150 	if ( cv->p.pressed ) {
151 	    if ( cv->p.sp!=NULL && cv->info_sp!=NULL &&
152 		    ((cv->p.sp->next!=NULL && cv->p.sp->next->to==cv->info_sp) ||
153 		     (cv->p.sp->prev!=NULL && cv->p.sp->prev->from==cv->info_sp)) ) {
154 		if ( cv->p.sp->next!=NULL && cv->p.sp->next->to==cv->info_sp )
155 		    len = SplineLength(cv->p.sp->next);
156 		else
157 		    len = SplineLength(cv->p.sp->prev);
158 	    } else if ( cv->p.spline == cv->info_spline && cv->info_spline!=NULL )
159 		len = SplineLengthRange(cv->info_spline,cv->p.t,cv->info_t);
160 	    else if ( cv->p.sp!=NULL && cv->info_spline!=NULL &&
161 		    cv->p.sp->next == cv->info_spline )
162 		len = SplineLengthRange(cv->info_spline,0,cv->info_t);
163 	    else if ( cv->p.sp!=NULL && cv->info_spline!=NULL &&
164 		    cv->p.sp->prev == cv->info_spline )
165 		len = SplineLengthRange(cv->info_spline,cv->info_t,1);
166 	    else if ( cv->info_sp!=NULL && cv->p.spline!=NULL &&
167 		    cv->info_sp->next == cv->p.spline )
168 		len = SplineLengthRange(cv->p.spline,0,cv->p.t);
169 	    else if ( cv->info_sp!=NULL && cv->p.spline!=NULL &&
170 		    cv->info_sp->prev == cv->p.spline )
171 		len = SplineLengthRange(cv->p.spline,cv->p.t,1);
172 	    else
173 return( false );
174 	    if ( len>1 )
175 		sprintf( buf, _("Spline Length=%.1f"), len);
176 	    else
177 		sprintf( buf, _("Spline Length=%g"), len);
178 	} else if ( cv->p.spline!=NULL ) {
179 	    s = cv->p.spline;
180 	    t = cv->p.t;
181 	    dx = (3*s->splines[0].a*t+2*s->splines[0].b)*t+s->splines[0].c;
182 	    dy = (3*s->splines[1].a*t+2*s->splines[1].b)*t+s->splines[1].c;
183 	    SlopeToBuf(buf,"",dx,dy);
184 	} else if ( cv->p.sp!=NULL ) {
185 	    if ( cv->p.sp->nonextcp )
186 		strcpy(buf,_("No Next Control Point"));
187 	    else
188 		sprintf(buf,_("Next CP: (%f,%f)"), (double) cv->p.sp->nextcp.x, (double) cv->p.sp->nextcp.y);
189 	} else
190 return( false );
191       break;
192       case 3:
193 	if ( cv->p.pressed )
194 return( false );
195 	else if ( cv->p.spline!=NULL ) {
196 	    CurveToBuf(buf,cv,cv->p.spline,cv->p.t);
197 	} else if ( cv->p.sp!=NULL && cv->p.sp->next!=NULL ) {
198 	    s = cv->p.sp->next;
199 	    dx = s->splines[0].c;
200 	    dy = s->splines[1].c;
201 	    SlopeToBuf(buf,_(" Next"),dx,dy);
202 	} else if ( cv->p.sp!=NULL ) {
203 	    if ( cv->p.sp->noprevcp )
204 		strcpy(buf,_("No Previous Control Point"));
205 	    else
206 		sprintf(buf,_("Prev CP: (%f,%f)"), (double) cv->p.sp->prevcp.x, (double) cv->p.sp->prevcp.y);
207 	} else
208 return( false );
209       break;
210       case 4:
211 	if ( cv->p.spline!=NULL )
212 return( false );
213 	else if ( cv->p.sp->next!=NULL ) {
214 	    CurveToBuf(buf,cv,cv->p.sp->next,0);
215 	} else if ( cv->p.sp->prev!=NULL ) {
216 	    s = cv->p.sp->prev;
217 	    dx = (3*s->splines[0].a*1+2*s->splines[0].b)*1+s->splines[0].c;
218 	    dy = (3*s->splines[1].a*1+2*s->splines[1].b)*1+s->splines[1].c;
219 	    SlopeToBuf(buf,_(" Prev"),dx,dy);
220 	} else
221 return( false );
222       break;
223       case 5:
224 	if ( cv->p.sp->next!=NULL ) {
225 	    if ( cv->p.sp->noprevcp )
226 		strcpy(buf,_("No Previous Control Point"));
227 	    else
228 		sprintf(buf,_("Prev CP: (%f,%f)"), (double) cv->p.sp->prevcp.x, (double) cv->p.sp->prevcp.y);
229 	} else {
230 	    CurveToBuf(buf,cv,cv->p.sp->prev,1);
231 	}
232       break;
233       case 6:
234 	if ( cv->p.sp->next!=NULL && cv->p.sp->prev!=NULL ) {
235 	    s = cv->p.sp->prev;
236 	    dx = (3*s->splines[0].a*1+2*s->splines[0].b)*1+s->splines[0].c;
237 	    dy = (3*s->splines[1].a*1+2*s->splines[1].b)*1+s->splines[1].c;
238 	    SlopeToBuf(buf,_(" Prev"),dx,dy);
239 	} else
240 return( false );
241       break;
242       case 7:
243 	if ( cv->p.sp->next!=NULL && cv->p.sp->prev!=NULL ) {
244 	    CurveToBuf(buf,cv,cv->p.sp->prev,1);
245 	} else
246 return( false );
247       break;
248       default:
249 return( false );
250     }
251     utf82u_strcpy(ubuf,buf);
252 return( true );
253 }
254 
RulerTextIntersection(CharView * cv,unichar_t * ubuf,int i)255 static int RulerTextIntersection(CharView *cv, unichar_t *ubuf, int i) {
256     char buf[80];
257 
258     if ( i==0 && cv->num_ruler_intersections>4 ) {
259 	real xoff = cv->ruler_intersections[cv->num_ruler_intersections-2].x - cv->ruler_intersections[1].x;
260 	real yoff = cv->ruler_intersections[cv->num_ruler_intersections-2].y - cv->ruler_intersections[1].y;
261 	real len = sqrt(xoff*xoff+yoff*yoff);
262 	snprintf(buf,sizeof buf, _("First Edge to Last Edge: %g x %g length %f"),fabs(xoff),fabs(yoff),len);
263 	utf82u_strcpy(ubuf,buf);
264 return( 1 );
265     } else if ( cv->num_ruler_intersections>4 )
266 	i -= 1;
267 
268     if ( i>=cv->num_ruler_intersections )
269 return( 0 );
270 
271     if ( i==0 ) {
272 	snprintf(buf,sizeof buf,"[%d] (%g,%g)",i,cv->ruler_intersections[i].x,cv->ruler_intersections[i].y);
273 	if ( cv->p.sp ) {
274 	    strcat(buf, _(" snapped"));
275 	    cv->start_intersection_snapped = 1;
276 	} else {
277 	    cv->start_intersection_snapped = 0;
278 	}
279     } else {
280 	real xoff = cv->ruler_intersections[i].x - cv->ruler_intersections[i-1].x;
281 	real yoff = cv->ruler_intersections[i].y - cv->ruler_intersections[i-1].y;
282 	real len = sqrt(xoff*xoff+yoff*yoff);
283 	snprintf(buf,sizeof buf, _("[%d] (%g,%g) %g x %g length %g"),i,cv->ruler_intersections[i].x,cv->ruler_intersections[i].y,fabs(xoff),fabs(yoff),len);
284 	if ( i==(cv->num_ruler_intersections-1) ) {
285 	    if ( cv->info_sp ) {
286 		strcat(buf, _(" snapped"));
287 		cv->end_intersection_snapped = 1;
288 	    } else {
289 		cv->end_intersection_snapped = 0;
290 	    }
291 	}
292     }
293 
294     utf82u_strcpy(ubuf,buf);
295 return( 1 );
296 }
297 
ruler_e_h(GWindow gw,GEvent * event)298 static int ruler_e_h(GWindow gw, GEvent *event) {
299     CharView *cv = (CharView *) GDrawGetUserData(gw);
300     unichar_t ubuf[80];
301     int line;
302     int i;
303 
304     switch ( event->type ) {
305       case et_expose:
306 	GDrawSetFont(gw,cv->rfont);
307 	for ( line=0; RulerText(cv,ubuf,line); ++line )
308 	    GDrawDrawText(gw,2,line*cv->rfh+cv->ras+1,ubuf,-1,measuretoolwindowforegroundcol);
309 	if ( cv->p.pressed ) for ( i=0; RulerTextIntersection(cv,ubuf,i); ++i )
310 	    GDrawDrawText(gw,2,(line+i)*cv->rfh+cv->ras+1,ubuf,-1,measuretoolwindowforegroundcol);
311       break;
312       case et_mousedown:
313 	cv->autonomous_ruler_w = false;
314 	GDrawDestroyWindow(gw);
315 	cv->ruler_w = NULL;
316       break;
317     }
318 return( true );
319 }
320 
ruler_linger_e_h(GWindow gw,GEvent * event)321 static int ruler_linger_e_h(GWindow gw, GEvent *event) {
322     CharView *cv = (CharView *) GDrawGetUserData(gw);
323     int i;
324 
325     switch ( event->type ) {
326       case et_expose:
327 	GDrawSetFont(gw,cv->rfont);
328 	for ( i=0; i < cv->ruler_linger_num_lines ; ++i )
329 	    GDrawDrawText(gw,2,i*cv->rfh+cv->ras+1,cv->ruler_linger_lines[i],-1,measuretoolwindowforegroundcol);
330       break;
331       case et_mousedown:
332 	// TBD
333 	// cv->autonomous_ruler_w = false;
334 	// GDrawDestroyWindow(gw);
335 	// cv->ruler_linger_w = NULL;
336       break;
337     }
338 return( true );
339 }
340 
341 static GFont *rvfont=NULL;
342 
343 /*
344  * Comparison function for use with qsort.
345  */
BasePointCompare(const void * _l,const void * _r)346 static int BasePointCompare(const void *_l, const void *_r) {
347 	const BasePoint *l = _l, *r = _r;
348 	if ( l->x>r->x)
349 return( 1 );
350 	if ( l->x<r->x)
351 return( -1 );
352 	if ( l->y>r->y)
353 return( 1 );
354 	if ( l->y<r->y)
355 return( -1 );
356 return( 0 );
357 }
358 
ReverseBasePointCompare(const void * l,const void * r)359 static int ReverseBasePointCompare(const void *l, const void *r) {
360 return( -BasePointCompare(l,r) );
361 }
362 
363 /*
364  * Fill buffer with intersects on a line (from,to).
365  * return number found, buf fill the buffer only up to a max_intersections.
366  *
367  * The points from and to are also put in the buffer.
368  *
369  * Copied somewhat from CVMouseUpKnife(), perhaps they should be consolidated
370  */
GetIntersections(CharView * cv,BasePoint from,BasePoint to,BasePoint * all_intersections,int max_intersections)371 static int GetIntersections(CharView *cv,BasePoint from,BasePoint to,BasePoint *all_intersections,int max_intersections) {
372     SplineSet *spl;
373     Spline *s, *nexts;
374     Spline dummy;
375     SplinePoint dummyfrom, dummyto;
376     BasePoint inters[9];	/* These bounds are hard coded in the SplinesIntersect function */
377     extended t1s[10], t2s[10];
378     int i;
379     int total_intersections = 0;
380 
381     memset(&dummy,0,sizeof(dummy));
382     memset(&dummyfrom,0,sizeof(dummyfrom));
383     memset(&dummyto,0,sizeof(dummyto));
384     dummyfrom.me.x = from.x; dummyfrom.me.y = from.y;
385     dummyto.me.x = to.x; dummyto.me.y = to.y;
386     dummyfrom.nextcp = dummyfrom.prevcp = dummyfrom.me;
387     dummyto.nextcp = dummyto.prevcp = dummyto.me;
388     dummyfrom.nonextcp = dummyfrom.noprevcp = dummyto.nonextcp = dummyto.noprevcp = true;
389     dummy.splines[0].d = from.x; dummy.splines[0].c = to.x-from.x;
390     dummy.splines[1].d = from.y; dummy.splines[1].c = to.y-from.y;
391     dummy.from = &dummyfrom; dummy.to = &dummyto;
392     dummy.islinear = dummy.knownlinear = true;
393     dummyfrom.next = dummyto.prev = &dummy;
394 
395     all_intersections[total_intersections++] = from;
396 
397     for ( spl = cv->b.layerheads[cv->b.drawmode]->splines; spl!=NULL; spl = spl->next ) {
398 	for ( s = spl->first->next; s!=NULL ; ) {
399 	    nexts = NULL;
400 	    if ( s->to!=spl->first )
401 		nexts = s->to->next;
402 
403 	    if ( SplinesIntersect(s,&dummy,inters,t1s,t2s)>0 ) {
404 		/* TBD, why is 4 used here (copied from CVMouseUpKnife()) */
405 		for ( i=0; i<4 && t1s[i]!=-1; ++i ) {
406 		    if ( (total_intersections+1)<max_intersections )
407 			all_intersections[total_intersections] = inters[i];
408 		    total_intersections++;
409 		}
410 	    }
411 	    s = nexts;
412 	}
413     }
414 
415     if ( total_intersections<max_intersections )
416 	all_intersections[total_intersections++] = to;
417 
418     qsort(all_intersections,
419 	total_intersections > max_intersections ? max_intersections : total_intersections,
420 	sizeof(all_intersections[0]),
421 	BasePointCompare(&from,&to)<=0 ? BasePointCompare : ReverseBasePointCompare );
422 
423     /*
424      * Filter out intersectsions that are too close.
425      * This is for snapped points, but we get more than one extra per snap,
426      * so do them all for now.
427      */
428     for ( i = 1 ; i<total_intersections && i<max_intersections ; ) {
429 	if ( (0.00001 > fabs(all_intersections[i].x-all_intersections[i-1].x)) &&
430 	     (0.00001 > fabs(all_intersections[i].y-all_intersections[i-1].y)) ) {
431 	    int j;
432 
433 	    for( j = i+1 ; j<total_intersections &&  j<max_intersections ; j++ )
434 		all_intersections[j-1] = all_intersections[j];
435 	    if ( total_intersections < max_intersections )
436 		total_intersections--;
437 	} else {
438 	    i++;
439 	}
440     }
441 
442 return( total_intersections );	/* note that it could be greater than max */
443 }
444 
RulerPlace(CharView * cv,GEvent * event)445 static void RulerPlace(CharView *cv, GEvent *event) {
446     unichar_t ubuf[80];
447     int width, x, y;
448     GRect size;
449     GPoint pt;
450     int i,h,w;
451     GWindowAttrs wattrs;
452     GRect pos;
453     FontRequest rq;
454     int as, ds, ld;
455 
456     if ( cv->ruler_w==NULL ) {
457 	memset(&wattrs,0,sizeof(wattrs));
458 	wattrs.mask = wam_events|wam_cursor|wam_positioned|wam_nodecor|wam_backcol|wam_bordwidth;
459 	wattrs.event_masks = (1<<et_expose)|(1<<et_resize)|(1<<et_mousedown);
460 	wattrs.cursor = ct_mypointer;
461 	wattrs.background_color = measuretoolwindowbackgroundcol;
462 	wattrs.nodecoration = 1;
463 	wattrs.border_width = 1;
464 	pos.x = pos.y = 0; pos.width=pos.height = 20;
465 	cv->ruler_w = GWidgetCreateTopWindow(NULL,&pos,ruler_e_h,cv,&wattrs);
466 
467 	if ( rvfont==NULL ) {
468 	    memset(&rq,0,sizeof(rq));
469 	    rq.utf8_family_name = FIXED_UI_FAMILIES;
470 	    rq.point_size = -12;
471 	    rq.weight = 400;
472 	    rvfont = GDrawInstanciateFont(cv->ruler_w,&rq);
473 	    rvfont = GResourceFindFont("CharView.Measure.Font",rvfont);
474 	}
475 	cv->rfont = rvfont;
476 	GDrawWindowFontMetrics(cv->ruler_w,cv->rfont,&as,&ds,&ld);
477 	cv->rfh = as+ds; cv->ras = as;
478     } else
479 	GDrawRaise(cv->ruler_w);
480 
481     if ( cv->p.pressed ) {
482 	BasePoint from;
483 
484 	from.x = cv->p.cx;
485 	from.y = cv->p.cy;
486 
487 	if ( !cv->ruler_intersections ) {
488 	    cv->allocated_ruler_intersections = 32;
489 	    cv->ruler_intersections = malloc(cv->allocated_ruler_intersections * sizeof(cv->ruler_intersections[0]));
490 	}
491 	for(;;) {
492 	    cv->num_ruler_intersections = GetIntersections(cv,from,cv->info,cv->ruler_intersections,cv->allocated_ruler_intersections);
493 	    if ( cv->num_ruler_intersections>cv->allocated_ruler_intersections ) {
494 		cv->allocated_ruler_intersections = cv->num_ruler_intersections * 2;
495 		cv->ruler_intersections = realloc(cv->ruler_intersections,cv->allocated_ruler_intersections * sizeof(cv->ruler_intersections[0]));
496 	    } else
497 		break;
498 	}
499     }
500 
501     GDrawSetFont(cv->ruler_w,cv->rfont);
502     width = h = 0;
503     for ( i=0; RulerText(cv,ubuf,i); ++i ) {
504 	w = GDrawGetTextWidth(cv->ruler_w,ubuf,-1);
505 	if ( w>width ) width = w;
506 	h += cv->rfh;
507     }
508     if ( cv->p.pressed ) for ( i=0; RulerTextIntersection(cv,ubuf,i); ++i ) {
509 	w = GDrawGetTextWidth(cv->ruler_w,ubuf,-1);
510 	if ( w>width ) width = w;
511 	h += cv->rfh;
512     }
513 
514     GDrawGetSize(GDrawGetRoot(NULL),&size);
515     pt.x = event->u.mouse.x; pt.y = event->u.mouse.y;
516     GDrawTranslateCoordinates(cv->v,GDrawGetRoot(NULL),&pt);
517     x = pt.x + infowindowdistance;
518     if ( x+width > size.width )
519 	x = pt.x - width-infowindowdistance;
520     y = pt.y -cv->ras-2;
521     if ( y+h > size.height )
522 	y = pt.y - h - cv->ras -10;
523     GDrawMoveResize(cv->ruler_w,x,y,width+4,h+4);
524 }
525 
RulerLingerPlace(CharView * cv,GEvent * event)526 static void RulerLingerPlace(CharView *cv, GEvent *event) {
527     int width, x, y;
528     GRect size;
529     GPoint pt;
530     int i,h,w;
531     GWindowAttrs wattrs;
532     GRect pos;
533     FontRequest rq;
534     int as, ds, ld;
535     int line;
536     int old_pressed;
537 
538     if ( cv->ruler_linger_w==NULL ) {
539 	memset(&wattrs,0,sizeof(wattrs));
540 	wattrs.mask = wam_events|wam_cursor|wam_positioned|wam_nodecor|wam_backcol|wam_bordwidth;
541 	wattrs.event_masks = (1<<et_expose)|(1<<et_resize)|(1<<et_mousedown);
542 	wattrs.cursor = ct_mypointer;
543 	wattrs.background_color = measuretoolwindowbackgroundcol;
544 	wattrs.nodecoration = 1;
545 	wattrs.border_width = 1;
546 	pos.x = pos.y = 0; pos.width=pos.height = 20;
547 	cv->ruler_linger_w = GWidgetCreateTopWindow(NULL,&pos,ruler_linger_e_h,cv,&wattrs);
548 
549 	if ( rvfont==NULL ) {
550 	    memset(&rq,0,sizeof(rq));
551 	    rq.utf8_family_name = FIXED_UI_FAMILIES;
552 	    rq.point_size = -12;
553 	    rq.weight = 400;
554 	    rvfont = GDrawInstanciateFont(cv->ruler_w,&rq);
555 	    rvfont = GResourceFindFont("CharView.Measure.Font",rvfont);
556 	}
557 	cv->rfont = rvfont;
558 	GDrawWindowFontMetrics(cv->ruler_linger_w,cv->rfont,&as,&ds,&ld);
559 	cv->rfh = as+ds; cv->ras = as;
560     } else
561 	GDrawRaise(cv->ruler_linger_w);
562 
563     GDrawSetFont(cv->ruler_linger_w,cv->rfont);
564     width = h = 0;
565     line = 0;
566     old_pressed = cv->p.pressed;
567     cv->p.pressed = true;
568 
569     for ( i=0; line<sizeof(cv->ruler_linger_lines)/sizeof(cv->ruler_linger_lines[0]) && RulerText(cv,cv->ruler_linger_lines[line],i) ; ++i,++line ) {
570 	w = GDrawGetTextWidth(cv->ruler_linger_w,cv->ruler_linger_lines[line],-1);
571 	if ( w>width ) width = w;
572 	h += cv->rfh;
573     }
574     cv->p.pressed = old_pressed;
575     for ( i=0; line<sizeof(cv->ruler_linger_lines)/sizeof(cv->ruler_linger_lines[0]) && RulerTextIntersection(cv,cv->ruler_linger_lines[line],i); ++i,++line ) {
576 	w = GDrawGetTextWidth(cv->ruler_linger_w,cv->ruler_linger_lines[line],-1);
577 	if ( w>width ) width = w;
578 	h += cv->rfh;
579     }
580     cv->ruler_linger_num_lines = line;
581 
582     GDrawGetSize(GDrawGetRoot(NULL),&size);
583     pt.x = event->u.mouse.x; pt.y = event->u.mouse.y;
584     GDrawTranslateCoordinates(cv->v,GDrawGetRoot(NULL),&pt);
585     x = pt.x + infowindowdistance;
586     if ( x+width > size.width )
587 	x = pt.x - width-infowindowdistance;
588     y = pt.y -cv->ras-2;
589     if ( y+h > size.height )
590 	y = pt.y - h - cv->ras -10;
591     GDrawMoveResize(cv->ruler_linger_w,x,y,width+4,h+4);
592     GDrawSetVisible(cv->ruler_linger_w,true);
593 }
594 
RulerLingerMove(CharView * cv)595 static void RulerLingerMove(CharView *cv) {
596     CharViewTab* tab = CVGetActiveTab(cv);
597     if ( cv->ruler_linger_w ) {
598 	int x, y;
599 	GRect size;
600 	GRect rsize;
601 	GRect csize;
602 	GPoint pt;
603 
604 	GDrawGetSize(GDrawGetRoot(NULL),&size);
605 	GDrawGetSize(cv->ruler_linger_w,&rsize);
606 	GDrawGetSize(cv->gw,&csize);
607 
608 	pt.x = tab->xoff + rint(cv->ruler_intersections[cv->num_ruler_intersections-1].x*tab->scale);
609 	pt.y = -tab->yoff + cv->height - rint(cv->ruler_intersections[cv->num_ruler_intersections-1].y*tab->scale);
610 	GDrawTranslateCoordinates(cv->v,GDrawGetRoot(NULL),&pt);
611 	x = pt.x + infowindowdistance;
612 	if ( x+rsize.width>size.width )
613 	    x = pt.x - rsize.width-infowindowdistance;
614 	y = pt.y -cv->ras-2;
615 	if ( y+rsize.height>size.height )
616 	    y = pt.y - rsize.height - cv->ras -10;
617 
618 	if ( x>=csize.x && x<=(csize.x+csize.width) && y>=csize.y && y<=(csize.y+csize.height) ) {
619 	    GDrawMove(cv->ruler_linger_w,x,y);
620 	    GDrawSetVisible(cv->ruler_linger_w,true);
621 	} else {
622 	    GDrawSetVisible(cv->ruler_linger_w,false);
623 	}
624     }
625 }
626 
CVMouseDownRuler(CharView * cv,GEvent * event)627 void CVMouseDownRuler(CharView *cv, GEvent *event) {
628 
629     cv->autonomous_ruler_w = false;
630 
631     RulerPlace(cv,event);
632     cv->p.rubberlining = true;
633     GDrawSetVisible(cv->ruler_w,true);
634     if ( cv->ruler_linger_w ) {
635 	GDrawDestroyWindow(cv->ruler_linger_w);
636 	cv->ruler_linger_w = NULL;
637     }
638 }
639 
CVMouseMoveRuler(CharView * cv,GEvent * event)640 void CVMouseMoveRuler(CharView *cv, GEvent *event) {
641     if ( cv->autonomous_ruler_w )
642 return;
643 
644     if ( !cv->p.pressed && (event->u.mouse.state&ksm_meta) ) {
645 	if ( cv->ruler_w!=NULL && GDrawIsVisible(cv->ruler_w)) {
646 	    GDrawDestroyWindow(cv->ruler_w);
647 	    cv->ruler_w = NULL;
648 	}
649 return;
650     }
651     if ( !cv->p.pressed )
652 	CVMouseAtSpline(cv,event);
653     RulerPlace(cv,event);
654     if ( !cv->p.pressed )
655 	GDrawSetVisible(cv->ruler_w,true);
656     GDrawSync(NULL);
657 // The following code may be unnecessary, and it can cause an infinite stack loop.
658 // One would hope that the event queue can take care of these things when we return to it.
659 // We'll find out.
660 //#if 0
661 //    GDrawProcessPendingEvents(NULL);		/* The resize needs to happen before the expose */
662 //    if ( !cv->p.pressed && (event->u.mouse.state&ksm_meta) ) /* but a mouse up might sneak in... */
663 //return;
664     GDrawRequestExpose(cv->ruler_w,NULL,false);
665     if (cv->p.pressed)
666         GDrawRequestExpose(cv->v,NULL,false);
667 //#endif // 0
668 }
669 
CVMouseUpRuler(CharView * cv,GEvent * event)670 void CVMouseUpRuler(CharView *cv, GEvent *event) {
671     if ( cv->ruler_w!=NULL ) {
672 	GRect size;
673 
674 	last_ruler_offset[1] = last_ruler_offset[0];
675 	last_ruler_offset[0].x = cv->info.x-cv->p.cx;
676 	last_ruler_offset[0].y = cv->info.y-cv->p.cy;
677 
678 	/* if we have gone out of bounds abandon the window */
679 	GDrawGetSize(cv->v,&size);
680 	if ( event->u.mouse.x<0 || event->u.mouse.y<0 || event->u.mouse.x>=size.width || event->u.mouse.y>=size.height ) {
681 	    GDrawDestroyWindow(cv->ruler_w);
682 	    cv->ruler_w = NULL;
683 return;
684 	}
685 
686 	if ( !(event->u.mouse.state & ksm_meta) ) {
687 	    /*cv->autonomous_ruler_w = true;*/
688 
689 	    if ( cv->ruler_linger_w ) {
690 		GDrawDestroyWindow(cv->ruler_linger_w);
691 		cv->ruler_linger_w = NULL;
692 	    }
693 	    if ( cv->num_ruler_intersections>1 ) {
694 		RulerLingerPlace(cv,event);
695 	    }
696 return;
697 	}
698 
699 	GDrawDestroyWindow(cv->ruler_w);
700 	cv->ruler_w = NULL;
701     }
702 }
703 
704 /* ************************************************************************** */
705 
PtInfoText(CharView * cv,int lineno,int active,char * buffer,int blen)706 static char *PtInfoText(CharView *cv, int lineno, int active, char *buffer, int blen) {
707     BasePoint *cp;
708     double t;
709     Spline *s;
710     SplinePoint *sp = cv->p.sp;
711     extern char *coord_sep;
712     double dx, dy, kappa, kappa2;
713     int emsize;
714 
715     if ( !cv->p.prevcp && !cv->p.nextcp ) {
716 	sp = cv->active_sp;
717 	if ( sp==NULL )
718 return( NULL );
719     }
720 
721     if ( active==-1 ) {
722 	if ( lineno>0 )
723 return( NULL );
724 	if ( sp->next==NULL || sp->prev==NULL )
725 return( NULL );
726 	kappa = SplineCurvature(sp->next,0);
727 	kappa2 = SplineCurvature(sp->prev,1);
728 	emsize = cv->b.sc->parent->ascent + cv->b.sc->parent->descent;
729 	if ( kappa == CURVATURE_ERROR || kappa2 == CURVATURE_ERROR )
730 	    strncpy(buffer,_("No curvature info"), blen);
731 	else
732 	    snprintf( buffer, blen, U_("∆Curvature: %g"), (kappa-kappa2)*emsize );
733 return( buffer );
734     }
735 
736     if ( (!cv->p.prevcp && active ) || (!cv->p.nextcp && !active)) {
737 	cp = &sp->nextcp;
738 	t = 0;
739 	s = sp->next;
740     } else {
741 	cp = &sp->prevcp;
742 	t = 1;
743 	s = sp->prev;
744     }
745 
746     switch( lineno ) {
747       case 0:
748 	if ( t==0 )
749 	    strncpy( buffer, _(" Next CP"), blen);
750 	else
751 	    strncpy( buffer, _(" Prev CP"), blen);
752       break;
753       case 1:
754 	snprintf( buffer, blen, "(%g%s%g)", (double) cp->x, coord_sep, (double) cp->y);
755       break;
756       case 2:
757 	snprintf( buffer, blen, "∆ (%g%s%g)", (double) (cp->x-sp->me.x), coord_sep, (double) (cp->y-sp->me.y));
758       break;
759       case 3:
760 	dx = cp->x - sp->me.x; dy = cp->y - sp->me.y;
761 	if ( dx==0 && dy==0 )
762 	    snprintf( buffer, blen, "%s", _("No Slope") );
763 	else if ( dx==0 )
764 	    snprintf( buffer, blen, "∆y/∆x= ∞" );
765 	else
766 	    snprintf( buffer, blen, "∆y/∆x= %g", dy/dx );
767       break;
768       case 4:
769 	dx = cp->x - sp->me.x; dy = cp->y - sp->me.y;
770 	snprintf( buffer, blen, "∠ %g°", atan2(dy,dx)*180/FF_PI );
771       break;
772       case 5:
773 	if ( s==NULL )
774 return( NULL );
775 	kappa = SplineCurvature(s,t);
776 	if ( kappa==CURVATURE_ERROR )
777 return( NULL );
778 	emsize = cv->b.sc->parent->ascent + cv->b.sc->parent->descent;
779 	/* If we normalize by the em-size, the curvature is often more */
780 	/*  readable */
781 	snprintf( buffer, blen, _("Curvature: %g"), kappa*emsize);
782       break;
783       default:
784 return( NULL );
785     }
786 return( buffer );
787 }
788 
cpinfo_e_h(GWindow gw,GEvent * event)789 static int cpinfo_e_h(GWindow gw, GEvent *event) {
790     CharView *cv = (CharView *) GDrawGetUserData(gw);
791     char buf[100];
792     int line, which, y;
793 
794     switch ( event->type ) {
795       case et_expose:
796 	y = cv->ras+1;
797 	GDrawSetFont(gw,cv->rfont);
798 	for ( which = 1; which>=0; --which ) {
799 	    for ( line=0; PtInfoText(cv,line,which,buf,sizeof(buf))!=NULL; ++line ) {
800 		GDrawDrawText8(gw,2,y,buf,-1,0x000000);
801 		y += cv->rfh+1;
802 	    }
803 	    GDrawDrawLine(gw,0,y+2-cv->ras,2000,y+2-cv->ras,0x000000);
804 	    y += 4;
805 	}
806 	if ( PtInfoText(cv,0,-1,buf,sizeof(buf))!=NULL )
807 	    GDrawDrawText8(gw,2,y,buf,-1,0x000000);
808       break;
809     }
810 return( true );
811 }
812 
CpInfoPlace(CharView * cv,GEvent * event)813 static void CpInfoPlace(CharView *cv, GEvent *event) {
814     CharViewTab* tab = CVGetActiveTab(cv);
815     char buf[100];
816     int line, which;
817     int width, x, y;
818     GRect size;
819     GPoint pt, pt2;
820     int h,w;
821     GWindowAttrs wattrs;
822     GRect pos;
823     FontRequest rq;
824     int as, ds, ld;
825     SplinePoint *sp;
826 
827     if ( cv->ruler_w==NULL ) {
828 	memset(&wattrs,0,sizeof(wattrs));
829 	wattrs.mask = wam_events|wam_cursor|wam_positioned|wam_nodecor|wam_backcol|wam_bordwidth;
830 	wattrs.event_masks = (1<<et_expose)|(1<<et_resize)|(1<<et_mousedown);
831 	wattrs.cursor = ct_mypointer;
832 	wattrs.background_color = 0xe0e0c0;
833 	wattrs.nodecoration = 1;
834 	wattrs.border_width = 1;
835 	pos.x = pos.y = 0; pos.width=pos.height = 20;
836 	cv->ruler_w = GWidgetCreateTopWindow(NULL,&pos,cpinfo_e_h,cv,&wattrs);
837 
838 	if ( rvfont==NULL ) {
839 	    memset(&rq,0,sizeof(rq));
840 	    rq.utf8_family_name = FIXED_UI_FAMILIES;
841 	    rq.point_size = -12;
842 	    rq.weight = 400;
843 	    rvfont = GDrawInstanciateFont(cv->ruler_w,&rq);
844 	    rvfont = GResourceFindFont("CharView.Measure.Font",rvfont);
845 	}
846 	cv->rfont = rvfont;
847 	GDrawWindowFontMetrics(cv->ruler_w,cv->rfont,&as,&ds,&ld);
848 	cv->rfh = as+ds; cv->ras = as;
849     } else
850 	GDrawRaise(cv->ruler_w);
851 
852     GDrawSetFont(cv->ruler_w,cv->rfont);
853     h = 0; width = 0;
854     for ( which = 0; which<2; ++which ) {
855 	for ( line=0; PtInfoText(cv,line,which,buf,sizeof(buf))!=NULL; ++line ) {
856 	    w = GDrawGetText8Width(cv->ruler_w,buf,-1);
857 	    if ( w>width ) width = w;
858 	    h += cv->rfh+1;
859 	}
860 	h += 4;
861     }
862     if ( PtInfoText(cv,0,-1,buf,sizeof(buf))!=NULL ) {
863 	w = GDrawGetText8Width(cv->ruler_w,buf,-1);
864 	if ( w>width ) width = w;
865 	h += cv->rfh+1;
866     }
867 
868     GDrawGetSize(GDrawGetRoot(NULL),&size);
869     pt.x = event->u.mouse.x; pt.y = event->u.mouse.y;	/* Address of cp */
870     GDrawTranslateCoordinates(cv->v,GDrawGetRoot(NULL),&pt);
871 
872     sp = cv->p.sp;
873     if ( !cv->p.prevcp && !cv->p.nextcp )
874 	sp = cv->active_sp;
875     if ( sp!=NULL ) {
876 	x =  tab->xoff + rint(sp->me.x*tab->scale);
877 	y = -tab->yoff + cv->height - rint(sp->me.y*tab->scale);
878 	if ( x>=0 && y>=0 && x<cv->width && y<cv->height ) {
879 	    pt2.x = x; pt2.y = y;
880 	    GDrawTranslateCoordinates(cv->v,GDrawGetRoot(NULL),&pt2);
881 	} else
882 	    sp = NULL;
883     }
884 
885     x = pt.x + infowindowdistance;
886     y = pt.y - cv->ras-2;
887     if ( sp!=NULL && x<=pt2.x-4 && x+width>=pt2.x+4 && y<=pt2.y-4 && y+h>=pt2.y+4 )
888 	x = pt2.x + 4;
889     if ( x+width > size.width ) {
890 	x = pt.x - width-30;
891 	if ( sp!=NULL && x<=pt2.x-4 && x+width>=pt2.x+4 && y<=pt2.y-4 && y+h>=pt2.y+4 )
892 	    x = pt2.x - width - 4;
893 	if ( x<0 ) {
894 	    x = pt.x + 10;
895 	    y = pt.y - h - infowindowdistance;
896 	    if ( sp!=NULL && x<=pt2.x-4 && x+width>=pt2.x+4 && y<=pt2.y-4 && y+h>=pt2.y+4 )
897 		y = pt2.y - h - 4;
898 	    if ( y<0 )
899 		y = pt.y+infowindowdistance;	/* If this doesn't work we have nowhere else to */
900 				/* try so don't check */
901 	}
902     }
903     if ( y+h > size.height )
904 	y = pt.y - h - cv->ras - 10;
905     GDrawMoveResize(cv->ruler_w,x,y,width+4,h+4);
906 }
907 
CPStartInfo(CharView * cv,GEvent * event)908 void CPStartInfo(CharView *cv, GEvent *event) {
909 //    printf("CPStartInfo() showcp:%d pressed:%d rw:%p\n", cv->showcpinfo, cv->p.pressed, cv->ruler_w );
910 
911     if ( !cv->showcpinfo )
912 return;
913     cv->autonomous_ruler_w = false;
914 
915     CpInfoPlace(cv,event);
916     GDrawSetVisible(cv->ruler_w,true);
917 }
918 
CPUpdateInfo(CharView * cv,GEvent * event)919 void CPUpdateInfo(CharView *cv, GEvent *event) {
920 
921 //    printf("CPUpdateInfo() showcp:%d pressed:%d rw:%p\n", cv->showcpinfo, cv->p.pressed, cv->ruler_w );
922 
923     if ( !cv->showcpinfo )
924 return;
925     if ( !cv->p.pressed ) {
926 	if ( cv->ruler_w!=NULL && GDrawIsVisible(cv->ruler_w)) {
927 	    GDrawDestroyWindow(cv->ruler_w);
928 	    cv->ruler_w = NULL;
929 	}
930 return;
931     }
932     if ( cv->ruler_w==NULL )
933 	CPStartInfo(cv,event);
934     else {
935 	CpInfoPlace(cv,event);
936 	GDrawSync(NULL);
937 	GDrawProcessPendingEvents(NULL);		/* The resize needs to happen before the expose */
938 	if ( !cv->p.pressed  ) /* but a mouse up might sneak in... */
939 return;
940 	GDrawRequestExpose(cv->ruler_w,NULL,false);
941     }
942 }
943 
CPEndInfo(CharView * cv)944 void CPEndInfo(CharView *cv) {
945     if ( cv->ruler_w!=NULL ) {
946 	if ( !cv->p.pressed ) {
947 	    GDrawDestroyWindow(cv->ruler_w);
948 	    cv->ruler_w = NULL;
949 	}
950     }
951     /* TBD, wrong time to kill? */
952     if ( cv->ruler_linger_w!=NULL && cv->b1_tool!=cvt_ruler && cv->b1_tool_old!=cvt_ruler ) {
953 	GDrawDestroyWindow(cv->ruler_linger_w);
954 	cv->ruler_linger_w = NULL;
955     }
956 }
957 
CVRulerExpose(GWindow pixmap,CharView * cv)958 void CVRulerExpose(GWindow pixmap,CharView *cv) {
959     CharViewTab* tab = CVGetActiveTab(cv);
960 
961     if ( cv->b1_tool!=cvt_ruler && cv->b1_tool_old!=cvt_ruler ) {
962 	cv->num_ruler_intersections = 0;
963 return;
964     }
965 
966     if ( cv->num_ruler_intersections >= 2 ) {
967 	int x =  tab->xoff + rint(cv->ruler_intersections[0].x*tab->scale);
968 	int y = -tab->yoff + cv->height - rint(cv->ruler_intersections[0].y*tab->scale);
969 	int xend =  tab->xoff + rint(cv->ruler_intersections[cv->num_ruler_intersections-1].x*tab->scale);
970 	int yend = -tab->yoff + cv->height - rint(cv->ruler_intersections[cv->num_ruler_intersections-1].y*tab->scale);
971 	real xdist = fabs(cv->ruler_intersections[0].x - cv->ruler_intersections[cv->num_ruler_intersections-1].x);
972 	real ydist = fabs(cv->ruler_intersections[0].y - cv->ruler_intersections[cv->num_ruler_intersections-1].y);
973 	int i;
974 	int len;
975 	int charwidth = 6; /* TBD */
976 	Color textcolor = (cv->start_intersection_snapped && cv->end_intersection_snapped) ? measuretoolcanvasnumberssnappedcol : measuretoolcanvasnumberscol;
977 	GRect prev_rect;
978 
979 	if ( measuretoolshowhorizontolvertical ) {
980 	    char buf[40];
981 	    unichar_t ubuf[40];
982 
983 	    if ( xdist*tab->scale>10.0 && ydist*tab->scale>10.0 ) {
984 
985 		GDrawSetFont(pixmap,cv->rfont);
986 		len = snprintf(buf,sizeof buf,"%g",xdist);
987 		utf82u_strcpy(ubuf,buf);
988 		GDrawDrawText(pixmap,(x+xend)/2 - len*charwidth/2,y + (y > yend ? 12 : -5),ubuf,-1,textcolor);
989 		GDrawDrawLine(pixmap,x,y,xend,y,measuretoollinecol);
990 
991 		len = snprintf(buf,sizeof buf,"%g",ydist);
992 		utf82u_strcpy(ubuf,buf);
993 		GDrawDrawText(pixmap,xend + (x < xend ? charwidth/2 : -(len * charwidth + charwidth/2)),(y+yend)/2,ubuf,-1,textcolor);
994 		GDrawDrawLine(pixmap,xend,y,xend,yend,measuretoollinecol);
995 	    }
996 	}
997 
998 	if ( !cv->p.rubberlining ) {
999 	    GDrawDrawLine(pixmap,x,y,xend,yend,measuretoollinecol);
1000 	}
1001 
1002 	GDrawSetFont(pixmap,cv->rfont);
1003 	for ( i=0 ; i<cv->num_ruler_intersections; ++i ) {
1004 	    GRect rect;
1005 
1006 	    rect.x = tab->xoff + rint(cv->ruler_intersections[i].x*tab->scale) - 1;
1007 	    rect.y = -tab->yoff + cv->height - rint(cv->ruler_intersections[i].y*tab->scale) - 1;
1008 	    rect.width = 3;
1009 	    rect.height = 3;
1010 
1011 	    GDrawFillElipse(pixmap,&rect,((i==(cv->num_ruler_intersections-1) && cv->info_sp) || (i==0 && cv->p.sp)) ? measuretoolpointsnappedcol : measuretoolpointcol);
1012 	    if ( i>0 && (cv->num_ruler_intersections<6 || (prev_rect.x + 10)<rect.x || (prev_rect.y + 10)<rect.y || (prev_rect.y - 10)>rect.y) ) {
1013 		real xoff = cv->ruler_intersections[i].x - cv->ruler_intersections[i-1].x;
1014 		real yoff = cv->ruler_intersections[i].y - cv->ruler_intersections[i-1].y;
1015 		real len = sqrt(xoff*xoff+yoff*yoff);
1016 		char buf[40];
1017 		unichar_t ubuf[40];
1018 		int x,y;
1019 
1020 		x = (prev_rect.x + rect.x)/2;
1021 		y = (prev_rect.y + rect.y)/2;
1022 
1023 		len = snprintf(buf,sizeof buf,"%g",len);
1024 		utf82u_strcpy(ubuf,buf);
1025 		GDrawDrawText(pixmap,x + (x < xend ? -(len*charwidth) : charwidth/2 ),y + (y < yend ? 12 : -5),ubuf,-1,textcolor);
1026 	    }
1027 	    prev_rect = rect;
1028 	}
1029 	RulerLingerMove(cv);	/* in case things are moving or scaling */
1030     }
1031 }
1032