Commit 8a94b1efd6cfe76ffbd66e9315542c9438a34722

Werner Lemberg 2014-04-12T20:44:33

[autofit] Redesign the recognition algorithm of strong points. In particular, local extrema without horizontal or vertical segments are better recognized: + A + D \ / \ / \ / \ / \ + C \ / B +/ If the distances AB and CD are large, point B wasn't previously detected as an extremum since the `ft_corner_is_flat' function `swallowed' BC regardless of its direction, tagging point B as weak. The next iteration started at B and made `ft_corner_is_flat' swallow point C, tagging it as weak also, et voilĂ . To improve that, another pass gets now performed before calling `ft_corner_is_flat' to improve the `topology' of an outline: A sequence of non-horizontal or non-vertical vectors that point into the same quadrant are handled as a single, large vector. Additionally, distances of near points are now accumulated, which makes the auto-hinter handle them as if they were prepended to the next non-near vector. This generally improves the auto-hinter's rendering results. * src/autofit/afhints.c (af_glyph_hints_reload): Implement it. * src/autofit/afhints.h (AF_FLAGS): Remove no longer used flag `AF_FLAG_NEAR'.

diff --git a/ChangeLog b/ChangeLog
index 0ef12bf..98f4ee5 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,41 @@
+2014-04-12  Werner Lemberg  <wl@gnu.org>
+
+	[autofit] Redesign the recognition algorithm of strong points.
+
+	In particular, local extrema without horizontal or vertical segments
+	are better recognized:
+
+	  + A                + D
+	   \                /
+	    \              /
+	     \            /
+	      \          /
+	       \        + C
+	        \    /
+	       B +/
+
+	If the distances AB and CD are large, point B wasn't previously
+	detected as an extremum since the `ft_corner_is_flat' function
+	`swallowed' BC regardless of its direction, tagging point B as weak.
+	The next iteration started at B and made `ft_corner_is_flat' swallow
+	point C, tagging it as weak also, et voilĂ .
+
+	To improve that, another pass gets now performed before calling
+	`ft_corner_is_flat' to improve the `topology' of an outline: A
+	sequence of non-horizontal or non-vertical vectors that point into
+	the same quadrant are handled as a single, large vector.
+
+	Additionally, distances of near points are now accumulated, which
+	makes the auto-hinter handle them as if they were prepended to the
+	next non-near vector.
+
+	This generally improves the auto-hinter's rendering results.
+
+	* src/autofit/afhints.c (af_glyph_hints_reload): Implement it.
+
+	* src/autofit/afhints.h (AF_FLAGS): Remove no longer used flag
+	`AF_FLAG_NEAR'.
+
 2014-04-05  Werner Lemberg  <wl@gnu.org>
 
 	[autofit] Improve scoring algorithm for identifying stems.
diff --git a/src/autofit/afhints.c b/src/autofit/afhints.c
index 270a06b..905662b 100644
--- a/src/autofit/afhints.c
+++ b/src/autofit/afhints.c
@@ -698,91 +698,165 @@
         }
       }
 
-      /* compute directions of in & out vectors */
       {
-        AF_Point      first  = points;
-        AF_Point      prev   = NULL;
-        FT_Pos        in_x   = 0;
-        FT_Pos        in_y   = 0;
-        AF_Direction  in_dir = AF_DIR_NONE;
-
-        FT_Pos  last_good_in_x = 0;
-        FT_Pos  last_good_in_y = 0;
-
+        /*
+         *  Compute directions of `in' and `out' vectors.
+         *
+         *  Note that distances between points that are very near to each
+         *  other are accumulated.  In other words, the auto-hinter
+         *  prepends the small vectors between near points to the first
+         *  non-near vector.  All intermediate points are tagged as
+         *  weak; the directions are adjusted also to be equal to the
+         *  accumulated one.
+         */
+
+        /* value 20 in `near_limit' is heuristic */
         FT_UInt  units_per_em = hints->metrics->scaler.face->units_per_EM;
         FT_Int   near_limit   = 20 * units_per_em / 2048;
 
+        AF_Point*  contour;
+        AF_Point*  contour_limit = hints->contours + hints->num_contours;
 
-        for ( point = points; point < point_limit; point++ )
+
+        for ( contour = hints->contours; contour < contour_limit; contour++ )
         {
-          AF_Point  next;
-          FT_Pos    out_x, out_y;
+          AF_Point  first = *contour;
+          AF_Point  next, prev, curr;
+
+          FT_Pos  out_x, out_y;
+
+          FT_Bool  is_first;
 
 
-          if ( point == first )
+          /* since the first point of a contour could be part of a */
+          /* series of near points, go backwards to find the first */
+          /* non-near point and adjust `first'                     */
+
+          point = first;
+          prev  = first->prev;
+
+          while ( prev != first )
           {
-            prev = first->prev;
+            out_x = point->fx - prev->fx;
+            out_y = point->fy - prev->fy;
 
-            in_x = first->fx - prev->fx;
-            in_y = first->fy - prev->fy;
+            /* we use Taxicab metrics to measure the vector length */
+            if ( FT_ABS( out_x ) + FT_ABS( out_y ) >= near_limit )
+              break;
 
-            last_good_in_x = in_x;
-            last_good_in_y = in_y;
+            point = prev;
+            prev  = prev->prev;
+          }
 
-            if ( FT_ABS( in_x ) + FT_ABS( in_y ) < near_limit )
-            {
-              /* search first non-near point to get a good `in_dir' value */
+          /* adjust first point */
+          first = point;
 
-              AF_Point  point_ = prev;
+          /* now loop over all points of the contour to get */
+          /* `in' and `out' vector directions               */
 
+          curr  = first;
+          out_x = 0;
+          out_y = 0;
 
-              while ( point_ != first )
-              {
-                AF_Point  prev_ = point_->prev;
+          is_first = 1;
 
-                FT_Pos  in_x_ = point_->fx - prev_->fx;
-                FT_Pos  in_y_ = point_->fy - prev_->fy;
+          for ( point = first;
+                point != first || is_first;
+                point = point->next )
+          {
+            AF_Direction  out_dir;
 
 
-                if ( FT_ABS( in_x_ ) + FT_ABS( in_y_ ) >= near_limit )
-                {
-                  last_good_in_x = in_x_;
-                  last_good_in_y = in_y_;
+            is_first = 0;
 
-                  break;
-                }
+            next = point->next;
 
-                point_ = prev_;
-              }
+            out_x += next->fx - point->fx;
+            out_y += next->fy - point->fy;
+
+            if ( FT_ABS( out_x ) + FT_ABS( out_y ) < near_limit )
+            {
+              next->flags |= AF_FLAG_WEAK_INTERPOLATION;
+              continue;
             }
 
-            in_dir = af_direction_compute( in_x, in_y );
-            first  = prev + 1;
+            /* we abuse the `u' and `v' fields to store index deltas */
+            /* to the next and previous non-near point, respectively */
+            curr->u = (FT_Pos)( next - curr );
+            next->v = -curr->u;
+
+            out_dir = af_direction_compute( out_x, out_y );
+
+            /* adjust directions for all points inbetween; */
+            /* the loop also updates position of `curr'    */
+            curr->out_dir = (FT_Char)out_dir;
+            for ( curr = curr->next; curr != next; curr = curr->next )
+            {
+              curr->in_dir  = (FT_Char)out_dir;
+              curr->out_dir = (FT_Char)out_dir;
+            }
+            next->in_dir = (FT_Char)out_dir;
+
+            out_x = 0;
+            out_y = 0;
           }
+        }
 
-          point->in_dir = (FT_Char)in_dir;
+        /*
+         *  The next step is to `simplify' an outline's topology so that we
+         *  can identify local extrema more reliably: A series of
+         *  non-horizontal or non-vertical vectors pointing into the same
+         *  quadrant are handled as a single, long vector.  From a
+         *  topological point of the view, the intermediate points are of no
+         *  interest and thus tagged as weak.
+         */
 
-          /* check whether the current point is near to the previous one */
-          /* (value 20 in `near_limit' is heuristic; we use Taxicab      */
-          /* metrics for the test)                                       */
+        for ( point = points; point < point_limit; point++ )
+        {
+          if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
+            continue;
 
-          if ( FT_ABS( in_x ) + FT_ABS( in_y ) < near_limit )
-            point->flags |= AF_FLAG_NEAR;
-          else
+          if ( point->in_dir  == AF_DIR_NONE &&
+               point->out_dir == AF_DIR_NONE )
           {
-            last_good_in_x = in_x;
-            last_good_in_y = in_y;
-          }
+            /* check whether both vectors point into the same quadrant */
+
+            FT_Pos  in_x, in_y;
+            FT_Pos  out_x, out_y;
+
+            AF_Point  next_u = point + point->u;
+            AF_Point  prev_v = point + point->v;
+
+
+            in_x = point->fx - prev_v->fx;
+            in_y = point->fy - prev_v->fy;
 
-          next  = point->next;
-          out_x = next->fx - point->fx;
-          out_y = next->fy - point->fy;
+            out_x = next_u->fx - point->fx;
+            out_y = next_u->fy - point->fy;
 
-          in_dir         = af_direction_compute( out_x, out_y );
-          point->out_dir = (FT_Char)in_dir;
+            if ( ( in_x ^ out_x ) >= 0 && ( in_y ^ out_y ) >= 0 )
+            {
+              /* yes, so tag current point as weak */
+              /* and update index deltas           */
+
+              point->flags |= AF_FLAG_WEAK_INTERPOLATION;
+
+              prev_v->u = (FT_Pos)( next_u - prev_v );
+              next_u->v = -prev_v->u;
+            }
+          }
+        }
 
-          /* Check for weak points.  The remaining points not collected */
-          /* in edges are then implicitly classified as strong points.  */
+        /*
+         *  Finally, check for remaining weak points.  Everything else not
+         *  collected in edges so far is then implicitly classified as strong
+         *  points.
+         */
+
+        for ( point = points; point < point_limit; point++ )
+        {
+          if ( point->flags & AF_FLAG_WEAK_INTERPOLATION )
+            continue;
 
           if ( point->flags & AF_FLAG_CONTROL )
           {
@@ -799,18 +873,25 @@
               goto Is_Weak_Point;
             }
 
-            /* test whether `in' and `out' direction is approximately */
-            /* the same (and use the last good `in' vector in case    */
-            /* the current point is near to the previous one)         */
-            if ( ft_corner_is_flat(
-                   point->flags & AF_FLAG_NEAR ? last_good_in_x : in_x,
-                   point->flags & AF_FLAG_NEAR ? last_good_in_y : in_y,
-                   out_x,
-                   out_y ) )
             {
-              /* current point lies on a straight, diagonal line */
-              /* (more or less)                                  */
-              goto Is_Weak_Point;
+              AF_Point  next_u = point + point->u;
+              AF_Point  prev_v = point + point->v;
+
+
+              if ( ft_corner_is_flat( point->fx  - prev_v->fx,
+                                      point->fy  - prev_v->fy,
+                                      next_u->fx - point->fx,
+                                      next_u->fy - point->fy ) )
+              {
+                /* either the `in' or the `out' vector is much more  */
+                /* dominant than the other one, so tag current point */
+                /* as weak and update index deltas                   */
+
+                prev_v->u = (FT_Pos)( next_u - prev_v );
+                next_u->v = -prev_v->u;
+
+                goto Is_Weak_Point;
+              }
             }
           }
           else if ( point->in_dir == -point->out_dir )
@@ -818,9 +899,6 @@
             /* current point forms a spike */
             goto Is_Weak_Point;
           }
-
-          in_x = out_x;
-          in_y = out_y;
         }
       }
     }
diff --git a/src/autofit/afhints.h b/src/autofit/afhints.h
index 5f1507f..6e1b1ff 100644
--- a/src/autofit/afhints.h
+++ b/src/autofit/afhints.h
@@ -4,7 +4,7 @@
 /*                                                                         */
 /*    Auto-fitter hinting routines (specification).                        */
 /*                                                                         */
-/*  Copyright 2003-2008, 2010-2012 by                                      */
+/*  Copyright 2003-2008, 2010-2012, 2014 by                                */
 /*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
 /*                                                                         */
 /*  This file is part of the FreeType project, and may only be used,       */
@@ -236,10 +236,7 @@ FT_BEGIN_HEADER
     AF_FLAG_WEAK_INTERPOLATION = 1 << 8,
 
     /* all inflection points in the outline have this flag set */
-    AF_FLAG_INFLECTION = 1 << 9,
-
-    /* the current point is very near to another one */
-    AF_FLAG_NEAR = 1 << 10
+    AF_FLAG_INFLECTION = 1 << 9
 
   } AF_Flags;