I just noticed that the darkening calculation might be off, making stuff
excessively bold. Here's a new patch. Sorry :(
diff --git a/include/freetype/ftautoh.h b/include/freetype/ftautoh.h
index cf7b76f..68d59e7 100644
--- a/include/freetype/ftautoh.h
+++ b/include/freetype/ftautoh.h
@@ -439,6 +439,36 @@ FT_BEGIN_HEADER
*/
+ /**************************************************************************
+ *
+ * @property:
+ * no-stem-darkening
+ *
+ * @description:
+ * *Experimental* *only*
+ *
+ * See the description of the CFF driver for details. Stem darkening is a
+ * side effect of using the auto-hinter unless setting this property to
+ * true.
+ *
+ */
+
+
+ /**************************************************************************
+ *
+ * @property:
+ * darkening-parameters
+ *
+ * @description:
+ * *Experimental* *only*
+ *
+ * See the description of the CFF driver for details. This implementation
+ * appropriates the CFF_CONFIG_OPTION_DARKENING_PARAMETER_* #defines for
+ * consistency.
+ *
+ */
+
+
/* */
diff --git a/src/autofit/afcjk.c b/src/autofit/afcjk.c
index 905408b..bfbe95d 100644
--- a/src/autofit/afcjk.c
+++ b/src/autofit/afcjk.c
@@ -247,6 +247,25 @@
FT_TRACE5(( "\n" ));
+ /* Grab the standard_width for the X-axis (i.e. vertical features) and
+ * use as standard_width of stems to compute darkening amount with,
+ * analogous to invoking cf2_computeDarkening() with stdVW.
+ *
+ * XXX: Since standard_width varies depending on what glyph is being
+ * displayed and therefore which script analyzer is invoked, always use the
+ * biggest value for the stem darkening computation. In my limited testing,
+ * this was usually near the "true" value.
+ *
+ * XXX: Values can be significantly bigger than what the stdVW PS dict
+ * entry says on well hinted OpenType/CFF fonts, resulting in less
+ * darkening and therefore less contrast. */
+ if ( metrics->root.globals->standard_vertical_width < metrics->axis[AF_DIMENSION_HORZ].standard_width )
+ {
+ metrics->root.globals->standard_vertical_width_old = metrics->root.globals->standard_vertical_width;
+ metrics->root.globals->standard_vertical_width =
+ metrics->axis[AF_DIMENSION_HORZ].standard_width;
+ }
+
af_glyph_hints_done( hints );
}
diff --git a/src/autofit/afglobal.c b/src/autofit/afglobal.c
index 64b9293..69314e1 100644
--- a/src/autofit/afglobal.c
+++ b/src/autofit/afglobal.c
@@ -323,6 +323,10 @@
globals->glyph_count = face->num_glyphs;
globals->glyph_styles = (FT_Byte*)( globals + 1 );
globals->module = module;
+ globals->stem_darkening_for_ppem = 0;
+ globals->darken_x = 0;
+ globals->standard_vertical_width = 0;
+ globals->standard_vertical_width_old = 0;
#ifdef FT_CONFIG_OPTION_USE_HARFBUZZ
globals->hb_font = hb_ft_font_create( face, NULL );
@@ -375,6 +379,10 @@
#endif
globals->glyph_count = 0;
+ globals->stem_darkening_for_ppem = 0;
+ globals->darken_x = 0;
+ globals->standard_vertical_width = 0;
+ globals->standard_vertical_width_old = 0;
globals->glyph_styles = NULL; /* no need to free this one! */
globals->face = NULL;
@@ -450,7 +458,6 @@
return error;
}
-
FT_LOCAL_DEF( FT_Bool )
af_face_globals_is_digit( AF_FaceGlobals globals,
FT_UInt gindex )
diff --git a/src/autofit/afglobal.h b/src/autofit/afglobal.h
index 9bbb687..4589433 100644
--- a/src/autofit/afglobal.h
+++ b/src/autofit/afglobal.h
@@ -111,6 +111,17 @@ FT_BEGIN_HEADER
AF_StyleMetrics metrics[AF_STYLE_MAX];
+ FT_UShort stem_darkening_for_ppem; /* Compute darkening amount once
+ per size. Use this to check if
+ darken_x needs to be
+ recomputed. */
+ FT_Pos darken_x;
+ FT_Pos standard_vertical_width; /* Copy from e.g.
+ AF_LatinMetrics.axis[AF_DIMENSION_HORZ],
+ to compute the darkening
+ amount. */
+ FT_Pos standard_vertical_width_old; /* Hack for less robust way of finding "true" width. */
+
AF_Module module; /* to access global properties */
} AF_FaceGlobalsRec;
diff --git a/src/autofit/aflatin.c b/src/autofit/aflatin.c
index 3065895..e8b13a5 100644
--- a/src/autofit/aflatin.c
+++ b/src/autofit/aflatin.c
@@ -251,6 +251,25 @@
FT_TRACE5(( "\n" ));
+ /* Grab the standard_width for the X-axis (i.e. vertical features) and
+ * use as standard_width of stems to compute darkening amount with,
+ * analogous to invoking cf2_computeDarkening() with stdVW.
+ *
+ * XXX: Since standard_width varies depending on what glyph is being
+ * displayed and therefore which script analyzer is invoked, always use the
+ * biggest value for the stem darkening computation. In my limited testing,
+ * this was usually near the "true" value.
+ *
+ * XXX: Values can be significantly bigger than what the stdVW PS dict
+ * entry says on well hinted OpenType/CFF fonts, resulting in less
+ * darkening and therefore less contrast. */
+ if ( metrics->root.globals->standard_vertical_width < metrics->axis[AF_DIMENSION_HORZ].standard_width )
+ {
+ metrics->root.globals->standard_vertical_width_old = metrics->root.globals->standard_vertical_width;
+ metrics->root.globals->standard_vertical_width =
+ metrics->axis[AF_DIMENSION_HORZ].standard_width;
+ }
+
af_glyph_hints_done( hints );
}
diff --git a/src/autofit/afloader.c b/src/autofit/afloader.c
index 7c2fa7c..8475c23 100644
--- a/src/autofit/afloader.c
+++ b/src/autofit/afloader.c
@@ -84,6 +84,7 @@
static FT_Error
af_loader_load_g( AF_Loader loader,
+ AF_Module module,
AF_Scaler scaler,
FT_UInt glyph_index,
FT_Int32 load_flags )
@@ -320,6 +321,37 @@
slot->format = FT_GLYPH_FORMAT_OUTLINE;
}
+ /* Apply stem darkening here. Darken only on the X axis, as the autohinter
+ * snaps to the Y axis. This matches the behavior of Adobe's proprietary
+ * TrueType engine.
+ *
+ * Note: The outline must be emboldened after it's loaded by
+ * FT_Load_Glyph(), but only in this function! Other occurences of
+ * FT_Load_Glyph in the writing system class implementations compute their
+ * hints using the Y axis, so emboldening does nothing there.
+ *
+ * First check if we already computed the darkening amount for the size we
+ * are given so the computation is done only once per size. Then check if
+ * standard_vertical_width has changed -- this can happen when for
+ * different glyphs, different writing class style analyzers run over the
+ * face testing different characters and coming up with different
+ * standard_vertical_widths. This means the computation may happen multiple
+ * times per size. In my limited testing, the biggest value was usually not
+ * too far off the mark except when it was. */
+ if ( !module->no_stem_darkening )
+ {
+ if ( face->size->metrics.x_ppem != loader->globals->stem_darkening_for_ppem
+ || loader->globals->standard_vertical_width
+ > loader->globals->standard_vertical_width_old)
+ {
+ af_loader_compute_darkening( loader, module );
+ loader->globals->standard_vertical_width_old =
+ loader->globals->standard_vertical_width;
+ }
+ if ( slot->format == FT_GLYPH_FORMAT_OUTLINE )
+ FT_Outline_EmboldenXY(&slot->outline, loader->globals->darken_x, 0);
+ }
+
Exit:
return error;
}
@@ -396,7 +428,7 @@
goto Exit;
}
- error = af_loader_load_g( loader, &scaler, gindex, load_flags );
+ error = af_loader_load_g( loader, module, &scaler, gindex, load_flags );
}
}
Exit:
@@ -404,4 +436,138 @@
}
+ /* Compute amount of pixels the face should be emboldened by, analogous to
+ * the CFF driver's cf2_computeDarkening(). See there for details of the
+ * algorithm.
+ * XXX: Currently a crude adaption of the original algorithm. Do better?
+ * XXX: what about the emboldening effect in cf2_computeDarkening()? */
+ FT_LOCAL_DEF( void )
+ af_loader_compute_darkening( AF_Loader loader,
+ AF_Module module )
+ {
+ #define cf2_intToFixed( i ) ( (FT_Int32)( (FT_UInt32)(i) << 16 ) )
+ #define cf2_fixedToInt( x ) ( (FT_Short)( ( (FT_UInt32)(x) + 0x8000U ) >> 16 ) )
+ #define cf2_floatToFixed( f ) ( (FT_Int32)( (f) * 65536.0 + 0.5 ) )
+ FT_UShort units_per_EM;
+ FT_Int32 ppem, emRatio;
+ FT_Int32 stemWidth, stemWidthPer1000, scaledStem, darkenAmount;
+ FT_Int logBase2;
+ FT_Int x1, y1, x2, y2, x3, y3, x4, y4;
+ FT_Pos stdVW;
+
+ ppem = cf2_intToFixed(loader->face->size->metrics.x_ppem);
+ ppem = FT_MAX( cf2_intToFixed( 4 ), ppem );
+ units_per_EM = loader->face->units_per_EM;
+
+ if ( units_per_EM == 0 )
+ units_per_EM = 1000;
+
+ emRatio = cf2_intToFixed( 1000 ) / units_per_EM;
+ if ( emRatio < cf2_floatToFixed( .01 ) )
+ {
+ /* If something goes wrong, silently disable darkening for the size. */
+ loader->globals->darken_x = 0;
+ loader->globals->stem_darkening_for_ppem = loader->globals->face->size->metrics.x_ppem;
+ return;
+ }
+
+ x1 = module->darken_params[0];
+ y1 = module->darken_params[1];
+ x2 = module->darken_params[2];
+ y2 = module->darken_params[3];
+ x3 = module->darken_params[4];
+ y3 = module->darken_params[5];
+ x4 = module->darken_params[6];
+ y4 = module->darken_params[7];
+
+ stdVW = loader->globals->standard_vertical_width;
+ if ( stdVW <= 0)
+ {
+ stemWidth = cf2_intToFixed( 75 ); /* Taken from cf2font.c */
+ stemWidthPer1000 = stemWidth;
+ }
+ else
+ {
+ stemWidth = cf2_intToFixed( stdVW );
+ stemWidthPer1000 = FT_MulFix( stemWidth, emRatio );
+ }
+
+ logBase2 = FT_MSB( (FT_UInt32)stemWidthPer1000 ) +
+ FT_MSB( (FT_UInt32)ppem );
+
+ if ( logBase2 >= 46 )
+ /* possible overflow */
+ scaledStem = cf2_intToFixed( x4 );
+ else
+ scaledStem = FT_MulFix( stemWidthPer1000, ppem );
+
+ /* now apply the darkening parameters */
+ if ( scaledStem < cf2_intToFixed( x1 ) )
+ darkenAmount = FT_DivFix( cf2_intToFixed( y1 ), ppem );
+
+ else if ( scaledStem < cf2_intToFixed( x2 ) )
+ {
+ FT_Int xdelta = x2 - x1;
+ FT_Int ydelta = y2 - y1;
+ FT_Int x = stemWidthPer1000 -
+ FT_DivFix( cf2_intToFixed( x1 ), ppem );
+
+
+ if ( !xdelta )
+ goto Try_x3;
+
+ darkenAmount = FT_MulDiv( x, ydelta, xdelta ) +
+ FT_DivFix( cf2_intToFixed( y1 ), ppem );
+ }
+
+ else if ( scaledStem < cf2_intToFixed( x3 ) )
+ {
+Try_x3:
+ {
+ FT_Int xdelta = x3 - x2;
+ FT_Int ydelta = y3 - y2;
+ FT_Int x = stemWidthPer1000 -
+ FT_DivFix( cf2_intToFixed( x2 ), ppem );
+
+
+ if ( !xdelta )
+ goto Try_x4;
+
+ darkenAmount = FT_MulDiv( x, ydelta, xdelta ) +
+ FT_DivFix( cf2_intToFixed( y2 ), ppem );
+ }
+ }
+
+ else if ( scaledStem < cf2_intToFixed( x4 ) )
+ {
+Try_x4:
+ {
+ FT_Int xdelta = x4 - x3;
+ FT_Int ydelta = y4 - y3;
+ FT_Int x = stemWidthPer1000 -
+ FT_DivFix( cf2_intToFixed( x3 ), ppem );
+
+
+ if ( !xdelta )
+ goto Use_y4;
+
+ darkenAmount = FT_MulDiv( x, ydelta, xdelta ) +
+ FT_DivFix( cf2_intToFixed( y3 ), ppem );
+ }
+ }
+
+ else
+ {
+Use_y4:
+ darkenAmount = FT_DivFix( cf2_intToFixed( y4 ), ppem );
+ }
+
+ /* convert back to true character space. */
+ darkenAmount = FT_DivFix( darkenAmount, emRatio * 2 );
+
+ loader->globals->darken_x = cf2_fixedToInt(darkenAmount);
+ loader->globals->stem_darkening_for_ppem = loader->globals->face->size->metrics.x_ppem;
+ return;
+ }
+
/* END */
diff --git a/src/autofit/afloader.h b/src/autofit/afloader.h
index 37cfd14..3c952de 100644
--- a/src/autofit/afloader.h
+++ b/src/autofit/afloader.h
@@ -75,6 +75,10 @@ FT_BEGIN_HEADER
FT_UInt gindex,
FT_Int32 load_flags );
+ FT_LOCAL_DEF( void )
+ af_loader_compute_darkening( AF_Loader loader,
+ AF_Module module );
+
/* */
diff --git a/src/autofit/afmodule.c b/src/autofit/afmodule.c
index 8ae425c..614b118 100644
--- a/src/autofit/afmodule.c
+++ b/src/autofit/afmodule.c
@@ -169,6 +169,45 @@
return error;
}
#endif /* AF_CONFIG_OPTION_USE_WARPER */
+ else if ( !ft_strcmp( property_name, "darkening-parameters" ) )
+ {
+ FT_Int* darken_params = (FT_Int*)value;
+
+ FT_Int x1 = darken_params[0];
+ FT_Int y1 = darken_params[1];
+ FT_Int x2 = darken_params[2];
+ FT_Int y2 = darken_params[3];
+ FT_Int x3 = darken_params[4];
+ FT_Int y3 = darken_params[5];
+ FT_Int x4 = darken_params[6];
+ FT_Int y4 = darken_params[7];
+
+ if ( x1 < 0 || x2 < 0 || x3 < 0 || x4 < 0 ||
+ y1 < 0 || y2 < 0 || y3 < 0 || y4 < 0 ||
+ x1 > x2 || x2 > x3 || x3 > x4 ||
+ y1 > 500 || y2 > 500 || y3 > 500 || y4 > 500 )
+ return FT_THROW( Invalid_Argument );
+
+ module->darken_params[0] = x1;
+ module->darken_params[1] = y1;
+ module->darken_params[2] = x2;
+ module->darken_params[3] = y2;
+ module->darken_params[4] = x3;
+ module->darken_params[5] = y3;
+ module->darken_params[6] = x4;
+ module->darken_params[7] = y4;
+
+ return error;
+ }
+ else if ( !ft_strcmp( property_name, "no-stem-darkening" ) )
+ {
+ FT_Bool* no_stem_darkening = (FT_Bool*)value;
+
+
+ module->no_stem_darkening = *no_stem_darkening;
+
+ return error;
+ }
FT_TRACE0(( "af_property_set: missing property `%s'\n",
property_name ));
@@ -245,6 +284,33 @@
return error;
}
#endif /* AF_CONFIG_OPTION_USE_WARPER */
+ else if ( !ft_strcmp( property_name, "darkening-parameters" ) )
+ {
+ FT_Int* darken_params = module->darken_params;
+ FT_Int* val = (FT_Int*)value;
+
+
+ val[0] = darken_params[0];
+ val[1] = darken_params[1];
+ val[2] = darken_params[2];
+ val[3] = darken_params[3];
+ val[4] = darken_params[4];
+ val[5] = darken_params[5];
+ val[6] = darken_params[6];
+ val[7] = darken_params[7];
+
+ return error;
+ }
+ else if ( !ft_strcmp( property_name, "no-stem-darkening" ) )
+ {
+ FT_Bool no_stem_darkening = module->no_stem_darkening;
+ FT_Bool* val = (FT_Bool*)value;
+
+
+ *val = no_stem_darkening;
+
+ return error;
+ }
FT_TRACE0(( "af_property_get: missing property `%s'\n",
property_name ));
@@ -296,6 +362,16 @@
#ifdef AF_CONFIG_OPTION_USE_WARPER
module->warping = 0;
#endif
+ module->no_stem_darkening = FALSE;
+
+ module->darken_params[0] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X1;
+ module->darken_params[1] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y1;
+ module->darken_params[2] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X2;
+ module->darken_params[3] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y2;
+ module->darken_params[4] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X3;
+ module->darken_params[5] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y3;
+ module->darken_params[6] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_X4;
+ module->darken_params[7] = CFF_CONFIG_OPTION_DARKENING_PARAMETER_Y4;
return FT_Err_Ok;
}
diff --git a/src/autofit/afmodule.h b/src/autofit/afmodule.h
index b9c2fd8..d23018a 100644
--- a/src/autofit/afmodule.h
+++ b/src/autofit/afmodule.h
@@ -41,7 +41,8 @@ FT_BEGIN_HEADER
#ifdef AF_CONFIG_OPTION_USE_WARPER
FT_Bool warping;
#endif
-
+ FT_Bool no_stem_darkening;
+ FT_Int darken_params[8];
} AF_ModuleRec, *AF_Module;
_______________________________________________
Freetype-devel mailing list
Freetype-devel@nongnu.org
https://lists.nongnu.org/mailman/listinfo/freetype-devel