Commit f216947813ab537e171897dee46c00cbfc22ed8a

Werner Lemberg 2016-02-28T22:48:34

[autofit] Properly handle spikes pointing to the x-axis. An example that gets better rendered is glyph `uusignTaml' (glyph index 2286) in font `FreeSerif.ttf' (Version 0412.2263) at 22ppem. * src/autofit/aflatin.c (af_latin_hints_compute_segments): Properly handle segments where the last point of the first segment is identical to the first point in the second one. This can happen for malformed fonts or spikes. We either merge the new segment with the previous one (both segments point into the same direction), or we discard the shorter segment if they point into different directions.

diff --git a/ChangeLog b/ChangeLog
index c0c9796..a667c0a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2016-02-27  Werner Lemberg  <wl@gnu.org>
 
+	[autofit] Properly handle spikes pointing to the x-axis.
+
+	An example that gets better rendered is glyph `uusignTaml' (glyph
+	index 2286) in font `FreeSerif.ttf' (Version 0412.2263) at 22ppem.
+
+	* src/autofit/aflatin.c (af_latin_hints_compute_segments): Properly
+	handle segments where the last point of the first segment is
+	identical to the first point in the second one.  This can happen for
+	malformed fonts or spikes.  We either merge the new segment with the
+	previous one (both segments point into the same direction), or we
+	discard the shorter segment if they point into different directions.
+
+2016-02-27  Werner Lemberg  <wl@gnu.org>
+
 	[autofit] Minor code clean-up.
 
 	* src/autofit/aflatin.c (af_latin_hints_compute_segments): Change
diff --git a/src/autofit/aflatin.c b/src/autofit/aflatin.c
index 4134739..f94190f 100644
--- a/src/autofit/aflatin.c
+++ b/src/autofit/aflatin.c
@@ -1436,20 +1436,35 @@
     /* do each contour separately */
     for ( ; contour < contour_limit; contour++ )
     {
-      AF_Point  point      =  contour[0];
-      AF_Point  last       =  point->prev;
-      int       on_edge    =  0;
+      AF_Point  point   = contour[0];
+      AF_Point  last    = point->prev;
+      int       on_edge = 0;
 
       /* we call values measured along a segment (point->v)    */
       /* `coordinates', and values orthogonal to it (point->u) */
       /* `positions'                                           */
       FT_Pos  min_pos      =  32000;
       FT_Pos  max_pos      = -32000;
+      FT_Pos  min_coord    =  32000;
+      FT_Pos  max_coord    = -32000;
+      FT_Pos  min_flags    =  AF_FLAG_NONE;
+      FT_Pos  max_flags    =  AF_FLAG_NONE;
       FT_Pos  min_on_coord =  32000;
       FT_Pos  max_on_coord = -32000;
 
       FT_Bool  passed;
 
+      AF_Segment  prev_segment = NULL;
+
+      FT_Pos     prev_min_pos;
+      FT_Pos     prev_max_pos;
+      FT_Pos     prev_min_coord;
+      FT_Pos     prev_max_coord;
+      FT_UShort  prev_min_flags;
+      FT_UShort  prev_max_flags;
+      FT_Pos     prev_min_on_coord;
+      FT_Pos     prev_max_on_coord;
+
 
       if ( point == last )  /* skip singletons -- just in case */
         continue;
@@ -1490,6 +1505,19 @@
           if ( u > max_pos )
             max_pos = u;
 
+          /* get minimum and maximum coordinate together with flags */
+          v = point->v;
+          if ( v < min_coord )
+          {
+            min_coord = v;
+            min_flags = point->flags;
+          }
+          if ( v > max_coord )
+          {
+            max_coord = v;
+            max_flags = point->flags;
+          }
+
           /* get minimum and maximum coordinate of `on' points */
           if ( !( point->flags & AF_FLAG_CONTROL ) )
           {
@@ -1502,37 +1530,149 @@
 
           if ( point->out_dir != segment_dir || point == last )
           {
-            FT_Pos  min_coord;
-            FT_Pos  max_coord;
+            /* check whether the new segment's start point is identical to */
+            /* the previous segment's end point; for example, this might   */
+            /* happen for spikes                                           */
+
+            if ( !prev_segment || segment->first != prev_segment->last )
+            {
+              /* points are different: we are just leaving an edge, thus */
+              /* record a new segment                                    */
+
+              segment->last = point;
+              segment->pos  = (FT_Short)( ( min_pos + max_pos ) >> 1 );
+
+              /* a segment is round if either its first or last point */
+              /* is a control point, and the length of the on points  */
+              /* inbetween doesn't exceed a heuristic limit           */
+              if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
+                   ( max_on_coord - min_on_coord ) < flat_threshold )
+                segment->flags |= AF_EDGE_ROUND;
+
+              segment->min_coord = (FT_Short)min_coord;
+              segment->max_coord = (FT_Short)max_coord;
+              segment->height    = segment->max_coord - segment->min_coord;
+
+              prev_segment      = segment;
+              prev_min_pos      = min_pos;
+              prev_max_pos      = max_pos;
+              prev_min_coord    = min_coord;
+              prev_max_coord    = max_coord;
+              prev_min_flags    = min_flags;
+              prev_max_flags    = max_flags;
+              prev_min_on_coord = min_on_coord;
+              prev_max_on_coord = max_on_coord;
+            }
+            else
+            {
+              /* points are the same: we don't create a new segment but */
+              /* merge the current segment with the previous one        */
+
+              if ( prev_segment->last->in_dir == point->in_dir )
+              {
+                /* we have identical directions (this can happen for       */
+                /* degenerate outlines that move zig-zag along the main    */
+                /* axis without changing the coordinate value of the other */
+                /* axis, and where the segments have just been merged):    */
+                /* unify segments                                          */
+
+                /* update constraints */
 
+                if ( prev_min_pos < min_pos )
+                  min_pos = prev_min_pos;
+                if ( prev_max_pos > max_pos )
+                  max_pos = prev_max_pos;
 
-            /* we are just leaving an edge; record a new segment! */
-            segment->last = point;
-            segment->pos  = (FT_Short)( ( min_pos + max_pos ) >> 1 );
+                if ( prev_min_coord < min_coord )
+                {
+                  min_coord = prev_min_coord;
+                  min_flags = prev_min_flags;
+                }
+                if ( prev_max_coord > max_coord )
+                {
+                  max_coord = prev_max_coord;
+                  max_flags = prev_max_flags;
+                }
 
-            /* a segment is round if either its first or last point */
-            /* is a control point, and the length of the on points  */
-            /* inbetween doesn't exceed a heuristic limit           */
-            if ( ( segment->first->flags | point->flags ) & AF_FLAG_CONTROL &&
-                 ( max_on_coord - min_on_coord ) < flat_threshold           )
-              segment->flags |= AF_EDGE_ROUND;
+                if ( prev_min_on_coord < min_on_coord )
+                  min_on_coord = prev_min_on_coord;
+                if ( prev_max_on_coord > max_on_coord )
+                  max_on_coord = prev_max_on_coord;
+
+                prev_segment->last = point;
+                prev_segment->pos  = (FT_Short)( ( min_pos +
+                                                   max_pos ) >> 1 );
+
+                if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
+                     ( max_on_coord - min_on_coord ) < flat_threshold )
+                  prev_segment->flags |= AF_EDGE_ROUND;
+                else
+                  prev_segment->flags &= ~AF_EDGE_ROUND;
+
+                prev_segment->min_coord = (FT_Short)min_coord;
+                prev_segment->max_coord = (FT_Short)max_coord;
+                prev_segment->height    = prev_segment->max_coord -
+                                          prev_segment->min_coord;
+              }
+              else
+              {
+                /* we have different directions; use the properties of the */
+                /* longer segment and discard the other one                */
 
-            /* compute segment size */
-            min_coord = max_coord = point->v;
+                if ( FT_ABS( prev_max_coord - prev_min_coord ) >
+                     FT_ABS( max_coord - min_coord ) )
+                {
+                  /* discard current segment */
 
-            v = segment->first->v;
-            if ( v < min_coord )
-              min_coord = v;
-            if ( v > max_coord )
-              max_coord = v;
+                  if ( min_pos < prev_min_pos )
+                    prev_min_pos = min_pos;
+                  if ( max_pos > prev_max_pos )
+                    prev_max_pos = max_pos;
 
-            segment->min_coord = (FT_Short)min_coord;
-            segment->max_coord = (FT_Short)max_coord;
-            segment->height    = (FT_Short)( segment->max_coord -
-                                             segment->min_coord );
+                  prev_segment->last = point;
+                  prev_segment->pos  = (FT_Short)( ( prev_min_pos +
+                                                     prev_max_pos ) >> 1 );
+                }
+                else
+                {
+                  /* discard previous segment */
+
+                  if ( prev_min_pos < min_pos )
+                    min_pos = prev_min_pos;
+                  if ( prev_max_pos > max_pos )
+                    max_pos = prev_max_pos;
+
+                  segment->last = point;
+                  segment->pos  = (FT_Short)( ( min_pos + max_pos ) >> 1 );
+
+                  if ( ( min_flags | max_flags ) & AF_FLAG_CONTROL      &&
+                       ( max_on_coord - min_on_coord ) < flat_threshold )
+                    segment->flags |= AF_EDGE_ROUND;
+
+                  segment->min_coord = (FT_Short)min_coord;
+                  segment->max_coord = (FT_Short)max_coord;
+                  segment->height    = segment->max_coord -
+                                       segment->min_coord;
+
+                  *prev_segment = *segment;
+
+                  prev_min_pos      = min_pos;
+                  prev_max_pos      = max_pos;
+                  prev_min_coord    = min_coord;
+                  prev_max_coord    = max_coord;
+                  prev_min_flags    = min_flags;
+                  prev_max_flags    = max_flags;
+                  prev_min_on_coord = min_on_coord;
+                  prev_max_on_coord = max_on_coord;
+                }
+              }
+
+              axis->num_segments--;
+            }
 
             on_edge = 0;
             segment = NULL;
+
             /* fall through */
           }
         }
@@ -1561,7 +1701,9 @@
           segment->first = point;
           segment->last  = point;
 
-          min_pos = max_pos = point->u;
+          min_pos   = max_pos   = point->u;
+          min_coord = max_coord = point->v;
+          min_flags = max_flags = point->flags;
 
           if ( point->flags & AF_FLAG_CONTROL )
           {