]> git.donarmstrong.com Git - xournal.git/blob - src/xo-shapes.c
minor fixes, increase version to 0.4.5, remove install-binary, add configure
[xournal.git] / src / xo-shapes.c
1 #ifdef HAVE_CONFIG_H
2 #  include <config.h>
3 #endif
4
5 #include <math.h>
6 #include <string.h>
7 #include <gtk/gtk.h>
8 #include <libgnomecanvas/libgnomecanvas.h>
9
10 #include "xournal.h"
11 #include "xo-shapes.h"
12 #include "xo-paint.h"
13
14 typedef struct Inertia {
15   double mass, sx, sy, sxx, sxy, syy;
16 } Inertia;
17
18 typedef struct RecoSegment {
19   struct Item *item;
20   int startpt, endpt;
21   double xcenter, ycenter, angle, radius;
22   double x1, y1, x2, y2;
23   gboolean reversed;
24 } RecoSegment;
25
26 struct RecoSegment recognizer_queue[MAX_POLYGON_SIDES+1];
27 int recognizer_queue_length;
28 struct UndoItem *last_item_checker; // check if queue is stale
29
30 void reset_recognizer(void)
31 {
32   recognizer_queue_length = 0;
33   last_item_checker = NULL;
34 }
35
36 /* compute mass and moments of a stroke */
37
38 void incr_inertia(double *pt, struct Inertia *s, int coef)
39 {
40   double dm;
41   dm = coef*hypot(pt[2]-pt[0], pt[3]-pt[1]);
42   s->mass += dm;
43   s->sx += dm*pt[0];
44   s->sy += dm*pt[1];
45   s->sxx += dm*pt[0]*pt[0];
46   s->syy += dm*pt[1]*pt[1];
47   s->sxy += dm*pt[0]*pt[1];
48 }
49    
50 void calc_inertia(double *pt, int start, int end, struct Inertia *s)
51 {
52   int i;
53   
54   s->mass = s->sx = s->sy = s->sxx = s->sxy = s->syy = 0.;
55   for (i=start, pt+=2*start; i<end; i++, pt+=2) incr_inertia(pt, s, 1);
56 }
57
58 /* compute normalized quantities */
59
60 inline double center_x(struct Inertia s) 
61
62   return s.sx/s.mass;
63 }
64
65 inline double center_y(struct Inertia s) 
66
67   return s.sy/s.mass;
68 }
69
70 inline double I_xx(struct Inertia s)
71 {
72   if (s.mass <= 0.) return 0.;
73   return (s.sxx - s.sx*s.sx/s.mass)/s.mass;
74 }
75
76 inline double I_xy(struct Inertia s)
77 {
78   if (s.mass <= 0.) return 0.;
79   return (s.sxy - s.sx*s.sy/s.mass)/s.mass;
80 }
81
82 inline double I_yy(struct Inertia s)
83 {
84   if (s.mass <= 0.) return 0.;
85   return (s.syy - s.sy*s.sy/s.mass)/s.mass;
86 }
87
88 inline double I_rad(struct Inertia s)
89 {
90   double ixx = I_xx(s), iyy = I_yy(s);
91   if (ixx+iyy <= 0.) return 0.;
92   return sqrt(ixx+iyy);
93 }
94
95 inline double I_det(struct Inertia s)
96 {
97   double ixx = I_xx(s), iyy = I_yy(s), ixy = I_xy(s);
98   if (s.mass <= 0.) return 0.;
99   if (ixx+iyy <= 0.) return 0.;
100   return 4*(ixx*iyy-ixy*ixy)/(ixx+iyy)/(ixx+iyy);
101 }
102
103 /* check if something is a polygonal line with at most nsides sides */
104
105 int find_polygonal(double *pt, int start, int end, int nsides, int *breaks, struct Inertia *ss)
106 {
107   struct Inertia s, s1, s2;
108   int k, i1, i2, n1, n2;
109   double det1, det2;
110   
111   if (end == start) return 0; // no way
112   if (nsides <= 0) return 0;
113   if (end-start<5) nsides = 1; // too small for a polygon
114   
115   // look for a linear piece that's big enough
116   for (k=0; k<nsides; k++) {
117     i1 = start + (k*(end-start))/nsides; 
118     i2 = start + ((k+1)*(end-start))/nsides;
119     calc_inertia(pt, i1, i2, &s);
120     if (I_det(s) < LINE_MAX_DET) break;
121   }
122   if (k==nsides) return 0; // failed!
123   
124   // grow the linear piece we found
125   while (1) {
126     if (i1>start) {
127       s1 = s;
128       incr_inertia(pt+2*(i1-1), &s1, 1);
129       det1 = I_det(s1);
130     } 
131     else det1 = 1.;
132     if (i2<end) {
133       s2 = s;
134       incr_inertia(pt+2*i2, &s2, 1);
135       det2 = I_det(s2);
136     }
137     else det2 = 1.;
138     if (det1<det2 && det1<LINE_MAX_DET) { i1--; s=s1; }
139     else if (det2<det1 && det2<LINE_MAX_DET) { i2++; s=s2; }
140     else break;
141   }
142   
143   if (i1>start) {
144     n1 = find_polygonal(pt, start, i1, (i2==end)?(nsides-1):(nsides-2), breaks, ss);
145     if (n1 == 0) return 0; // it doesn't work
146   }
147   else n1 = 0;
148
149   breaks[n1] = i1;
150   breaks[n1+1] = i2;
151   ss[n1] = s;
152
153   if (i2<end) {
154     n2 = find_polygonal(pt, i2, end, nsides-n1-1, breaks+n1+1, ss+n1+1);
155     if (n2 == 0) return 0;
156   }
157   else n2 = 0;
158   
159   return n1+n2+1;
160 }
161
162 /* improve on the polygon found by find_polygonal() */
163
164 void optimize_polygonal(double *pt, int nsides, int *breaks, struct Inertia *ss)
165 {
166   int i;
167   double cost, newcost;
168   struct Inertia s1, s2;
169   gboolean improved;
170   
171   for (i=1; i<nsides; i++) {
172     // optimize break between sides i and i+1
173     cost = I_det(ss[i-1])*I_det(ss[i-1])+I_det(ss[i])*I_det(ss[i]);
174     s1 = ss[i-1]; s2 = ss[i];
175     improved = FALSE;
176     while (breaks[i]>breaks[i-1]+1) {
177       // try moving the break to the left
178       incr_inertia(pt+2*(breaks[i]-1), &s1, -1);
179       incr_inertia(pt+2*(breaks[i]-1), &s2, 1);
180       newcost = I_det(s1)*I_det(s1)+I_det(s2)*I_det(s2);
181       if (newcost >= cost) break;
182       improved = TRUE;
183       cost = newcost; 
184       breaks[i]--;
185       ss[i-1] = s1;
186       ss[i] = s2;
187     }
188     if (improved) continue;
189     s1 = ss[i-1]; s2 = ss[i];
190     while (breaks[i]<breaks[i+1]-1) {
191       // try moving the break to the right
192       incr_inertia(pt+2*breaks[i], &s1, 1);
193       incr_inertia(pt+2*breaks[i], &s2, -1);
194       newcost = I_det(s1)*I_det(s1)+I_det(s2)*I_det(s2);
195       if (newcost >= cost) break;
196       cost = newcost; 
197       breaks[i]++;
198       ss[i-1] = s1;
199       ss[i] = s2;
200     }
201   }
202 }
203
204 /* find the geometry of a recognized segment */
205
206 void get_segment_geometry(double *pt, int start, int end, struct Inertia *s, struct RecoSegment *r)
207 {
208   double a, b, c, lmin, lmax, l;
209   int i;
210   
211   r->xcenter = center_x(*s);
212   r->ycenter = center_y(*s);
213   a = I_xx(*s); b = I_xy(*s); c = I_yy(*s);
214   /* max angle for inertia quadratic form solves: tan(2t) = 2b/(a-c) */
215   r->angle = atan2(2*b, a-c)/2; 
216   r->radius = sqrt(3*(a+c));
217   
218   lmin = lmax = 0.;
219   for (i=start, pt+=2*start; i<=end; i++, pt+=2) {
220     l = (pt[0]-r->xcenter)*cos(r->angle)+(pt[1]-r->ycenter)*sin(r->angle);
221     if (l<lmin) lmin = l;
222     if (l>lmax) lmax = l;
223   }
224   r->x1 = r->xcenter + lmin*cos(r->angle);
225   r->y1 = r->ycenter + lmin*sin(r->angle);
226   r->x2 = r->xcenter + lmax*cos(r->angle);
227   r->y2 = r->ycenter + lmax*sin(r->angle);
228 }
229
230 /* test if we have a circle; inertia has been precomputed by caller */
231
232 double score_circle(double *pt, int start, int end, struct Inertia *s)
233 {
234   double sum, x0, y0, r0, dm, deltar;
235   int i;
236   
237   if (s->mass == 0.) return 0;
238   sum = 0.;
239   x0 = center_x(*s); y0 = center_y(*s); r0 = I_rad(*s);
240   for (i=start, pt+=2*start; i<end; i++, pt+=2) {
241     dm = hypot(pt[2]-pt[0], pt[3]-pt[1]);
242     deltar = hypot(pt[0]-x0, pt[1]-y0) - r0;
243     sum += dm * fabs(deltar);
244   }
245   return sum/(s->mass*r0);
246 }
247
248 /* replace strokes by various shapes */
249
250 void make_circle_shape(double x0, double y0, double r)
251 {
252   int npts, i;
253   struct Item *item;
254   struct UndoErasureData *erasure;
255   
256   npts = (int)(2*r);
257   if (npts<12) npts = 12; // min. number of points
258   realloc_cur_path(npts+1);
259   ui.cur_path.num_points = npts+1;
260   for (i=0;i<=npts; i++) {
261     ui.cur_path.coords[2*i] = x0 + r*cos((2*M_PI*i)/npts);
262     ui.cur_path.coords[2*i+1] = y0 + r*sin((2*M_PI*i)/npts);
263   }
264 }
265
266 void calc_edge_isect(struct RecoSegment *r1, struct RecoSegment *r2, double *pt)
267 {
268   double t;
269   t = (r2->xcenter - r1->xcenter) * sin(r2->angle) - 
270       (r2->ycenter - r1->ycenter) * cos(r2->angle);
271   t /= sin(r2->angle-r1->angle);
272   pt[0] = r1->xcenter + t*cos(r1->angle);
273   pt[1] = r1->ycenter + t*sin(r1->angle);
274 }
275
276 void remove_recognized_strokes(struct RecoSegment *rs, int num_old_items)
277 {
278   struct Item *old_item;
279   int i, shift;
280   struct UndoErasureData *erasure;
281
282   old_item = NULL;
283   prepare_new_undo();
284   undo->type = ITEM_RECOGNIZER;
285   undo->layer = ui.cur_layer;
286   undo->erasurelist = NULL;
287   shift = 0;
288   
289   for (i=0; i<num_old_items; i++) {
290     if (rs[i].item == old_item) continue; // already done
291     old_item = rs[i].item;
292     erasure = g_new(struct UndoErasureData, 1);
293     erasure->item = old_item;
294     erasure->npos = g_list_index(ui.cur_layer->items, old_item) + (shift++);
295     erasure->nrepl = 0;
296     erasure->replacement_items = NULL;
297     undo->erasurelist = g_list_append(undo->erasurelist, erasure);
298     if (old_item->canvas_item != NULL)
299       gtk_object_destroy(GTK_OBJECT(old_item->canvas_item));
300     ui.cur_layer->items = g_list_remove(ui.cur_layer->items, old_item);
301     ui.cur_layer->nitems--;
302   }
303 }
304
305 struct Item *insert_recognized_curpath(void)
306 {
307   struct Item *item;
308   int i;
309   struct UndoErasureData *erasure;
310
311   erasure = (struct UndoErasureData *)(undo->erasurelist->data);
312   item = g_new(struct Item, 1);
313   item->type = ITEM_STROKE;
314   g_memmove(&(item->brush), &(erasure->item->brush), sizeof(struct Brush));
315   item->brush.variable_width = FALSE;
316   subdivide_cur_path();
317   item->path = gnome_canvas_points_new(ui.cur_path.num_points);
318   g_memmove(item->path->coords, ui.cur_path.coords, 2*ui.cur_path.num_points*sizeof(double));
319   item->widths = NULL;
320   update_item_bbox(item);
321   ui.cur_path.num_points = 0;
322   
323   erasure->nrepl++;
324   erasure->replacement_items = g_list_append(erasure->replacement_items, item);
325   ui.cur_layer->items = g_list_append(ui.cur_layer->items, item);
326   ui.cur_layer->nitems++;
327   make_canvas_item_one(ui.cur_layer->group, item);
328   return item;
329 }
330
331
332 /* test if segments form standard shapes */
333
334 gboolean try_rectangle(void)
335 {
336   struct RecoSegment *rs, *r1, *r2;
337   int i;
338   double dist, avg_angle;
339   
340   // first, we need whole strokes to combine to 4 segments...
341   if (recognizer_queue_length<4) return FALSE;
342   rs = recognizer_queue + recognizer_queue_length - 4;
343   if (rs->startpt!=0) return FALSE;
344
345   // check edges make angles ~= Pi/2 and vertices roughly match
346   avg_angle = 0.;
347   for (i=0; i<=3; i++) {
348     r1 = rs+i; r2 = rs+(i+1)%4;
349     if (fabs(fabs(r1->angle-r2->angle)-M_PI/2) > RECTANGLE_ANGLE_TOLERANCE)
350       return FALSE;
351     avg_angle += r1->angle;
352     if (r2->angle > r1->angle) avg_angle += (i+1)*M_PI/2;
353     else avg_angle -= (i+1)*M_PI/2;
354     // test if r1 points away from r2 rather than towards it
355     r1->reversed = ((r1->x2-r1->x1)*(r2->xcenter-r1->xcenter)+
356                    (r1->y2-r1->y1)*(r2->ycenter-r1->ycenter)) < 0;
357   }
358   for (i=0; i<=3; i++) {
359     r1 = rs+i; r2 = rs+(i+1)%4;
360     dist = hypot((r1->reversed?r1->x1:r1->x2) - (r2->reversed?r2->x2:r2->x1),
361                  (r1->reversed?r1->y1:r1->y2) - (r2->reversed?r2->y2:r2->y1));
362     if (dist > RECTANGLE_LINEAR_TOLERANCE*(r1->radius+r2->radius)) return FALSE;
363   }
364   
365   // make a rectangle of the correct size and slope
366   avg_angle = avg_angle/4;
367   if (fabs(avg_angle)<SLANT_TOLERANCE) avg_angle = 0.;
368   if (fabs(avg_angle)>M_PI/2-SLANT_TOLERANCE) avg_angle = M_PI/2;
369   realloc_cur_path(5);
370   ui.cur_path.num_points = 5;
371   for (i=0; i<=3; i++) rs[i].angle = avg_angle+i*M_PI/2;
372   for (i=0; i<=3; i++) calc_edge_isect(rs+i, rs+(i+1)%4, ui.cur_path.coords+2*i+2);
373   ui.cur_path.coords[0] = ui.cur_path.coords[8];
374   ui.cur_path.coords[1] = ui.cur_path.coords[9];
375   
376   remove_recognized_strokes(rs, 4);
377   insert_recognized_curpath();
378   return TRUE;
379 }
380
381 gboolean try_arrow(void)
382 {
383   struct RecoSegment *rs;
384   int i, j;
385   double alpha[3], dist, pt[2], tmp, delta;
386   double x1, y1, x2, y2, angle;
387   gboolean rev[3];
388   
389   // first, we need whole strokes to combine to nsides segments...
390   if (recognizer_queue_length<3) return FALSE;
391   rs = recognizer_queue + recognizer_queue_length - 3;
392   if (rs->startpt!=0) return FALSE;
393
394   // check arrow head not too big, and orient main segment
395   for (i=1; i<=2; i++) {
396     if (rs[i].radius > ARROW_MAXSIZE*rs[0].radius) return FALSE;
397     rev[i] = (hypot(rs[i].xcenter-rs->x1, rs[i].ycenter-rs->y1) <
398               hypot(rs[i].xcenter-rs->x2, rs[i].ycenter-rs->y2));
399   }
400   if (rev[1]!=rev[2]) return FALSE;
401   if (rev[1]) { 
402     x1 = rs->x2; y1 = rs->y2; x2 = rs->x1; y2 = rs->y1; 
403     angle = rs->angle + M_PI;
404   }
405   else { 
406     x1 = rs->x1; y1 = rs->y1; x2 = rs->x2; y2 = rs->y2;
407     angle = rs->angle;
408   }
409     
410   // check arrow head not too big, and angles roughly ok
411   for (i=1; i<=2; i++) {
412     rs[i].reversed = FALSE;
413     alpha[i] = rs[i].angle - angle;
414     while (alpha[i]<-M_PI/2) { alpha[i]+=M_PI; rs[i].reversed = !rs[i].reversed; }
415     while (alpha[i]>M_PI/2) { alpha[i]-=M_PI; rs[i].reversed = !rs[i].reversed; }
416 #ifdef RECOGNIZER_DEBUG
417     printf("DEBUG: arrow: alpha[%d] = %.1f degrees\n", i, alpha[i]*180/M_PI);
418 #endif
419     if (fabs(alpha[i])<ARROW_ANGLE_MIN || fabs(alpha[i])>ARROW_ANGLE_MAX) return FALSE;
420   }
421   
422   // check arrow head segments are roughly symmetric
423   if (alpha[1]*alpha[2]>0 || fabs(alpha[1]+alpha[2]) > ARROW_ASYMMETRY_MAX_ANGLE) return FALSE;
424   if (rs[1].radius/rs[2].radius > 1+ARROW_ASYMMETRY_MAX_LINEAR) return FALSE;
425   if (rs[2].radius/rs[1].radius > 1+ARROW_ASYMMETRY_MAX_LINEAR) return FALSE;
426
427   // check vertices roughly match
428   calc_edge_isect(rs+1, rs+2, pt);
429   for (j=1; j<=2; j++) {
430     dist = hypot(pt[0]-(rs[j].reversed?rs[j].x1:rs[j].x2),
431                  pt[1]-(rs[j].reversed?rs[j].y1:rs[j].y2));
432 #ifdef RECOGNIZER_DEBUG
433     printf("DEBUG: linear tolerance: tip[%d] = %.2f\n", j, dist/rs[j].radius);
434 #endif
435     if (dist>ARROW_TIP_LINEAR_TOLERANCE*rs[j].radius) return FALSE;
436   }
437   dist = (pt[0]-x2)*sin(angle)-(pt[1]-y2)*cos(angle);
438   dist /= rs[1].radius + rs[2].radius;
439 #ifdef RECOGNIZER_DEBUG
440   printf("DEBUG: sideways gap tolerance = %.2f\n", dist);
441 #endif
442   if (fabs(dist)>ARROW_SIDEWAYS_GAP_TOLERANCE) return FALSE;
443   dist = (pt[0]-x2)*cos(angle)+(pt[1]-y2)*sin(angle);
444   dist /= rs[1].radius + rs[2].radius;
445 #ifdef RECOGNIZER_DEBUG
446   printf("DEBUG: main linear gap = %.2f\n", dist);
447 #endif
448   if (dist<ARROW_MAIN_LINEAR_GAP_MIN || dist>ARROW_MAIN_LINEAR_GAP_MAX) return FALSE;
449
450   // make an arrow of the correct size and slope
451   if (fabs(rs->angle)<SLANT_TOLERANCE) { // nearly horizontal
452     angle = angle - rs->angle;
453     y1 = y2 = rs->ycenter;
454   }
455   if (rs->angle>M_PI/2-SLANT_TOLERANCE) { // nearly vertical
456     angle = angle - (rs->angle-M_PI/2);
457     x1 = x2 = rs->xcenter;
458   }
459   if (rs->angle<-M_PI/2+SLANT_TOLERANCE) { // nearly vertical
460     angle = angle - (rs->angle+M_PI/2);
461     x1 = x2 = rs->xcenter;
462   }
463   delta = fabs(alpha[1]-alpha[2])/2;
464   dist = (hypot(rs[1].x1-rs[1].x2, rs[1].y1-rs[1].y2) +
465           hypot(rs[2].x1-rs[2].x2, rs[2].y1-rs[2].y2))/2;
466   
467   realloc_cur_path(2);
468   ui.cur_path.num_points = 2;
469   ui.cur_path.coords[0] = x1; ui.cur_path.coords[1] = y1;
470   ui.cur_path.coords[2] = x2; ui.cur_path.coords[3] = y2;
471   remove_recognized_strokes(rs, 3);
472   insert_recognized_curpath();
473
474   realloc_cur_path(3);
475   ui.cur_path.num_points = 3;
476   ui.cur_path.coords[0] = x2 - dist*cos(angle+delta);
477   ui.cur_path.coords[1] = y2 - dist*sin(angle+delta);
478   ui.cur_path.coords[2] = x2; 
479   ui.cur_path.coords[3] = y2;
480   ui.cur_path.coords[4] = x2 - dist*cos(angle-delta);
481   ui.cur_path.coords[5] = y2 - dist*sin(angle-delta);
482   insert_recognized_curpath();
483
484   return TRUE;
485 }
486
487 gboolean try_closed_polygon(int nsides)
488 {
489   struct RecoSegment *rs, *r1, *r2;
490   int i;
491   double dist, pt[2];
492   
493   // first, we need whole strokes to combine to nsides segments...
494   if (recognizer_queue_length<nsides) return FALSE;
495   rs = recognizer_queue + recognizer_queue_length - nsides;
496   if (rs->startpt!=0) return FALSE;
497
498   // check vertices roughly match
499   for (i=0; i<nsides; i++) {
500     r1 = rs+i; r2 = rs+(i+1)%nsides;
501     // test if r1 points away from r2 rather than towards it
502     calc_edge_isect(r1, r2, pt);
503     r1->reversed = (hypot(pt[0]-r1->x1,pt[1]-r1->y1) < hypot(pt[0]-r1->x2,pt[1]-r1->y2));
504   }
505   for (i=0; i<nsides; i++) {
506     r1 = rs+i; r2 = rs+(i+1)%nsides;
507     calc_edge_isect(r1, r2, pt);
508     dist = hypot((r1->reversed?r1->x1:r1->x2)-pt[0],(r1->reversed?r1->y1:r1->y2)-pt[1])
509          + hypot((r2->reversed?r2->x2:r2->x1)-pt[0],(r2->reversed?r2->y2:r2->y1)-pt[1]);
510     if (dist > POLYGON_LINEAR_TOLERANCE*(r1->radius+r2->radius)) return FALSE;
511   }
512   
513   // make a polygon of the correct size and slope
514   realloc_cur_path(nsides+1);
515   ui.cur_path.num_points = nsides+1;
516   for (i=0; i<nsides; i++) 
517     calc_edge_isect(rs+i, rs+(i+1)%nsides, ui.cur_path.coords+2*i+2);
518   ui.cur_path.coords[0] = ui.cur_path.coords[2*nsides];
519   ui.cur_path.coords[1] = ui.cur_path.coords[2*nsides+1];
520   
521   remove_recognized_strokes(rs, nsides);
522   insert_recognized_curpath();
523   return TRUE;
524 }
525
526 /* the main pattern recognition function, called after finalize_stroke() */
527 void recognize_patterns(void)
528 {
529   struct Item *it;
530   struct Inertia s, ss[4];
531   struct RecoSegment *rs;
532   int n, i;
533   int brk[5];
534   double score;
535   
536   if (!undo || undo->type!=ITEM_STROKE) return;
537   if (undo->next != last_item_checker) reset_recognizer(); // reset queue
538   if (last_item_checker!=NULL && ui.cur_layer != last_item_checker->layer) reset_recognizer();
539
540   it = undo->item;
541   calc_inertia(it->path->coords, 0, it->path->num_points-1, &s);
542 #ifdef RECOGNIZER_DEBUG
543   printf("DEBUG: Mass=%.0f, Center=(%.1f,%.1f), I=(%.0f,%.0f, %.0f), "
544      "Rad=%.2f, Det=%.4f \n", 
545      s.mass, center_x(s), center_y(s), I_xx(s), I_yy(s), I_xy(s), I_rad(s), I_det(s));
546 #endif
547
548   // first see if it's a polygon
549   n = find_polygonal(it->path->coords, 0, it->path->num_points-1, MAX_POLYGON_SIDES, brk, ss);
550   if (n>0) {
551     optimize_polygonal(it->path->coords, n, brk, ss);
552 #ifdef RECOGNIZER_DEBUG
553     printf("DEBUG: Polygon, %d edges: ", n);
554     for (i=0; i<n; i++)
555       printf("DEBUG: %d-%d (M=%.0f, det=%.4f) ", brk[i], brk[i+1], ss[i].mass, I_det(ss[i]));
556     printf("\n");
557 #endif
558     /* update recognizer segment queue (most recent at end) */
559     while (n+recognizer_queue_length > MAX_POLYGON_SIDES) {
560       // remove oldest polygonal stroke
561       i=1;
562       while (i<recognizer_queue_length && recognizer_queue[i].startpt!=0) i++;
563       recognizer_queue_length-=i;
564       g_memmove(recognizer_queue, recognizer_queue+i, 
565               recognizer_queue_length * sizeof(struct RecoSegment));
566     }
567 #ifdef RECOGNIZER_DEBUG
568     printf("DEBUG: Queue now has %d + %d edges\n", recognizer_queue_length, n);
569 #endif
570     rs = recognizer_queue + recognizer_queue_length;
571     recognizer_queue_length += n;
572     for (i=0; i<n; i++) {
573       rs[i].item = it;
574       rs[i].startpt = brk[i];
575       rs[i].endpt = brk[i+1];
576       get_segment_geometry(it->path->coords, brk[i], brk[i+1], ss+i, rs+i);
577     }  
578     if (try_rectangle()) { reset_recognizer(); return; }
579     if (try_arrow()) { reset_recognizer(); return; }
580     if (try_closed_polygon(3)) { reset_recognizer(); return; }
581     if (try_closed_polygon(4)) { reset_recognizer(); return; }
582     if (n==1) { // current stroke is a line
583       if (fabs(rs->angle)<SLANT_TOLERANCE) { // nearly horizontal
584         rs->angle = 0.;
585         rs->y1 = rs->y2 = rs->ycenter;
586       }
587       if (fabs(rs->angle)>M_PI/2-SLANT_TOLERANCE) { // nearly vertical
588         rs->angle = (rs->angle>0)?(M_PI/2):(-M_PI/2);
589         rs->x1 = rs->x2 = rs->xcenter;
590       }
591       realloc_cur_path(2);
592       ui.cur_path.num_points = 2;
593       ui.cur_path.coords[0] = rs->x1;
594       ui.cur_path.coords[1] = rs->y1;
595       ui.cur_path.coords[2] = rs->x2;
596       ui.cur_path.coords[3] = rs->y2;
597       remove_recognized_strokes(rs, 1);
598       rs->item = insert_recognized_curpath();
599     }
600     last_item_checker = undo;
601     return;
602   }
603
604   // not a polygon: maybe a circle ?
605   reset_recognizer();
606   if (I_det(s)>CIRCLE_MIN_DET) {
607     score = score_circle(it->path->coords, 0, it->path->num_points-1, &s);
608 #ifdef RECOGNIZER_DEBUG
609     printf("DEBUG: Circle score: %.2f\n", score);
610 #endif
611     if (score < CIRCLE_MAX_SCORE) {
612       make_circle_shape(center_x(s), center_y(s), I_rad(s));
613       recognizer_queue[0].item = it;
614       remove_recognized_strokes(recognizer_queue, 1);
615       insert_recognized_curpath();
616     }
617   }
618 }
619