Commit 3f542498b2c3d480238bd14fad76e1c3ef83f1b4

Werner Lemberg 2013-09-11T23:08:31

[autofit] Improve Hebrew rendering. This change introduces a new blue zone property `AF_BLUE_PROPERTY_LATIN_LONG' to make the auto-hinter ignore short top segments. * src/autofit/afblue.dat: Fix Hebrew blue strings. Use AF_BLUE_PROPERTY_LATIN_LONG for AF_BLUE_STRING_HEBREW_TOP. * src/autofit/afblue.hin (AF_BLUE_PROPERTY_LATIN_LONG): New macro. * src/autofit/afblue.c, src/autofit/afblue.h: Updated. * src/autofit/aflatin.c (af_latin_metrics_init_blues): Handle `AF_LATIN_IS_LONG_BLUE'. * src/autofit/aflatin.h (AF_LATIN_IS_LONG_BLUE): New macro.

diff --git a/ChangeLog b/ChangeLog
index 3ec7f09..79d1e37 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,23 @@
+2013-09-11  Werner Lemberg  <wl@gnu.org>
+
+	[autofit] Improve Hebrew rendering.
+
+	This change introduces a new blue zone property
+	`AF_BLUE_PROPERTY_LATIN_LONG' to make the auto-hinter ignore short
+	top segments.
+
+	* src/autofit/afblue.dat: Fix Hebrew blue strings.
+	Use AF_BLUE_PROPERTY_LATIN_LONG for AF_BLUE_STRING_HEBREW_TOP.
+
+	* src/autofit/afblue.hin (AF_BLUE_PROPERTY_LATIN_LONG): New macro.
+
+	* src/autofit/afblue.c, src/autofit/afblue.h: Updated.
+
+	* src/autofit/aflatin.c (af_latin_metrics_init_blues): Handle
+	`AF_LATIN_IS_LONG_BLUE'.
+
+	* src/autofit/aflatin.h (AF_LATIN_IS_LONG_BLUE): New macro.
+
 2013-08-28  Behdad Esfahbod  <behdad@google.com>
 
 	[sfnt] Fix frame access while reading WOFF table directory.
diff --git a/src/autofit/afblue.c b/src/autofit/afblue.c
index 0947d2a..f025e1b 100644
--- a/src/autofit/afblue.c
+++ b/src/autofit/afblue.c
@@ -36,9 +36,9 @@
     '\0',
     'p', 'q', 'g', 'j', 'y',  /* pqgjy */
     '\0',
-    '\xD7', '\x90', '\xD7', '\x91', '\xD7', '\x9D', '\xD7', '\xA4', '\xD7', '\xA9', '\xD7', '\x93', '\xD7', '\x92',  /* אבםפשדג */
+    '\xD7', '\x91', '\xD7', '\x93', '\xD7', '\x94', '\xD7', '\x97', '\xD7', '\x9A', '\xD7', '\x9B', '\xD7', '\x9D', '\xD7', '\xA1',  /* בדהחךכםס */
     '\0',
-    '\xD7', '\x90', '\xD7', '\x94', '\xD7', '\x97', '\xD7', '\x9B', '\xD7', '\x9E', '\xD7', '\xA1',  /* אהחכמס */
+    '\xD7', '\x91', '\xD7', '\x98', '\xD7', '\x9B', '\xD7', '\x9D', '\xD7', '\xA1', '\xD7', '\xA6',  /* בטכםסצ */
     '\0',
     '\xD7', '\xA7', '\xD7', '\x9A', '\xD7', '\x9F', '\xD7', '\xA3', '\xD7', '\xA5',  /* קךןףץ */
 #ifdef AF_CONFIG_OPTION_CJK
@@ -103,10 +103,11 @@
     { AF_BLUE_STRING_LATIN_SMALL,          0                                  },
     { AF_BLUE_STRING_LATIN_SMALL_MINOR,    0                                  },
     { AF_BLUE_STRING_MAX,                  0                                  },
-    { AF_BLUE_STRING_HEBREW_TOP,       AF_BLUE_PROPERTY_LATIN_TOP },
-    { AF_BLUE_STRING_HEBREW_BOTTOM,    0                          },
-    { AF_BLUE_STRING_HEBREW_DESCENDER, 0                          },
-    { AF_BLUE_STRING_MAX,              0                          },
+    { AF_BLUE_STRING_HEBREW_TOP,       AF_BLUE_PROPERTY_LATIN_TOP  |
+                                       AF_BLUE_PROPERTY_LATIN_LONG   },
+    { AF_BLUE_STRING_HEBREW_BOTTOM,    0                             },
+    { AF_BLUE_STRING_HEBREW_DESCENDER, 0                             },
+    { AF_BLUE_STRING_MAX,              0                             },
 #ifdef AF_CONFIG_OPTION_CJK
     { AF_BLUE_STRING_CJK_TOP_FILL,      AF_BLUE_PROPERTY_CJK_TOP |
                                         AF_BLUE_PROPERTY_CJK_FILL    },
diff --git a/src/autofit/afblue.dat b/src/autofit/afblue.dat
index 5527b2c..af85fc3 100644
--- a/src/autofit/afblue.dat
+++ b/src/autofit/afblue.dat
@@ -77,9 +77,9 @@ AF_BLUE_STRING_ENUM AF_BLUE_STRINGS_ARRAY AF_BLUE_STRING_MAX_LEN:
     "pqgjy"
 
   AF_BLUE_STRING_HEBREW_TOP
-    "אבםפשדג"
+    "בדהחךכםס"
   AF_BLUE_STRING_HEBREW_BOTTOM
-    "אהחכמס"
+    "בטכםסצ"
   AF_BLUE_STRING_HEBREW_DESCENDER
     "קךןףץ"
 
@@ -147,10 +147,11 @@ AF_BLUE_STRINGSET_ENUM AF_BLUE_STRINGSETS_ARRAY AF_BLUE_STRINGSET_MAX_LEN:
     { AF_BLUE_STRING_MAX,                  0                                  }
 
   AF_BLUE_STRINGSET_HEBR
-    { AF_BLUE_STRING_HEBREW_TOP,       AF_BLUE_PROPERTY_LATIN_TOP }
-    { AF_BLUE_STRING_HEBREW_BOTTOM,    0                          }
-    { AF_BLUE_STRING_HEBREW_DESCENDER, 0                          }
-    { AF_BLUE_STRING_MAX,              0                          }
+    { AF_BLUE_STRING_HEBREW_TOP,       AF_BLUE_PROPERTY_LATIN_TOP  |
+                                       AF_BLUE_PROPERTY_LATIN_LONG   }
+    { AF_BLUE_STRING_HEBREW_BOTTOM,    0                             }
+    { AF_BLUE_STRING_HEBREW_DESCENDER, 0                             }
+    { AF_BLUE_STRING_MAX,              0                             }
 
 #ifdef AF_CONFIG_OPTION_CJK
 
diff --git a/src/autofit/afblue.h b/src/autofit/afblue.h
index 67f6777..cbe46b1 100644
--- a/src/autofit/afblue.h
+++ b/src/autofit/afblue.h
@@ -80,9 +80,9 @@ FT_BEGIN_HEADER
     AF_BLUE_STRING_LATIN_SMALL = 26,
     AF_BLUE_STRING_LATIN_SMALL_MINOR = 34,
     AF_BLUE_STRING_HEBREW_TOP = 40,
-    AF_BLUE_STRING_HEBREW_BOTTOM = 55,
-    AF_BLUE_STRING_HEBREW_DESCENDER = 68,
-    af_blue_1_1 = 78,
+    AF_BLUE_STRING_HEBREW_BOTTOM = 57,
+    AF_BLUE_STRING_HEBREW_DESCENDER = 70,
+    af_blue_1_1 = 80,
 #ifdef AF_CONFIG_OPTION_CJK
     AF_BLUE_STRING_CJK_TOP_FILL = af_blue_1_1 + 1,
     AF_BLUE_STRING_CJK_TOP_UNFILL = af_blue_1_1 + 77,
@@ -129,6 +129,7 @@ FT_BEGIN_HEADER
   /* is a safe bet.                                                        */
 #define AF_BLUE_PROPERTY_LATIN_TOP        ( 1 << 0 )
 #define AF_BLUE_PROPERTY_LATIN_SMALL_TOP  ( 1 << 1 )
+#define AF_BLUE_PROPERTY_LATIN_LONG       ( 1 << 2 )
 
 #define AF_BLUE_PROPERTY_CJK_HORIZ  ( 1 << 0 )
 #define AF_BLUE_PROPERTY_CJK_TOP    ( 1 << 1 )
diff --git a/src/autofit/afblue.hin b/src/autofit/afblue.hin
index 424ca7e..a9f1ae1 100644
--- a/src/autofit/afblue.hin
+++ b/src/autofit/afblue.hin
@@ -98,6 +98,7 @@ FT_BEGIN_HEADER
   /* is a safe bet.                                                        */
 #define AF_BLUE_PROPERTY_LATIN_TOP        ( 1 << 0 )
 #define AF_BLUE_PROPERTY_LATIN_SMALL_TOP  ( 1 << 1 )
+#define AF_BLUE_PROPERTY_LATIN_LONG       ( 1 << 2 )
 
 #define AF_BLUE_PROPERTY_CJK_HORIZ  ( 1 << 0 )
 #define AF_BLUE_PROPERTY_CJK_TOP    ( 1 << 1 )
diff --git a/src/autofit/aflatin.c b/src/autofit/aflatin.c
index 93c9f31..7243fb9 100644
--- a/src/autofit/aflatin.c
+++ b/src/autofit/aflatin.c
@@ -219,9 +219,7 @@
 
 
     /* we walk over the blue character strings as specified in the  */
-    /* script's entry in the `af_blue_stringset' array, finding its */
-    /* top-most or bottom-most points (depending on the string      */
-    /* properties)                                                  */
+    /* script's entry in the `af_blue_stringset' array              */
 
     FT_TRACE5(( "latin blue zones computation\n"
                 "============================\n"
@@ -290,7 +288,7 @@
 
             /* Avoid single-point contours since they are never rasterized. */
             /* In some fonts, they correspond to mark attachment points     */
-            /* which are way outside of the glyph's real outline.           */
+            /* that are way outside of the glyph's real outline.            */
             if ( last <= first )
               continue;
 
@@ -319,8 +317,6 @@
               best_contour_last  = last;
             }
           }
-
-          FT_TRACE5(( "  U+%04lX: best_y = %5ld", ch, best_y ));
         }
 
         /* now check whether the point belongs to a straight or round   */
@@ -403,6 +399,178 @@
 
           } while ( next != best_point );
 
+          if ( AF_LATIN_IS_LONG_BLUE( bs ) )
+          {
+            /* If this flag is set, we have an additional constraint to  */
+            /* get the blue zone distance: Find a segment of the topmost */
+            /* (or bottommost) contour that is longer than a heuristic   */
+            /* threshold.  This ensures that small bumps in the outline  */
+            /* are ignored (for example, the `vertical serifs' found in  */
+            /* many Hebrew glyph designs).                               */
+
+            /* If this segment is long enough, we are done.  Otherwise,  */
+            /* search the segment next to the extremum that is long      */
+            /* enough, has the same direction, and a not too large       */
+            /* vertical distance from the extremum.  Note that the       */
+            /* algorithm doesn't check whether the found segment is      */
+            /* actually the one (vertically) nearest to the extremum.    */
+
+            /* heuristic threshold value */
+            FT_Pos  length_threshold = metrics->units_per_em / 25;
+
+
+            dist = FT_ABS( points[best_segment_last].x -
+                             points[best_segment_first].x );
+
+            if ( dist < length_threshold                       &&
+                 best_segment_last - best_segment_first + 2 <=
+                   best_contour_last - best_contour_first      )
+            {
+              /* heuristic threshold value */
+              FT_Pos  height_threshold = metrics->units_per_em / 4;
+
+              FT_Int   first;
+              FT_Int   last;
+              FT_Bool  hit;
+
+              FT_Bool  left2right;
+
+
+              /* compute direction */
+              prev = best_point;
+
+              do
+              {
+                if ( prev > best_contour_first )
+                  prev--;
+                else
+                  prev = best_contour_last;
+
+                if ( points[prev].x != best_x )
+                  break;
+
+              } while ( prev != best_point );
+
+              /* skip glyph for the degenerate case */
+              if ( prev == best_point )
+                continue;
+
+              left2right = FT_BOOL( points[prev].x < points[best_point].x );
+
+              first = best_segment_last;
+              last  = first;
+              hit   = 0;
+
+              do
+              {
+                FT_Bool  l2r;
+                FT_Pos   d;
+                FT_Int   p_first, p_last;
+
+
+                if ( !hit )
+                {
+                  /* no hit; adjust first point */
+                  first = last;
+
+                  /* also adjust first and last on point */
+                  if ( FT_CURVE_TAG( outline.tags[first] ) ==
+                         FT_CURVE_TAG_ON )
+                  {
+                    p_first = first;
+                    p_last  = first;
+                  }
+                  else
+                  {
+                    p_first = -1;
+                    p_last  = -1;
+                  }
+
+                  hit = 1;
+                }
+
+                if ( last < best_contour_last )
+                  last++;
+                else
+                  last = best_contour_first;
+
+                if ( FT_ABS( best_y - points[first].y ) > height_threshold )
+                {
+                  /* vertical distance too large */
+                  hit = 0;
+                  continue;
+                }
+
+                /* same test as above */
+                dist = FT_ABS( points[last].y - points[first].y );
+                if ( dist > 5 )
+                  if ( FT_ABS( points[last].x - points[first].x ) <=
+                         20 * dist )
+                  {
+                    hit = 0;
+                    continue;
+                  }
+
+                if ( FT_CURVE_TAG( outline.tags[last] ) == FT_CURVE_TAG_ON )
+                {
+                  p_last = last;
+                  if ( p_first < 0 )
+                    p_first = last;
+                }
+
+                l2r = FT_BOOL( points[first].x < points[last].x );
+                d   = FT_ABS( points[last].x - points[first].x );
+
+                if ( l2r == left2right     &&
+                     d >= length_threshold )
+                {
+                  /* all constraints are met; update segment after finding */
+                  /* its end                                               */
+                  do
+                  {
+                    if ( last < best_contour_last )
+                      last++;
+                    else
+                      last = best_contour_first;
+
+                    d = FT_ABS( points[last].y - points[first].y );
+                    if ( d > 5 )
+                      if ( FT_ABS( points[next].x - points[first].x ) <=
+                             20 * dist )
+                      {
+                        last--;
+                        break;
+                      }
+
+                    p_last = last;
+
+                    if ( FT_CURVE_TAG( outline.tags[last] ) ==
+                           FT_CURVE_TAG_ON )
+                    {
+                      p_last = last;
+                      if ( p_first < 0 )
+                        p_first = last;
+                    }
+
+                  } while ( last != best_segment_first );
+
+                  best_y = points[first].y;
+
+                  best_segment_first = first;
+                  best_segment_last  = last;
+
+                  best_on_point_first = p_first;
+                  best_on_point_last  = p_last;
+
+                  break;
+                }
+
+              } while ( last != best_segment_first );
+            }
+          }
+
+          FT_TRACE5(( "  U+%04lX: best_y = %5ld", ch, best_y ));
+
           /* now set the `round' flag depending on the segment's kind: */
           /*                                                           */
           /* - if the horizontal distance between the first and last   */
diff --git a/src/autofit/aflatin.h b/src/autofit/aflatin.h
index 8581fe5..7d294aa 100644
--- a/src/autofit/aflatin.h
+++ b/src/autofit/aflatin.h
@@ -65,6 +65,8 @@ FT_BEGIN_HEADER
           ( (b)->properties & AF_BLUE_PROPERTY_LATIN_TOP )
 #define AF_LATIN_IS_SMALL_TOP_BLUE( b ) \
           ( (b)->properties & AF_BLUE_PROPERTY_LATIN_SMALL_TOP )
+#define AF_LATIN_IS_LONG_BLUE( b ) \
+          ( (b)->properties & AF_BLUE_PROPERTY_LATIN_LONG )
 
 #define AF_LATIN_MAX_WIDTHS  16