Commit b6fd5bc06c4434a832328c4682544c436484265e

Nikolaus Waxweiler 2015-11-03T10:40:58

* src/autofit/afloader.c (af_loader_load_g): Implement emboldening.

diff --git a/ChangeLog b/ChangeLog
index e9aaefe..80b0b2f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2015-11-02  Nikolaus Waxweiler  <madigens@gmail.com>
 
+	* src/autofit/afloader.c (af_loader_load_g): Implement emboldening.
+
+2015-11-02  Nikolaus Waxweiler  <madigens@gmail.com>
+
 	[autofit] Implement darkening computation function.
 
 	This is a crude adaption of the original `cf2_computeDarkening'
diff --git a/src/autofit/afloader.c b/src/autofit/afloader.c
index 615743e..f37530d 100644
--- a/src/autofit/afloader.c
+++ b/src/autofit/afloader.c
@@ -76,6 +76,14 @@
   }
 
 
+#define af_intToFixed( i ) \
+          ( (FT_Fixed)( (FT_UInt32)(i) << 16 ) )
+#define af_fixedToInt( x ) \
+          ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) )
+#define af_floatToFixed( f ) \
+          ( (FT_Fixed)( (f) * 65536.0 + 0.5 ) )
+
+
   /* Do the main work of `af_loader_load_glyph'.  Note that we never   */
   /* have to deal with composite glyphs as those get loaded into       */
   /* FT_GLYPH_FORMAT_OUTLINE by the recursed `FT_Load_Glyph' function. */
@@ -88,6 +96,8 @@
                     FT_UInt    glyph_index,
                     FT_Int32   load_flags )
   {
+    AF_Module  module = loader->globals->module;
+
     FT_Error          error;
     FT_Face           face     = loader->face;
     AF_StyleMetrics   metrics  = loader->metrics;
@@ -103,6 +113,132 @@
     if ( error )
       goto Exit;
 
+    /*
+     * Apply stem darkening (emboldening) here before hints are applied to
+     * the outline.  Glyphs are scaled down proportionally to the
+     * emboldening so that curve points don't fall outside their precomputed
+     * blue zones.
+     *
+     * Any emboldening done by the font driver (e.g., the CFF driver)
+     * doesn't reach here because the autohinter loads the unprocessed
+     * glyphs in font units for analysis (functions `af_*_metrics_init_*')
+     * and then above to prepare it for the rasterizers by itself,
+     * independently of the font driver.  So emboldening must be done here,
+     * within the autohinter.
+     *
+     * All glyphs to be autohinted pass through here one by one.  The
+     * standard widths can therefore change from one glyph to the next,
+     * depending on what script a glyph is assigned to (each script has its
+     * own set of standard widths and other metrics).  The darkening amount
+     * must therefore be recomputed for each size and
+     * `standard_{vertical,horizontal}_width' change.
+     */
+    if ( !module->no_stem_darkening )
+    {
+      AF_FaceGlobals         globals = loader->globals;
+      AF_WritingSystemClass  writing_system_class;
+
+      FT_Pos  stdVW = 0;
+      FT_Pos  stdHW = 0;
+
+      FT_Bool  size_changed = face->size->metrics.x_ppem
+                                != globals->stem_darkening_for_ppem;
+
+      FT_Fixed  em_size  = af_intToFixed( face->units_per_EM );
+      FT_Fixed  em_ratio = FT_DivFix( af_intToFixed( 1000 ), em_size );
+
+      FT_Matrix  scale_down_matrix = { 0x10000L, 0, 0, 0x10000L };
+
+
+      /* Skip stem darkening for broken fonts. */
+      if ( !face->units_per_EM )
+        goto After_Emboldening;
+
+      /*
+       * We depend on the writing system (script analyzers) to supply
+       * standard widths for the script of the glyph we are looking at.  If
+       * it can't deliver, stem darkening is effectively disabled.
+       */
+      writing_system_class =
+        AF_WRITING_SYSTEM_CLASSES_GET[metrics->style_class->writing_system];
+
+      if ( writing_system_class->style_metrics_getstdw )
+        writing_system_class->style_metrics_getstdw( metrics,
+                                                     &stdHW,
+                                                     &stdVW );
+      else
+        goto After_Emboldening;
+
+
+      if ( size_changed                                               ||
+           ( stdVW > 0 && stdVW != globals->standard_vertical_width ) )
+      {
+        FT_Fixed  darken_by_font_units_x, darken_x;
+
+
+        darken_by_font_units_x =
+          af_intToFixed( af_loader_compute_darkening( loader,
+                                                      face,
+                                                      stdVW ) );
+        darken_x = FT_DivFix( FT_MulFix( darken_by_font_units_x,
+                                         face->size->metrics.x_scale ),
+                              em_ratio );
+
+        globals->standard_vertical_width = stdVW;
+        globals->stem_darkening_for_ppem = face->size->metrics.x_ppem;
+        globals->darken_x                = af_fixedToInt( darken_x );
+      }
+
+      if ( size_changed                                                 ||
+           ( stdHW > 0 && stdHW != globals->standard_horizontal_width ) )
+      {
+        FT_Fixed  darken_by_font_units_y, darken_y;
+
+
+        darken_by_font_units_y =
+          af_intToFixed( af_loader_compute_darkening( loader,
+                                                      face,
+                                                      stdHW ) );
+        darken_y = FT_DivFix( FT_MulFix( darken_by_font_units_y,
+                                         face->size->metrics.y_scale ),
+                              em_ratio );
+
+        globals->standard_horizontal_width = stdHW;
+        globals->stem_darkening_for_ppem   = face->size->metrics.x_ppem;
+        globals->darken_y                  = af_fixedToInt( darken_y );
+
+        /*
+         * Scale outlines down on the Y-axis to keep them inside their blue
+         * zones.  The stronger the emboldening, the stronger the
+         * downscaling (plus heuristical padding to prevent outlines still
+         * falling out their zones due to rounding).
+         *
+         * Reason: `FT_Outline_Embolden' works by shifting the rightmost
+         * points of stems farther to the right, and topmost points farther
+         * up.  This positions points on the Y-axis outside their
+         * pre-computed blue zones and leads to distortion when applying the
+         * hints in the code further below.  Code outside this emboldening
+         * block doesn't know we are presenting it with modified outlines
+         * the analyzer didn't see!
+         *
+         * An unfortunate side effect of downscaling is that the emboldening
+         * effect is slightly decreased.  The loss becomes more pronounced
+         * versus the CFF driver at smaller sizes, e.g., at 9ppem and below.
+         */
+        globals->scale_down_factor =
+          FT_DivFix( em_size - ( darken_by_font_units_y + af_intToFixed( 8 ) ),
+                     em_size );
+      }
+
+      FT_Outline_EmboldenXY( &slot->outline,
+                             globals->darken_x,
+                             globals->darken_y );
+
+      scale_down_matrix.yy = globals->scale_down_factor;
+      FT_Outline_Transform( &slot->outline, &scale_down_matrix );
+    }
+
+  After_Emboldening:
     loader->transformed = internal->glyph_transformed;
     if ( loader->transformed )
     {
@@ -412,13 +548,6 @@
    *
    * XXX: Currently a crude adaption of the original algorithm.  Do better?
    */
-#define af_intToFixed( i ) \
-          ( (FT_Fixed)( (FT_UInt32)(i) << 16 ) )
-#define af_fixedToInt( x ) \
-          ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) )
-#define af_floatToFixed( f ) \
-          ( (FT_Fixed)( (f) * 65536.0 + 0.5 ) )
-
   FT_LOCAL_DEF( FT_Int32 )
   af_loader_compute_darkening( AF_Loader  loader,
                                FT_Face    face,