From 4d3876eef9f90a75f2ae55e880759937482e6b3f Mon Sep 17 00:00:00 2001 From: Steve Kondik Date: Tue, 28 Aug 2012 22:24:56 -0700 Subject: freetype: Add Infinality patches * See here for details: * http://www.infinality.net/blog/infinality-freetype-patches/ Change-Id: I4f747e45332fc7f16fb22ac3499069519017035f --- Android.mk | 2 +- include/freetype/config/ftoption.h | 35 +- include/freetype/internal/ftobjs.h | 8 + src/autofit/aflatin.c | 233 ++- src/base/ftlcdfil.c | 46 +- src/base/ftobjs.c | 69 +- src/base/ftoutln.c | 22 + src/base/ftsynth.c | 27 + src/smooth/ftsmooth.c | 3499 +++++++++++++++++++++++++++++++++++- src/truetype/truetype.c | 1 + src/truetype/ttgload.c | 148 +- src/truetype/ttinterp.c | 825 ++++++++- src/truetype/ttinterp.h | 72 +- src/truetype/ttobjs.h | 3 +- src/truetype/ttsubpix.c | 261 +++ src/truetype/ttsubpix.h | 778 ++++++++ 16 files changed, 5851 insertions(+), 178 deletions(-) create mode 100644 src/truetype/ttsubpix.c create mode 100644 src/truetype/ttsubpix.h diff --git a/Android.mk b/Android.mk index 51ae31b..89fa12b 100644 --- a/Android.mk +++ b/Android.mk @@ -46,7 +46,7 @@ LOCAL_CFLAGS += "-DFT2_BUILD_LIBRARY" # the following is for testing only, and should not be used in final builds # of the product -#LOCAL_CFLAGS += "-DTT_CONFIG_OPTION_BYTECODE_INTERPRETER" +LOCAL_CFLAGS += "-DTT_CONFIG_OPTION_BYTECODE_INTERPRETER" LOCAL_CFLAGS += -O2 diff --git a/include/freetype/config/ftoption.h b/include/freetype/config/ftoption.h index 4414cb0..e4b99c2 100644 --- a/include/freetype/config/ftoption.h +++ b/include/freetype/config/ftoption.h @@ -92,7 +92,7 @@ FT_BEGIN_HEADER /* This is done to allow FreeType clients to run unmodified, forcing */ /* them to display normal gray-level anti-aliased glyphs. */ /* */ -/* #define FT_CONFIG_OPTION_SUBPIXEL_RENDERING */ +#define FT_CONFIG_OPTION_SUBPIXEL_RENDERING /*************************************************************************/ @@ -558,6 +558,39 @@ FT_BEGIN_HEADER /* #define TT_CONFIG_OPTION_BYTECODE_INTERPRETER */ + /*************************************************************************/ + /* */ + /* Define TT_CONFIG_OPTION_SUBPIXEL_HINTING if you want to compile */ + /* EXPERIMENTAL subpixel hinting support into the TrueType driver. This */ + /* replaces the native TrueType hinting mechanism when anything but */ + /* FT_RENDER_MODE_MONO is requested. */ + /* */ + /* Enabling this causes the TrueType driver to ignore instructions under */ + /* certain conditions. This is done in accordance with the guide here, */ + /* with some minor differences: */ + /* */ + /* http://www.microsoft.com/typography/cleartype/truetypecleartype.aspx */ + /* */ + /* By undefining this, you only compile the code necessary to hint */ + /* TrueType glyphs with native TT hinting. */ + /* */ + /* This option requires TT_CONFIG_OPTION_BYTECODE_INTERPRETER to be */ + /* defined. */ + /* */ +#define TT_CONFIG_OPTION_SUBPIXEL_HINTING + + + /*************************************************************************/ + /* */ + /* Define FT_CONFIG_OPTION_INFINALITY_PATCHSET if you want to enable */ + /* all additional infinality patches, which are configured via env */ + /* variables. */ + /* */ + /* This option requires TT_CONFIG_OPTION_SUBPIXEL_HINTING to */ + /* defined. */ + /* */ +#define FT_CONFIG_OPTION_INFINALITY_PATCHSET + /*************************************************************************/ /* */ /* If you define TT_CONFIG_OPTION_UNPATENTED_HINTING, a special version */ diff --git a/include/freetype/internal/ftobjs.h b/include/freetype/internal/ftobjs.h index eee3d24..d4da5da 100644 --- a/include/freetype/internal/ftobjs.h +++ b/include/freetype/internal/ftobjs.h @@ -81,6 +81,14 @@ FT_BEGIN_HEADER #define FT_PIX_ROUND( x ) FT_PIX_FLOOR( (x) + 32 ) #define FT_PIX_CEIL( x ) FT_PIX_FLOOR( (x) + 63 ) + /* + * These are used in ttinterp.c for subpixel hinting with an + * adjustable grids-per-pixel value. + */ +#define FT_PIX_FLOOR_GRID( x, n ) ( (x) & ~( 64 / (n) - 1 ) ) +#define FT_PIX_ROUND_GRID( x, n ) FT_PIX_FLOOR_GRID( (x) + 32 / (n), (n) ) +#define FT_PIX_CEIL_GRID( x, n ) FT_PIX_FLOOR_GRID( (x) + 63 / (n), (n) ) + /* * Return the highest power of 2 that is <= value; this correspond to diff --git a/src/autofit/aflatin.c b/src/autofit/aflatin.c index 0fd3045..fdf2dd2 100644 --- a/src/autofit/aflatin.c +++ b/src/autofit/aflatin.c @@ -22,6 +22,7 @@ #include "aflatin.h" #include "aferrors.h" +#include "strings.h" #ifdef AF_CONFIG_OPTION_USE_WARPER @@ -528,7 +529,30 @@ FT_Pos delta; AF_LatinAxis axis; FT_UInt nn; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int checked_adjust_heights_env = 0; + FT_Bool adjust_heights = FALSE; + if ( checked_adjust_heights_env == 0 ) + { + char *adjust_heights_env = getenv( "INFINALITY_FT_AUTOHINT_INCREASE_GLYPH_HEIGHTS" ); + if ( adjust_heights_env != NULL ) + { + if ( strcasecmp(adjust_heights_env, "default" ) != 0 ) + { + if ( strcasecmp(adjust_heights_env, "true") == 0) + adjust_heights = TRUE; + else if ( strcasecmp(adjust_heights_env, "1") == 0) + adjust_heights = TRUE; + else if ( strcasecmp(adjust_heights_env, "on") == 0) + adjust_heights = TRUE; + else if ( strcasecmp(adjust_heights_env, "yes") == 0) + adjust_heights = TRUE; + } + } + checked_adjust_heights_env = 1; + } +#endif if ( dim == AF_DIMENSION_HORZ ) { @@ -556,7 +580,7 @@ { AF_LatinAxis Axis = &metrics->axis[AF_DIMENSION_VERT]; AF_LatinBlue blue = NULL; - + int threshold = 40; for ( nn = 0; nn < Axis->blue_count; nn++ ) { @@ -566,12 +590,16 @@ break; } } - +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( adjust_heights + && metrics->root.scaler.face->size->metrics.x_ppem < 15 + && metrics->root.scaler.face->size->metrics.x_ppem > 5 ) + threshold = 52; +#endif if ( blue ) { FT_Pos scaled = FT_MulFix( blue->shoot.org, scaler->y_scale ); - FT_Pos fitted = ( scaled + 40 ) & ~63; - + FT_Pos fitted = ( scaled + threshold ) & ~63; if ( scaled != fitted ) { @@ -626,7 +654,6 @@ AF_LatinBlue blue = &axis->blues[nn]; FT_Pos dist; - blue->ref.cur = FT_MulFix( blue->ref.org, scale ) + delta; blue->ref.fit = blue->ref.cur; blue->shoot.cur = FT_MulFix( blue->shoot.org, scale ) + delta; @@ -635,7 +662,12 @@ /* a blue zone is only active if it is less than 3/4 pixels tall */ dist = FT_MulFix( blue->ref.org - blue->shoot.org, scale ); + +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + /* Do always, in order to prevent fringes */ +#else if ( dist <= 48 && dist >= -48 ) +#endif { #if 0 FT_Pos delta1; @@ -686,7 +718,12 @@ delta2 = -delta2; blue->ref.fit = FT_PIX_ROUND( blue->ref.cur ); +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + /* Round to prevent fringes */ + blue->shoot.fit = FT_PIX_ROUND( blue->ref.fit - delta2 ); +#else blue->shoot.fit = blue->ref.fit - delta2; +#endif #endif @@ -1433,6 +1470,8 @@ if ( dist < 0 ) dist = -dist; + /* round down to pixels */ + /*dist = FT_MulFix( dist, scale ) & ~63;*/ dist = FT_MulFix( dist, scale ); if ( dist < best_dist ) { @@ -1595,20 +1634,95 @@ FT_Pos dist = width; FT_Int sign = 0; FT_Int vertical = ( dim == AF_DIMENSION_VERT ); +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + FT_Int infinality_dist = 0; - if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) || - axis->extra_light ) - return width; + + FT_UInt autohint_snap_stem_height = 0; + FT_UInt checked_autohint_snap_stem_height = 0; + + if ( checked_autohint_snap_stem_height == 0) + { + char *autohint_snap_stem_height_env = getenv( "INFINALITY_FT_AUTOHINT_SNAP_STEM_HEIGHT" ); + if ( autohint_snap_stem_height_env != NULL ) + { + sscanf ( autohint_snap_stem_height_env, "%u", &autohint_snap_stem_height ); + if (autohint_snap_stem_height > 100 ) autohint_snap_stem_height = 100; + else if (autohint_snap_stem_height < 0 ) autohint_snap_stem_height = 0; + } + checked_autohint_snap_stem_height = 1; + } + + if ( autohint_snap_stem_height == 0 ) +#endif + if ( !AF_LATIN_HINTS_DO_STEM_ADJUST( hints ) || + axis->extra_light ) + return width; if ( dist < 0 ) { dist = -width; sign = 1; } +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + /* Calculate snap value differently than standard freetype */ + if ( /* stem_snap_light*/ autohint_snap_stem_height > 0 + && ( + ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) + || ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) ) ) + { + infinality_dist = af_latin_snap_width( axis->widths, axis->width_count, dist ); + + if ( metrics->root.scaler.face->size->metrics.x_ppem > 9 + && axis->width_count > 0 + && abs ( axis->widths[0].cur - infinality_dist ) < 32 + && axis->widths[0].cur > 52 ) + { + if ( strstr(metrics->root.scaler.face->style_name, "Regular") + || strstr(metrics->root.scaler.face->style_name, "Book") + || strstr(metrics->root.scaler.face->style_name, "Medium") + || strcmp(metrics->root.scaler.face->style_name, "Italic") == 0 + || strcmp(metrics->root.scaler.face->style_name, "Oblique") == 0 ) + { + /* regular weight */ + if ( axis->widths[0].cur < 64 ) infinality_dist = 64 ; + else if (axis->widths[0].cur < 88) infinality_dist = 64; + else if (axis->widths[0].cur < 160) infinality_dist = 128; + else if (axis->widths[0].cur < 240) infinality_dist = 190; + else infinality_dist = ( infinality_dist ) & ~63; + } + else + { + /* bold gets a different threshold */ + if ( axis->widths[0].cur < 64 ) infinality_dist = 64 ; + else if (axis->widths[0].cur < 108) infinality_dist = 64; + else if (axis->widths[0].cur < 160) infinality_dist = 128; + else if (axis->widths[0].cur < 222) infinality_dist = 190; + else if (axis->widths[0].cur < 288) infinality_dist = 254; + else infinality_dist = ( infinality_dist + 16 ) & ~63; + } + + } + if (infinality_dist < 52) + { + if (metrics->root.scaler.face->size->metrics.x_ppem < 9 ) + { + + if (infinality_dist < 32) infinality_dist = 32; + } + else + infinality_dist = 64; + } + } + else if ( autohint_snap_stem_height < 100 + && (( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) || + ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) ) ) +#else - if ( ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) || - ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) ) + if ( ( vertical && !AF_LATIN_HINTS_DO_VERT_SNAP( hints ) ) || + ( !vertical && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) ) +#endif { /* smooth hinting process: very lightly quantize the stem width */ @@ -1667,7 +1781,10 @@ dist = ( dist + 32 ) & ~63; } } - else + else +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( autohint_snap_stem_height < 100 ) +#endif { /* strong hinting process: snap the stem width to integer pixels */ @@ -1675,7 +1792,9 @@ dist = af_latin_snap_width( axis->widths, axis->width_count, dist ); - +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( autohint_snap_stem_height > 0 ) goto Done_Width; +#endif if ( vertical ) { /* in the case of vertical hinting, always round */ @@ -1738,6 +1857,23 @@ } Done_Width: +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if (axis->widths[0].cur > 42 ) + /* weighted average */ + dist = (dist * (100 - autohint_snap_stem_height) + infinality_dist * autohint_snap_stem_height ) / 100; + + { + int factor = 100; + if (axis->standard_width < 100) + factor = axis->standard_width; + + if (metrics->root.scaler.face->size->metrics.x_ppem >=9 && dist < 52 ) dist += ((52 - dist) * factor) / 100; + if (metrics->root.scaler.face->size->metrics.x_ppem <9 && dist < 32 ) dist += ((32 - dist) * factor) / 100; + + if (axis->standard_width > 100 && metrics->root.scaler.face->size->metrics.x_ppem >=11 && dist < 64 ) dist = 64; + if (axis->standard_width > 100 && metrics->root.scaler.face->size->metrics.x_ppem >=9 && dist < 52 ) dist = 52; + } +#endif if ( sign ) dist = -dist; @@ -1760,6 +1896,8 @@ (AF_Edge_Flags)base_edge->flags, (AF_Edge_Flags)stem_edge->flags ); +/* if fitted_width causes stem_edge->pos to land basically on top of an existing + * stem_edge->pos, then add or remove 64. Need to figure out a way to do this */ stem_edge->pos = base_edge->pos + fitted_width; @@ -2234,8 +2372,32 @@ { FT_Error error; int dim; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int emboldening_strength = 0; + int checked_use_various_tweaks_env = 0; + FT_Bool use_various_tweaks = FALSE; + if ( checked_use_various_tweaks_env == 0 ) + { + char *use_various_tweaks_env = getenv( "INFINALITY_FT_USE_VARIOUS_TWEAKS" ); + if ( use_various_tweaks_env != NULL ) + { + if ( strcasecmp(use_various_tweaks_env, "default" ) != 0 ) + { + if ( strcasecmp(use_various_tweaks_env, "true") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "1") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "on") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "yes") == 0) + use_various_tweaks = TRUE; + } + } + checked_use_various_tweaks_env = 1; + } +#endif error = af_glyph_hints_reload( hints, outline ); if ( error ) goto Exit; @@ -2292,7 +2454,54 @@ } } af_glyph_hints_save( hints, outline ); +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET +#if 0 + if( metrics->root.scaler.face->style_name ) + { + if ( strcasestr(metrics->root.scaler.face->style_name, "Bold") + || strcasestr(metrics->root.scaler.face->style_name, "Black") + || strcasestr(metrics->root.scaler.face->style_name, "Narrow") + && metrics->root.scaler.face->size->metrics.x_ppem < 15 + || strcasestr(metrics->root.scaler.face->style_name, "Condensed") + && metrics->root.scaler.face->size->metrics.x_ppem < 20 ) + goto Exit; + } + if( metrics->root.scaler.face->family_name ) + { + if ( strcasestr(metrics->root.scaler.face->family_name, "Bold") + || strcasestr(metrics->root.scaler.face->family_name, "Black") + || strcasestr(metrics->root.scaler.face->family_name, "Narrow") + && metrics->root.scaler.face->size->metrics.x_ppem < 15 + || strcasestr(metrics->root.scaler.face->family_name, "Condensed") + && metrics->root.scaler.face->size->metrics.x_ppem < 20 ) + goto Exit; + } + /* if the font is particularly thin, embolden it, up to 1 px */ + if ( use_various_tweaks + && metrics->axis->widths[0].cur <= FT_MulDiv ( autohint_minimum_stem_width, 64, 100) + && !( dim == AF_DIMENSION_VERT ) + && !AF_LATIN_HINTS_DO_HORZ_SNAP( hints ) ) + { + if ( metrics->axis->widths[0].cur + / metrics->root.scaler.face->size->metrics.x_ppem < 5 ) + { + emboldening_strength = FT_MulDiv ( autohint_minimum_stem_width, 64, 100) - metrics->axis->widths[0].cur; + if ( metrics->root.scaler.face->size->metrics.x_ppem < 9 ) + emboldening_strength -= 10; + if ( metrics->root.scaler.face->size->metrics.x_ppem < 7 ) + emboldening_strength -= 10; + } + if ( emboldening_strength < 0 ) emboldening_strength = 0; + FT_Outline_Embolden(outline,emboldening_strength); + } +#endif + /* Save this width for use in ftsmooth.c. This is a shameful hack */ + const char* c1 = "CUR_WIDTH"; + char c2[8]; + snprintf(c2,8,"%ld",metrics->axis->widths[0].cur); + setenv(c1, c2, 1); +#endif Exit: return error; } diff --git a/src/base/ftlcdfil.c b/src/base/ftlcdfil.c index 0da4ba1..09a9551 100644 --- a/src/base/ftlcdfil.c +++ b/src/base/ftlcdfil.c @@ -21,6 +21,9 @@ #include FT_IMAGE_H #include FT_INTERNAL_OBJECTS_H +#include +#include +#include #ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING @@ -287,10 +290,51 @@ { 0x00, 0x55, 0x56, 0x55, 0x00 }; /* the values here sum up to a value larger than 256, */ /* providing a cheap gamma correction */ - static const FT_Byte default_filter[5] = + static FT_Byte default_filter[5] = { 0x10, 0x40, 0x70, 0x40, 0x10 }; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int checked_filter_params_env = 0; + if ( checked_filter_params_env == 0 ) + { + char *filter_params = getenv( "INFINALITY_FT_FILTER_PARAMS" ); + if ( filter_params != NULL && strcmp(filter_params, "") != 0 ) + { + float f1, f2, f3, f4, f5; + if ( strcasecmp(filter_params, "default" ) != 0) + { + int args_assigned = 0; + args_assigned = sscanf ( filter_params, "%f %f %f %f %f", &f1, &f2, &f3, &f4, &f5 ); + + if ( args_assigned == 5 ) + { + if ( f1 + f2 + f3 + f4 + f5 > 5 ) + { + /* Assume we were given integers instead of floats */ + /* 0 to 100 */ + default_filter[0] = (FT_Byte) (f1 * 2.55f + 0.5f); + default_filter[1] = (FT_Byte) (f2 * 2.55f + 0.5f); + default_filter[2] = (FT_Byte) (f3 * 2.55f + 0.5f); + default_filter[3] = (FT_Byte) (f4 * 2.55f + 0.5f); + default_filter[4] = (FT_Byte) (f5 * 2.55f + 0.5f); + } + else + { + /* Assume we were given floating point values */ + /* 0 to 1.0 */ + default_filter[0] = (FT_Byte) (f1 * 255.0f + 0.5f); + default_filter[1] = (FT_Byte) (f2 * 255.0f + 0.5f); + default_filter[2] = (FT_Byte) (f3 * 255.0f + 0.5f); + default_filter[3] = (FT_Byte) (f4 * 255.0f + 0.5f); + default_filter[4] = (FT_Byte) (f5 * 255.0f + 0.5f); + } + } + } + } + checked_filter_params_env = 1; + } +#endif if ( !library ) return FT_Err_Invalid_Argument; diff --git a/src/base/ftobjs.c b/src/base/ftobjs.c index 36ee797..66a199a 100644 --- a/src/base/ftobjs.c +++ b/src/base/ftobjs.c @@ -513,6 +513,22 @@ ft_lookup_glyph_renderer( FT_GlyphSlot slot ); +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + static void + ft_glyphslot_enlarge_metrics( FT_GlyphSlot slot, + FT_Render_Mode mode ) + { + FT_Glyph_Metrics* metrics = &slot->metrics; + FT_Pos enlarge_cbox = 0; + /* enlarge for grayscale rendering */ + if ( mode == FT_RENDER_MODE_NORMAL ) enlarge_cbox = 64; + + metrics->horiBearingX -= enlarge_cbox; + metrics->width += 2*enlarge_cbox; + } +#endif /* FT_CONFIG_OPTION_INFINALITY_PATCHSET */ + + #ifdef GRID_FIT_METRICS static void ft_glyphslot_grid_fit_metrics( FT_GlyphSlot slot, @@ -571,8 +587,33 @@ FT_Bool autohint = FALSE; FT_Module hinter; TT_Face ttface = (TT_Face)face; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int checked_use_various_tweaks_env = FALSE; + FT_Bool use_various_tweaks = FALSE; + + if ( !checked_use_various_tweaks_env ) + { + char *use_various_tweaks_env = getenv( "INFINALITY_FT_USE_VARIOUS_TWEAKS" ); + if ( use_various_tweaks_env != NULL ) + { + if ( strcasecmp(use_various_tweaks_env, "default" ) != 0 ) + { + if ( strcasecmp(use_various_tweaks_env, "true") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "1") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "on") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "yes") == 0) use_various_tweaks = TRUE; + } + } + checked_use_various_tweaks_env = 1; + } + /* Force autohint if no tt instructions */ + if ( use_various_tweaks && + ttface->num_locations && + ttface->max_profile.maxSizeOfInstructions == 0 ) + load_flags |= FT_LOAD_FORCE_AUTOHINT; +#endif if ( !face || !face->size || !face->glyph ) return FT_Err_Invalid_Face_Handle; @@ -652,8 +693,18 @@ if ( autohint ) { FT_AutoHinter_Service hinting; - - +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( use_various_tweaks ) + { + /* Force slight hinting over full hinting always */ + load_flags &= ~FT_LOAD_TARGET_LCD; + load_flags &= ~FT_LOAD_TARGET_LCD_V; + load_flags &= ~FT_LOAD_TARGET_MONO; + load_flags &= ~FT_LOAD_TARGET_NORMAL; + load_flags |= FT_LOAD_TARGET_LIGHT; + /*printf("%d ", load_flags);*/ + } +#endif /* try to load embedded bitmaps first if available */ /* */ /* XXX: This is really a temporary hack that should disappear */ @@ -691,6 +742,10 @@ } else { +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + char* c1 = "CUR_WIDTH"; + char* c2 = "0"; +#endif error = driver->clazz->load_glyph( slot, face->size, glyph_index, @@ -698,6 +753,16 @@ if ( error ) goto Exit; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + setenv(c1, c2, 1); + + { + /* fix for sdl_ttf */ + FT_Render_Mode mode = FT_LOAD_TARGET_MODE( load_flags ); + ft_glyphslot_enlarge_metrics( slot, mode ); + } +#endif + if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) { /* check that the loaded outline is correct */ diff --git a/src/base/ftoutln.c b/src/base/ftoutln.c index 76e2b04..fa4ee0d 100644 --- a/src/base/ftoutln.c +++ b/src/base/ftoutln.c @@ -897,7 +897,29 @@ FT_Vector v_prev, v_first, v_next, v_cur; FT_Int c, n, first; FT_Int orientation; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int checked_use_various_tweaks_env = 0; + FT_Bool use_various_tweaks = FALSE; + if ( checked_use_various_tweaks_env == 0 ) + { + char *use_various_tweaks_env = getenv( "INFINALITY_FT_USE_VARIOUS_TWEAKS" ); + if ( use_various_tweaks_env != NULL ) + { + if ( strcasecmp(use_various_tweaks_env, "default" ) != 0 ) + { + if ( strcasecmp(use_various_tweaks_env, "true") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "1") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "on") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "yes") == 0) use_various_tweaks = TRUE; + } + } + checked_use_various_tweaks_env = 1; + } + + if ( use_various_tweaks ) + ystrength = FT_PIX_FLOOR ( ystrength ); +#endif if ( !outline ) return FT_Err_Invalid_Argument; diff --git a/src/base/ftsynth.c b/src/base/ftsynth.c index 81e2ed2..b186d8d 100644 --- a/src/base/ftsynth.c +++ b/src/base/ftsynth.c @@ -88,7 +88,26 @@ FT_Face face = slot->face; FT_Error error; FT_Pos xstr, ystr; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + int checked_use_various_tweaks_env = 0; + FT_Bool use_various_tweaks = FALSE; + if ( checked_use_various_tweaks_env == 0 ) + { + char *use_various_tweaks_env = getenv( "INFINALITY_FT_USE_VARIOUS_TWEAKS" ); + if ( use_various_tweaks_env != NULL ) + { + if ( strcasecmp(use_various_tweaks_env, "default" ) != 0 ) + { + if ( strcasecmp(use_various_tweaks_env, "true") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "1") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "on") == 0) use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "yes") == 0) use_various_tweaks = TRUE; + } + } + checked_use_various_tweaks_env = 1; + } +#endif if ( slot->format != FT_GLYPH_FORMAT_OUTLINE && slot->format != FT_GLYPH_FORMAT_BITMAP ) @@ -101,6 +120,11 @@ if ( slot->format == FT_GLYPH_FORMAT_OUTLINE ) { +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( use_various_tweaks ) + (void)FT_Outline_EmboldenXY( &slot->outline, xstr, FT_PIX_FLOOR( ystr ) ); + else +#endif /* ignore error */ (void)FT_Outline_EmboldenXY( &slot->outline, xstr, ystr ); } @@ -141,6 +165,9 @@ slot->metrics.width += xstr; slot->metrics.height += ystr; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + /*if ( !use_various_tweaks ) */ +#endif slot->metrics.horiAdvance += xstr; slot->metrics.vertAdvance += ystr; diff --git a/src/smooth/ftsmooth.c b/src/smooth/ftsmooth.c index 00499cc..a628dff 100644 --- a/src/smooth/ftsmooth.c +++ b/src/smooth/ftsmooth.c @@ -26,6 +26,16 @@ #include "ftsmerrs.h" +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET +#include +#include "../../include/freetype/ftbitmap.h" +#include "strings.h" +#include "../autofit/aflatin.h" +#include "../../include/freetype/ftoutln.h" + +#define verbose FALSE +#define STVALUES if (verbose) printf ("scale:%f translate:%ld ", *scale_value, *translate_value); +#endif /* initialize renderer -- init its raster */ static FT_Error @@ -64,35 +74,2792 @@ FT_Error error = Smooth_Err_Ok; - if ( slot->format != render->glyph_format ) - { - error = Smooth_Err_Invalid_Argument; + if ( slot->format != render->glyph_format ) + { + error = Smooth_Err_Invalid_Argument; + goto Exit; + } + + if ( matrix ) + FT_Outline_Transform( &slot->outline, matrix ); + + if ( delta ) + FT_Outline_Translate( &slot->outline, delta->x, delta->y ); + + Exit: + return error; + } + + + /* return the glyph's control box */ + static void + ft_smooth_get_cbox( FT_Renderer render, + FT_GlyphSlot slot, + FT_BBox* cbox ) + { + FT_MEM_ZERO( cbox, sizeof ( *cbox ) ); + + if ( slot->format == render->glyph_format ) + FT_Outline_Get_CBox( &slot->outline, cbox ); + } + +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + static FT_Fixed FT_FixedFromFloat(float f) + { + short value = f; + unsigned short fract = (f - value) * 0xFFFF; + return (FT_Fixed)((long)value << 16 | (unsigned long)fract); + } + + + /* ChromeOS sharpening algorithm */ + /* soften the sub-pixel anti-aliasing and sharpen */ + static void + _ft_lcd_chromeos_sharpen( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_Byte cutoff, + double gamma_value ) + { + static FT_Bool initialized_gamma = FALSE; + static unsigned short gamma_ramp[256]; + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + int ii; + + if (!initialized_gamma) + { + initialized_gamma = TRUE; + /* linear to voltage */ + for ( ii = 0; ii < 256; ii++ ) + { + gamma_ramp[ii] = (unsigned char) + ( pow( (double)ii/255.0, gamma_value ) * 255.0f ); + if (gamma_ramp[ii] < cutoff) { + gamma_ramp[ii] = 0; + } + } + } + + /* horizontal in-place sub-pixel sharpening filter */ + if ( mode == FT_RENDER_MODE_LCD) + { + FT_Byte* line = bitmap->buffer; + for ( ; height > 0; height--, line += bitmap->pitch ) + { + FT_UInt xx; + for ( xx = 0; xx < width; xx++ ) + { + line[xx] = gamma_ramp[line[xx]]; + } + } + } + } + + /* simple linear scale to handle various sliding values */ + float + sliding_scale ( int min_value, + int max_value, + float min_amount, + float max_amount, + int cur_value ) + { + + float m = (min_amount - max_amount) / (float)(min_value - max_value); + float result = (((float)cur_value * m) + (max_amount - max_value * m)) ; + + if (min_amount < max_amount) + { + if (result < min_amount) return min_amount; + if (result > max_amount) return max_amount; + } + else + { + if (result < max_amount) return max_amount; + if (result > min_amount) return min_amount; + } + + return result; + } + + + /* brightness and contrast adjustment on the bitmap */ + static FT_Bool + _ft_bitmap_bc ( FT_Bitmap* bitmap, + float brightness, + float contrast ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + FT_Byte* line = bitmap->buffer; + FT_UInt xx; + + if ( brightness == 0 && contrast == 0 ) return FALSE; + + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch ) + { + for ( xx = 0; xx < width - 1; xx += 1 ) + { + if ( line[xx] > 0) + { + float value = (float)(255 - line[xx]) / 256.0; + FT_Int result = 0; + + if (brightness < 0.0) value = value * ( 1.0 + brightness); + else value = value + ((1.0 - value) * brightness); + value = (value - 0.5) * (tan ((contrast + 1.0) * 3.141592/4.0) ) + 0.5; + + result = (FT_Int)(255.0 - (value) * 256.0); + + if (result < 0) result = 0; + if (result > 255) result = 255; + + line[xx] = result; + } + } + } + return TRUE; + } + + + /* Filter to mimic Windows-style sharpening */ + /* Determined via 100% experimentation. */ + static void + _ft_lcd_windows_sharpen( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + + FT_Bitmap new_bitmap; + + FT_Bitmap_New(&new_bitmap); + + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + new_line = (&new_bitmap)->buffer; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + FT_UInt xx, threshold = 128; + FT_Byte* prevline = line - bitmap->pitch; + FT_Byte* nextline = line + bitmap->pitch; + + FT_Byte* new_prevline = new_line - bitmap->pitch; + FT_Byte* new_nextline = new_line + bitmap->pitch; + + for ( xx = 1; xx < width - 1; xx += 1 ) + { + /* subpixel grid sp11 sp21 sp31 */ + /* where sp22 is sp12 sp22 sp32 */ + /* current subpixel. sp13 sp23 sp33 */ + + FT_Int prevtotal, nexttotal, lefttotal, righttotal, sidesdiff, + prevdiff, nextdiff, sp11, sp21, sp31, sp12, sp22, sp32, + sp13, sp23, sp33; + + sp12 = line [xx-1]; + sp22 = line [xx]; + sp32 = line [xx+1]; + + if (height == bitmap->rows) + { + prevtotal = sp11 = sp21 = sp31 = 0; + prevdiff = sp22; + lefttotal = sp12 + sp13; + righttotal = sp32 + sp33; + } + else + { + prevtotal = prevline[xx-1] + prevline[xx] + prevline[xx+1]; + sp11 = prevline [xx-1]; + sp21 = prevline [xx]; + sp31 = prevline [xx+1]; + prevdiff = sp22 - sp21; + lefttotal = sp11 + sp12 + sp13; + righttotal = sp31 + sp32 + sp33; + } + + + if (height == 1) + { + nexttotal = sp13 = sp23 = sp33 = 0; + nextdiff = sp22; + lefttotal = sp11 + sp12; + righttotal = sp31 + sp32; + } + else + { + nexttotal = nextline[xx-1] + nextline[xx] + nextline[xx+1]; + sp13 = nextline [xx-1]; + sp23 = nextline [xx]; + sp33 = nextline [xx+1]; + nextdiff = sp23 - sp22; + lefttotal = sp11 + sp12 + sp13; + righttotal = sp31 + sp32 + sp33; + } + + sidesdiff = lefttotal - righttotal; + if (sidesdiff < 0) sidesdiff *= -1; + if (prevdiff < 0) prevdiff *= -1; + if (nextdiff < 0) nextdiff *= -1; + + /* if the current pixel is less than threshold, and greater than 0 */ + if ( sp22 <= threshold && sp22 > 0 ) + { + /* A pixel is horizontally isolated if: */ + /* 1: All upper adjecent pixels are >= threshold */ + if ( prevtotal >= nexttotal && abs (sp11 - sp12) > 5 && abs (sp21 - sp22) > 5 && abs (sp31 - sp32) > 5 /* not a vert stem end */ + && sp11 >= threshold + && sp21 >= threshold + && sp31 >= threshold && abs (sp23 - sp22) > 15 /* not on a vert stem */ + ) + { + /* darken upper adjacent subpixel; lighten current */ + if (height != (FT_UInt)bitmap->rows) new_prevline[xx] += ((255 - new_prevline[xx]) * strength) / 100 ; + new_line[xx] -= (new_line[xx] * strength) / 100; + + if (height != 1 && height != (FT_UInt)bitmap->rows) if (new_nextline[xx] > 155 + (100 - strength)) new_prevline[xx] = 255; + + } + else if ( nexttotal > prevtotal && abs (sp13 - sp12) > 5 && abs (sp23 - sp22) > 5 && abs (sp33 - sp32) > 5 + /* 2: All lower adjecent pixels are >= threshold */ + && sp13 >= threshold + && sp23 >= threshold + && sp33 >= threshold && abs (sp22 - sp21) > 15 + ) + { + /* darken lower adjacent subpixel; lighten current */ + if (height != 1) new_nextline[xx] += (255 - new_nextline[xx]) * strength / 100 ; + new_line[xx] -= (new_line[xx] * strength) / 100; + + if (height != 1) if (new_nextline[xx] > 155 + (100 - strength)) new_nextline[xx] = 255; + + } + } + else if ( sp22 > threshold && sp22 < 255 ) + { + if ( sp11 <= threshold && abs (sp13 - sp12) > 5 && abs (sp23 - sp22) > 5 && abs (sp33 - sp32) > 5 + && sp21 <= threshold + && sp31 <= threshold + && prevtotal <= nexttotal && abs (sp22 - sp21) > 15 + ) + { + /* bring this subpixel 1/3 of the way to 255 at 100% strength */ + new_line[xx] += (strength * (255 - new_line[xx]))/100 ; + if (height != (FT_UInt)bitmap->rows) new_prevline[xx] -= (new_prevline[xx] * strength) / 300; + } + else if ( + sp13 <= threshold && abs (sp11 - sp12) > 5 && abs (sp21 - sp22) > 5 && abs (sp31 - sp32) > 5 + && sp23 <= threshold + && sp33 <= threshold && + nexttotal < prevtotal && abs (sp23 - sp22) > 15 + + ) + { + new_line[xx] += (strength * (255 - new_line[xx]))/100 ; + if (height != 1) new_nextline[xx] -= (new_nextline[xx] * strength) / 300; + } + } + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + static void + _ft_lcd_darken_x ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + + FT_Bitmap new_bitmap; + + int factor1,factor2; + int bias = 0; + + FT_Bitmap_New(&new_bitmap); + + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + new_line = (&new_bitmap)->buffer; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + FT_UInt xx; + FT_Byte* prevline = line - bitmap->pitch; + FT_Byte* nextline = line + bitmap->pitch; + + for ( xx = 1; xx < width - 1; xx += 1 ) + { + /* subpixel grid sp11 sp21 sp31 */ + /* where sp22 is sp12 sp22 sp32 */ + /* current subpixel. sp13 sp23 sp33 */ + + FT_Int sp21, sp12, sp22, sp32, sp23; + + sp12 = line [xx-1]; + sp22 = line [xx]; + sp32 = line [xx+1]; + + if (height == bitmap->rows) + { + sp21 = 0; + } + else + { + sp21 = prevline [xx]; + } + + if (height == 1) + { + sp23 = 0; + + } + else + { + sp23 = nextline [xx]; + } + + /* darken subpixel if neighbor above and below are much less than */ + /* safer but less effective */ + factor1 = 5; + factor2 = 5; + + /* make matches in the middle of glyph slightly darker */ + /*if (height > 1 && height < (FT_UInt)bitmap->rows) bias = 1;*/ + + if ( sp22 > factor1 * sp21 && sp22 > factor1 * sp23 && sp22 > factor2 && sp12 > 16 && sp32 > 16 ) + if (new_line[xx] < (strength * 255) / 100 ) + new_line[xx] = (strength * 255) / 100 + bias * (255 - (strength * 255) / 100) / 3; + + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + static void + _ft_lcd_darken_y ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + + FT_Bitmap new_bitmap; + + FT_Bitmap_New(&new_bitmap); + + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + new_line = (&new_bitmap)->buffer; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + + FT_UInt xx; + for ( xx = 1; xx < width - 1; xx += 1 ) + { + if (line[xx] > line[xx-1] && line[xx] > line[xx+1]) + { + if (new_line[xx] > 0) new_line[xx] += (strength * (255 - new_line[xx])) / 100; + new_line[xx-1] += (strength * (255 - line[xx-1])) / 100; + new_line[xx+1] += (strength * (255 - line[xx+1])) / 100; + } + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + static void + _ft_bitmap_cap ( FT_Bitmap* bitmap, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + + FT_UInt cur_value = 0; + + FT_Bitmap new_bitmap; + + FT_Bitmap_New(&new_bitmap); + + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + new_line = (&new_bitmap)->buffer; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + + FT_UInt xx; + for ( xx = 1; xx < width - 1; xx += 1 ) + { + cur_value = (new_line[xx-1] + new_line[xx] + new_line[xx+1]) / 3; + if (cur_value > (strength * 255) / 100 ) + { + FT_UInt new_factor = (strength * 255) / 100; + new_line[xx] = (new_line[xx] * new_factor) / cur_value; + new_line[xx+1] = (new_line[xx+1] * new_factor) / cur_value; + new_line[xx-1] = (new_line[xx-1] * new_factor) / cur_value; + } + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + int + gamma2 ( int val, float value ) + { + return 256 * (1.0 - pow((1.0 - (float)val/ 256.0) , 1.0/value)); + } + + + + static void + _ft_bitmap_embolden ( FT_Bitmap* bitmap, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + FT_Bitmap new_bitmap; + FT_UInt xx; + + FT_Bitmap_New(&new_bitmap); + + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + + new_line = (&new_bitmap)->buffer; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + + for ( xx = 1; xx < width - 1; xx += 1 ) + { + + FT_Int new_value = 0; + + new_value = (strength * line [xx-1]) / 100 + gamma2(line [xx], .75) + (strength * line [xx+1]) / 100; + if (new_value > 255) new_value = 255; + + new_line[xx] = new_value; + + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + + static void + _ft_bitmap_gamma ( FT_Bitmap* bitmap, + float strength ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + FT_Byte* line = bitmap->buffer; + + FT_UInt xx; + + if (strength > 0) + for (height = (FT_UInt)bitmap->rows; + height > 0; + height--, line += bitmap->pitch ) + { + + for ( xx = 1; xx < width - 1; xx += 1 ) + { + if (abs(line[xx-1] - line[xx]) < 20 || abs(line[xx+1] - line[xx]) < 20) + line [xx] = gamma2(line [xx], strength) ; + + } + + } + } + + + /* Fringe filter */ + static void + _ft_lcd_fringe_filter ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + FT_Byte* new_line; + FT_Byte* line = bitmap->buffer; + + FT_Bitmap new_bitmap; + FT_Bitmap_New(&new_bitmap); + + + line = bitmap->buffer; + FT_Bitmap_Copy(library, bitmap, &new_bitmap); + new_line = (&new_bitmap)->buffer; + for (height = (FT_UInt)bitmap->rows ; height > 0; height--, line += bitmap->pitch, new_line += bitmap->pitch ) + { + /* Threshold set to 1/2 pixel intensity */ + FT_UInt xx, threshold = 128; + + /* Hack to make this work when bitmap is at first or last line */ + FT_Int fudge = bitmap->pitch * (height == (FT_UInt)bitmap->rows); + + + FT_Byte* prevline = line - bitmap->pitch + fudge; + FT_Byte* nextline = line + bitmap->pitch; + + for ( xx = 1; xx < width - 1; xx += 1 ) + { + /* subpixel grid sp11 sp21 sp31 */ + /* where sp22 is sp12 sp22 sp32 */ + /* current subpixel. sp13 sp23 sp33 */ + + FT_Int prevtotal, nexttotal, lefttotal, righttotal, sidesdiff, + leftdiff, rightdiff, prevdiff, nextdiff, sp11, sp21, sp31, + sp12, sp22, sp32, sp13, sp23, sp33; + + sp12 = line [xx-1]; + sp22 = line [xx]; + sp32 = line [xx+1]; + + /* if at max height fake out some values */ + if (height == (FT_UInt)bitmap->rows) + { + prevtotal = sp11 = sp21 = sp31 = 0; + prevdiff = sp22; + lefttotal = sp12 + sp13; + righttotal = sp32 + sp33; + } + else + { + prevtotal = prevline[xx-1] + prevline[xx] + prevline[xx+1]; + sp11 = prevline [xx-1]; + sp21 = prevline [xx]; + sp31 = prevline [xx+1]; + prevdiff = sp22 - sp21; + lefttotal = sp11 + sp12 + sp13; + righttotal = sp31 + sp32 + sp33; + } + + /* if at min height fake out some values */ + if (height == 1) + { + nexttotal = sp13 = sp23 = sp33 = 0; + nextdiff = sp22; + lefttotal = sp11 + sp12; + righttotal = sp31 + sp32; + } + else + { + nexttotal = nextline[xx-1] + nextline[xx] + nextline[xx+1]; + sp13 = nextline [xx-1]; + sp23 = nextline [xx]; + sp33 = nextline [xx+1]; + nextdiff = sp23 - sp22; + lefttotal = sp11 + sp12 + sp13; + righttotal = sp31 + sp32 + sp33; + } + + sidesdiff = lefttotal - righttotal; + leftdiff = sp22 - sp12; + rightdiff = sp32 - sp22; + if (sidesdiff < 0) sidesdiff *= -1; + if (prevdiff < 0) prevdiff *= -1; + if (nextdiff < 0) nextdiff *= -1; + if (leftdiff < 0) leftdiff *= -1; + if (rightdiff < 0) rightdiff *= -1; + + /* if the current subpixel is less than threshold, and varies only + slightly to left or right, lighten it */ + if ( sp22 <= threshold && sp22 > 0 && (leftdiff < 10 || rightdiff < 10 ) ) + { + /* A pixel is horizontally isolated if: */ + /* 1: All upper adjecent subpixels are >= threshold and all lower + adjacent ones are essentially white */ + if ( prevtotal >= nexttotal + && sp11 >= threshold + && sp21 >= threshold + && sp31 >= threshold + && sp13 < 2 + && sp23 < 2 + && sp33 < 2 + ) + + { + new_line[xx] -= (new_line[xx] * strength) / 100; + if (leftdiff < 10) new_line[xx-1] -= (new_line[xx-1] * strength) / 200; /* OPPORTUNITY FOR IMPROVEMENT - keep going left until 255? */ + if (rightdiff < 10) new_line[xx+1] -= (new_line[xx+1] * strength) / 200; /* OPPORTUNITY FOR IMPROVEMENT */ + } + else if ( nexttotal > prevtotal + /* 2: the inverse of above */ + && sp13 >= threshold + && sp23 >= threshold + && sp33 >= threshold + && sp11 < 2 + && sp21 < 2 + && sp31 < 2 + ) + { + new_line[xx] -= (new_line[xx] * strength) / 100; + if (leftdiff < 10) new_line[xx-1] -= (new_line[xx-1] * strength) / 200; /* OPPORTUNITY FOR IMPROVEMENT - keep going left until 255? */ + if (rightdiff < 10) new_line[xx+1] -= (new_line[xx+1] * strength) / 200; /* OPPORTUNITY FOR IMPROVEMENT */ + } + } + /* otherwise if the current subpixel is more than threshold, and varies + slightly to left or right, darken it */ + else if ( sp22 > threshold && sp22 < 255 && (leftdiff < 10 || rightdiff < 10 ) ) + { + if ( sp11 <= 2 + && sp21 <= 2 + && sp31 <= 2 + && sp13 >= threshold + && sp23 >= threshold + && sp33 >= threshold + && + prevtotal < nexttotal + ) + + { + new_line[xx] += ((255 - new_line[xx]) * strength) / 100; + } + else if ( + sp13 <= 2 + && sp23 <= 2 + && sp33 <= 2 && + nexttotal < prevtotal + && sp11 >= threshold + && sp21 >= threshold + && sp31 >= threshold + + ) + { + new_line[xx] += ((255 - new_line[xx]) * strength) / 100; + } + } + } + } + FT_Bitmap_Copy(library, &new_bitmap, bitmap); + FT_Bitmap_Done(library, &new_bitmap); + } + + + /* Grayscale filter */ + static void + _ft_lcd_grayscale_filter ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_UInt strength, + FT_Library library ) + { + + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + FT_Byte* line = bitmap->buffer; + + for (height = (FT_UInt)bitmap->rows; height > 0; height--, line += bitmap->pitch ) + { + FT_UInt xx; + for ( xx = 0; xx < width - 1; xx += 3 ) + { + FT_UInt total = line [xx] + line [xx + 1] + line [xx + 2]; + line[xx] = ( (100-strength) * line[xx] + strength * (total / 3) ) / 100; + line[xx+1] = ( (100-strength) * line[xx+1] + strength * (total / 3) ) / 100; + line[xx+2] = ( (100-strength) * line[xx+2] + strength * (total / 3) ) / 100; + } + } + } + + + + /*************************************************************************/ + /* */ + /* */ + /* */ + /* */ + /* */ + /* */ + + + typedef struct SA_Rule_ + { + const char family[32]; + const int ppem[5]; + } SA_Rule; + +#define STEM_WIDTH_2_PPEM 18 +#define MAX_PPEM 100 + + + +/* "Font name", {ppem where stem width becomes 1, + * ppem where stem width becomes 2... etc.} */ +/* 100 means auto-calculate */ +#define SNAPPING_STEM_WIDTHS_RULES_SIZE 21 + SA_Rule SNAPPING_STEM_WIDTHS_Rules + [SNAPPING_STEM_WIDTHS_RULES_SIZE] = + { + { "Andale Mono", {10, 21, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Arial Narrow", {10, 21, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Calibri", {10, 19, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Cantarell", {10, 22, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Century Gothic", {10, 22, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Comfortaa", {10, 19, 22, MAX_PPEM, MAX_PPEM} }, + { "Consolas", {10, 20, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Corbel", {10, 21, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Futura", {10, 14, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Gill Sans", {10, 17, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Helvetica CY", {10, 23, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Inconsolata", {10, 23, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Liberation Sans Narrow", {10, 22, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Liberation Sans", {10, 19, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Lucida Grande", {10, 16, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Lucida Sans Unicode", {10, 16, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Luxi Sans", {10, 17, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Open Sans", {10, 20, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Rokkitt", {10, 21, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Segoe UI", {10, 23, MAX_PPEM, MAX_PPEM, MAX_PPEM} }, + { "Trebuchet MS", {10, 17, STEM_WIDTH_2_PPEM, MAX_PPEM, MAX_PPEM} }, + }; + + +/* "Font name", {ppem, scale_up=1|scale_down=0} */ +#define SNAPPING_STEM_SCALING_RULES_SIZE 31 + SA_Rule SNAPPING_STEM_SCALING_Rules + [SNAPPING_STEM_SCALING_RULES_SIZE] = + { + { "Andale Mono", {11, 1,} }, + { "Bitstream Vera Sans", {12, 1,} }, + { "Calibri", {15, 1,} }, + { "Calibri", {17, 1,} }, + { "Calibri", {18, 1,} }, + { "Candara", {14, 1,} }, + { "Candara", {17, 1,} }, + { "Canwell", {13, 0,} }, + { "Comfortaa", {11, 0,} }, + { "Consolas", {11, 1,} }, + { "DejaVu Sans", {12, 1,} }, + { "Freesans", {16, 0,} }, + { "Freeserif", {13, 1,} }, + { "Freeserif", {17, 1,} }, + { "Inconsolata", {12, 1,} }, + { "Inconsolata", {15, 1,} }, + { "Lucida Grande", {13, 1,} }, + { "Myriad Pro", {14, 1,} }, + { "Myriad Pro", {17, 1,} }, + { "Nina", {11, 0,} }, + { "Nina", {12, 0,} }, + { "Nina", {13, 0,} }, + { "Optima", {17, 1,} }, + { "Raleway", {15, 0,} }, + { "Samba", {11, 0,} }, + { "Times New Roman", {17, 1,} }, + { "Trebuchet MS", {17, 0,} }, + { "Trebuchet MS", {13, 0,} }, + { "Trebuchet MS", {20, 1,} }, + { "Verdana", {12, 1,} }, + { "Verdana", {15, 1,} }, + }; + + +/* "Font name", {ppem, scale_up=1|scale_down=0} */ +#define SNAPPING_M_RULES_SIZE 9 + SA_Rule SNAPPING_M_Rules + [SNAPPING_M_RULES_SIZE] = + { + { "Courier New", {13, 1,} }, + { "Courier New", {14, 1,} }, + { "Courier", {13, 1,} }, + { "Courier", {14, 1,} }, + { "Droid Sans Mono", {12, 0,} }, + { "Bitstream Vera Sans", {12, 0,} }, + { "DejaVu Sans", {12, 0,} }, + { "Essential PragmataPro", {13, 0,} }, + { "Essential PragmataPro", {14, 0,} }, + }; + + +/* "Font name", {ppem, ppem} */ +#define SNAPPING_SYNTHESIZE_STEMS_RULES_SIZE 1 + SA_Rule SNAPPING_SYNTHESIZE_STEMS_Rules + [SNAPPING_SYNTHESIZE_STEMS_RULES_SIZE] = + { + { "---", {13, 13,} }, + }; + + +/* "Font name", {ppem, ppem} */ +#define SNAPPING_NO_BEARING_CORRECTION_RULES_SIZE 1 + SA_Rule SNAPPING_NO_BEARING_CORRECTION_Rules + [SNAPPING_NO_BEARING_CORRECTION_RULES_SIZE] = + { + { "Times New Roman", {0, 100,} }, + }; + + +/* "Font name", {ppem, ppem} */ +#define SNAPPING_EDGE_DETECTION_RULES_SIZE 8 + SA_Rule SNAPPING_EDGE_DETECTION_Rules + [SNAPPING_EDGE_DETECTION_RULES_SIZE] = + { + { "Tahoma", {11, 11,} }, + { "Courier New", {10, 12,} }, + { "Arial", {11, 11,} }, + { "Arial", {13, 13,} }, + { "Liberation Sans", {11, 11,} }, + { "FreeSans", {11, 11,} }, + { "FreeSans", {13, 13,} }, + { "Palatino Linotype", {0, 100,} }, + }; + +/* "Font name", {ppem, translate_value} */ +#define SNAPPING_STEM_TRANSLATING_RULES_SIZE 6 + SA_Rule SNAPPING_STEM_TRANSLATING_Rules + [SNAPPING_STEM_TRANSLATING_RULES_SIZE] = + { + { "Arial", {11, 32,} }, + { "Arial Unicode MS", {11, 32,} }, + { "FreeSans", {11, 32,} }, + { "Arimo", {11, 32,} }, + { "Liberation Sans", {11, 32,} }, + { "Tahoma", {11, 32,} }, + }; + +/* "Font name", {ppem, translate_value} */ +#define SNAPPING_STEM_TRANSLATING_ONLY_RULES_SIZE 74 + SA_Rule SNAPPING_STEM_TRANSLATING_ONLY_Rules + [SNAPPING_STEM_TRANSLATING_ONLY_RULES_SIZE] = + { + { "Arial Unicode MS", {10, 16,} }, + { "Arial Unicode MS", {8, 32,} }, + { "Arial Unicode MS", {9, 32,} }, + { "Arial", {10, 16,} }, + { "Arial", {8, 32,} }, + { "Arial", {9, 32,} }, + { "Arial", {16, -24,} }, + { "Arimo", {10, 8,} }, + { "Arimo", {8, 32,} }, + { "Arimo", {9, 32,} }, + { "Bitstream Vera Sans", {8, 16,} }, + { "Calibri", {10, 16,} }, + { "Calibri", {15, 0,} }, + { "Candara", {10, 16,} }, + { "Cantarell", {11, 0} }, + { "Cantarell", {12, 0} }, + { "Consolas", {8, 32,} }, + { "Consolas", {9, 32,} }, + { "Corbel", {10, 16,} }, + { "Courier", {13, 16,} }, + { "Courier", {15, 0,} }, + { "Dejavu Sans Mono", {7, 16,} }, + { "Dejavu Sans Mono", {8, 32,} }, + { "Dejavu Sans Mono", {9, 16,} }, + { "Dejavu Sans", {8, 16,} }, + { "Dejavu Sans", {15, -20,} }, + { "Droid Sans", {8, 16,} }, + { "Droid Sans", {9, 16,} }, + { "Freesans", {10, 16,} }, + { "Freesans", {9, 8,} }, + { "Georgia", {13, 16,} }, + { "Georgia", {14, 16,} }, + { "Georgia", {15, 0,} }, + { "Inconsolata", {10, 24,} }, + { "Inconsolata", {9, 32,} }, + { "Liberation Sans", {10, 8,} }, + { "Liberation Sans", {8, 32,} }, + { "Liberation Sans", {9, 32,} }, + { "Lucida Grande", {13, 24,} }, + { "Lucida Grande", {14, 24,} }, + { "Lucida Grande", {8, 16,} }, + { "Lucida Grande", {9, 16,} }, + { "Lucida Sans Unicode", {13, 24,} }, + { "Lucida Sans Unicode", {14, 24,} }, + { "Lucida Sans Unicode", {8, 16,} }, + { "Lucida Sans Unicode", {9, 16,} }, + { "Microsoft Sans Serif", {10, 16,} }, + { "Microsoft Sans Serif", {8, 32,} }, + { "Microsoft Sans Serif", {9, 32,} }, + { "Myriad Pro", {10, 16,} }, + { "Myriad Pro", {11, 0,} }, + { "Myriad Pro", {9, 16,} }, + { "Open Sans", {10, 16,} }, + { "Open Sans", {9, 16,} }, + { "Optima", {10, 0} }, + { "Optima", {11, 0} }, + { "Optima", {12, 0} }, + { "Segoe UI", {10, 0,} }, + { "Segoe UI", {7, 32,} }, + { "Segoe UI", {8, 16,} }, + { "Segoe UI", {9, 24,} }, + { "Tahoma", {7, 32,} }, + { "Tahoma", {8, 32,} }, + { "Tahoma", {9, 32,} }, + { "Times New Roman", {17, 8,} }, + { "Trebuchet MS", {10, 16,} }, + { "Trebuchet MS", {11, 0,} }, + { "Trebuchet MS", {8, 32,} }, + { "Trebuchet MS", {9, 32,} }, + { "Verdana", {8, 16,} }, + { "Verdana", {15, 16,} }, + { "Verdana", {14, 32,} }, + { "Verdana", {18, 32,} }, + { "Verdana", {19, 24,} }, + }; + + +/* "Font name", {start ppem, end ppem} */ +#define ALWAYS_USE_100_RULES_SIZE 46 + SA_Rule ALWAYS_USE_100_Rules + [ALWAYS_USE_100_RULES_SIZE] = + { + { "Andale Mono", {0, MAX_PPEM,} }, + { "Arial Unicode MS", {0, MAX_PPEM,} }, + { "Arial", {0, MAX_PPEM,} }, + { "Arimo", {0, MAX_PPEM,} }, + { "Bitstream Vera Sans Mono", {0, MAX_PPEM,} }, + { "Bitstream Vera Sans", {10, 14,} }, + { "Bitstream Vera Sans", {16, 17,} }, + { "Calibri", {23, MAX_PPEM,} }, + { "Consolas", {0, MAX_PPEM,} }, + { "Courier New", {12, 12,} }, + { "Courier", {0, MAX_PPEM,} }, + { "Cousine", {0, MAX_PPEM,} }, + { "DejaVu Sans Mono", {0, MAX_PPEM,} }, + { "DejaVu Sans", {10, 14,} }, + { "DejaVu Sans", {16, 17,} }, + { "Droid Sans", {12, 12,} }, + { "Droid Sans", {15, 15,} }, + { "FreeMono", {0, MAX_PPEM,} }, + { "FreeSans", {0, MAX_PPEM,} }, + { "Liberation Mono", {0, MAX_PPEM,} }, + { "Lucida Console", {0, MAX_PPEM,} }, + { "Luxi Sans", {13, 13,} }, + { "Microsoft Sans Serif", {0, MAX_PPEM,} }, + { "Monaco", {0, MAX_PPEM,} }, + { "Segoe UI", {11, 12,} }, + { "Segoe UI", {14, 14,} }, + { "Tahoma", {11, 11,} }, + { "Tahoma", {14, MAX_PPEM,} }, + { "Times New Roman", {14, 14,} }, + { "Times New Roman", {16, 16,} }, + { "Trebuchet MS", {13, 13,} }, + { "Ubuntu", {12, 13,} }, + { "Ubuntu", {15, 15,} }, + { "Verdana", {0, 14,} }, + { "Verdana", {16, MAX_PPEM,} }, + { "Pragmata", {0, MAX_PPEM,} }, + { "Essential PragmataPro", {0, MAX_PPEM,} }, + }; + + + + +#define AUTOHINT_BRIGHTNESS_RULES_SIZE 3 + SA_Rule BRIGHTNESS_Rules + [AUTOHINT_BRIGHTNESS_RULES_SIZE] = + { + { "Baskerville", {0, -20,} }, + { "Garamond", {0, -20,} }, + { "Optima", {0, -20,} }, + }; + +#define AUTOHINT_CONTRAST_RULES_SIZE 3 + SA_Rule CONTRAST_Rules + [AUTOHINT_CONTRAST_RULES_SIZE] = + { + { "Baskerville", {0, 25,} }, + { "Garamond", {0, 25,} }, + { "Optima", {0, 25,} }, + }; + +#if 0 +#define STEM_SPACING_RULES_SIZE 3 + SA_Rule STEM_SPACING_Rules + [STEM_SPACING_RULES_SIZE] = + { + { "Tahoma", {10, 12, 18, 18, 30} }, + { "Arial", {10, 11, 23, 25, 30} }, + { "Freesans", {10, 12, 18, 18, 30} }, + }; + +#define STEM_START_RULES_SIZE 3 + SA_Rule STEM_START_Rules + [STEM_START_RULES_SIZE] = + { + { "Tahoma", {14, 17, 30, 100, 100} }, + { "Arial", {11, 18, 23, 30, 30} }, + { "Freesans", {10, 18, 18, 25, 30} }, + }; +#endif + + typedef struct Stem_Data_ + { + FT_Int stem_width; + FT_Int stem_spacing; + FT_Int stem_start; + FT_Int stem_scaling; + FT_Int stem_translating_only; + FT_Int stem_translating; + FT_Int brightness; + FT_Int contrast; + FT_Bool use_100; + FT_Bool synth_stems; + FT_Bool edge_detection; + FT_Bool bearing_correction; + FT_Int m; + } Stem_Data; + + + typedef struct Stem_Segment_ + { + FT_Long x1; + FT_Long x2; + FT_Int y; + } Stem_Segment; + + typedef struct Stem_Center_ + { + FT_Long x; + FT_Long y; + FT_Long w; + FT_Long x1; + FT_Long x2; + } Stem_Center; + + typedef struct Stem_ + { + FT_Long center; + FT_Long count; + FT_Long rcount; /* used to count within a range in possible stems */ + FT_Long width; + FT_Long height; + FT_Short zone; /* 1 2 or 3 */ + FT_Bool generated; + } Stem; + + + static void + swap_stem ( Stem* s1, Stem* s2 ) + { + Stem s; + s.center = s1->center; + s.count = s1->count; + s.rcount = s1->rcount; + s.width = s1->width; + s.zone = s1->zone; + s.generated = s1->generated; + + s1->center = s2->center; + s1->count = s2->count; + s1->rcount = s2->rcount; + s1->width = s2->width; + s1->zone = s2->zone; + s1->generated = s2->generated; + + s2->center = s.center; + s2->count = s.count; + s2->rcount = s.rcount; + s2->width = s.width; + s2->zone = s.zone; + s2->generated = s.generated; + } + + + FT_LOCAL_DEF( void ) + sa_fill_known_stem_values ( + FT_String* family, + int ppem, + FT_String* style, + FT_UInt num_stems, + Stem_Data* known_stem_values ) + { + FT_Int i, j; + if (verbose) printf("%s ", family); + + i = 0; + while ( i < SNAPPING_STEM_WIDTHS_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_STEM_WIDTHS_Rules[i].family, family ) == 0 ) ) + { + j = 0; + known_stem_values->stem_width = 1; + + while (j < 4) + { + if (SNAPPING_STEM_WIDTHS_Rules[i].ppem[j] == MAX_PPEM ) + { + known_stem_values->stem_width = -1; /* use default */ + j = 5; + i = SNAPPING_STEM_WIDTHS_RULES_SIZE; + } + else if (ppem < SNAPPING_STEM_WIDTHS_Rules[i].ppem[j]) + { + known_stem_values->stem_width = j; + j = 5; + i = SNAPPING_STEM_WIDTHS_RULES_SIZE; + } + j++; + } + } + i++; + } + + i = 0; + while ( i < SNAPPING_STEM_SCALING_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_STEM_SCALING_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->stem_scaling = -1; /* default */ + + if (ppem == SNAPPING_STEM_SCALING_Rules[i].ppem[0]) + { + known_stem_values->stem_scaling = SNAPPING_STEM_SCALING_Rules[i].ppem[1]; + i = SNAPPING_STEM_SCALING_RULES_SIZE; + } + } + i++; + } + + + i = 0; + while ( i < SNAPPING_M_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_M_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->m = -1; /* default */ + + if (ppem == SNAPPING_M_Rules[i].ppem[0]) + { + known_stem_values->m = SNAPPING_M_Rules[i].ppem[1]; + i = SNAPPING_M_RULES_SIZE; + } + } + i++; + } + + i = 0; + while ( i < SNAPPING_STEM_TRANSLATING_ONLY_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_STEM_TRANSLATING_ONLY_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->stem_translating_only = -1024; /* default */ + + if (ppem == SNAPPING_STEM_TRANSLATING_ONLY_Rules[i].ppem[0] + || SNAPPING_STEM_TRANSLATING_ONLY_Rules[i].ppem[0] == 0) + { + known_stem_values->stem_translating_only = SNAPPING_STEM_TRANSLATING_ONLY_Rules[i].ppem[1]; + i = SNAPPING_STEM_TRANSLATING_ONLY_RULES_SIZE; + } + } + i++; + } + + i = 0; + while ( i < SNAPPING_STEM_TRANSLATING_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_STEM_TRANSLATING_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->stem_translating = 0; /* default */ + + if (ppem == SNAPPING_STEM_TRANSLATING_Rules[i].ppem[0] + || SNAPPING_STEM_TRANSLATING_Rules[i].ppem[0] == 0) + { + known_stem_values->stem_translating = SNAPPING_STEM_TRANSLATING_Rules[i].ppem[1]; + i = SNAPPING_STEM_TRANSLATING_RULES_SIZE; + } + } + i++; + } + + + i = 0; + while ( i < ALWAYS_USE_100_RULES_SIZE ) + { + if ( family && + ( strcasecmp( ALWAYS_USE_100_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->use_100 = FALSE; /* default */ + + if (ppem >= ALWAYS_USE_100_Rules[i].ppem[0] && ppem <= ALWAYS_USE_100_Rules[i].ppem[1] ) + { + known_stem_values->use_100 = TRUE; + i = ALWAYS_USE_100_RULES_SIZE; + } + } + i++; + } + + + i = 0; + while ( i < SNAPPING_SYNTHESIZE_STEMS_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_SYNTHESIZE_STEMS_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->synth_stems = FALSE; /* default */ + + if (ppem >= SNAPPING_SYNTHESIZE_STEMS_Rules[i].ppem[0] && ppem <= SNAPPING_SYNTHESIZE_STEMS_Rules[i].ppem[1] ) + { + known_stem_values->synth_stems = TRUE; + i = SNAPPING_SYNTHESIZE_STEMS_RULES_SIZE; + } + } + i++; + } + + + i = 0; + while ( i < SNAPPING_EDGE_DETECTION_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_EDGE_DETECTION_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->edge_detection = FALSE; /* default */ + + if (ppem >= SNAPPING_EDGE_DETECTION_Rules[i].ppem[0] && ppem <= SNAPPING_EDGE_DETECTION_Rules[i].ppem[1] ) + { + known_stem_values->edge_detection = TRUE; + i = SNAPPING_EDGE_DETECTION_RULES_SIZE; + } + } + i++; + } + + + i = 0; + while ( i < SNAPPING_NO_BEARING_CORRECTION_RULES_SIZE ) + { + if ( family && + ( strcasecmp( SNAPPING_NO_BEARING_CORRECTION_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->bearing_correction = TRUE; /* default */ + + if (ppem >= SNAPPING_NO_BEARING_CORRECTION_Rules[i].ppem[0] && ppem <= SNAPPING_NO_BEARING_CORRECTION_Rules[i].ppem[1] ) + { + known_stem_values->bearing_correction = FALSE; + i = SNAPPING_NO_BEARING_CORRECTION_RULES_SIZE; + } + } + i++; + } + + +#if 0 + i = 0; + while ( i < AUTOHINT_BRIGHTNESS_RULES_SIZE ) + { + if ( family && + ( strcasecmp( BRIGHTNESS_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->brightness = 0.0; + + if (ppem == BRIGHTNESS_Rules[i].ppem[0] || BRIGHTNESS_Rules[i].ppem[0] == 0) + { + known_stem_values->brightness = BRIGHTNESS_Rules[i].ppem[1]; + i = AUTOHINT_BRIGHTNESS_RULES_SIZE; + } + } + i++; + } + + i = 0; + while ( i < AUTOHINT_CONTRAST_RULES_SIZE ) + { + if ( family && + ( strcasecmp( CONTRAST_Rules[i].family, family ) == 0 ) ) + { + known_stem_values->contrast = 0.0; + + if (ppem == CONTRAST_Rules[i].ppem[0] || CONTRAST_Rules[i].ppem[0] == 0) + { + known_stem_values->contrast = CONTRAST_Rules[i].ppem[1]; + i = AUTOHINT_CONTRAST_RULES_SIZE; + } + } + i++; + } + + for ( i = 0; i <= STEM_SPACING_RULES_SIZE; i++ ) + { + if ( family && + ( strcasecmp( STEM_SPACING_Rules[i].family, family ) == 0 ) ) + { + j = 0; + known_stem_values->stem_spacing = 2; /* default */ + + while (j < 4) + { + if (ppem < STEM_SPACING_Rules[i].ppem[j]) + { + known_stem_values->stem_spacing = j; + j = 5; + } + j++; + } + } + } + + + for ( i = 0; i <= STEM_START_RULES_SIZE; i++ ) + { + if ( family && + ( strcasecmp( STEM_START_Rules[i].family, family ) == 0 ) ) + { + j = 0; + known_stem_values->stem_start = 1; /* default */ + + while (j < 4) + { + if (ppem < STEM_START_Rules[i].ppem[j]) + { + known_stem_values->stem_start = j; + j = 5; + } + j++; + } + } + } +#endif + } + + + FT_LOCAL_DEF( FT_Int ) + get_contrast ( + FT_String* family, + int ppem) + { + FT_Int i; + if (verbose) printf("%s ", family); + + i = 0; + while ( i < AUTOHINT_CONTRAST_RULES_SIZE ) + { + if ( family && + ( strcasecmp( CONTRAST_Rules[i].family, family ) == 0 ) ) + { + if (ppem == CONTRAST_Rules[i].ppem[0] || CONTRAST_Rules[i].ppem[0] == 0) + { + return CONTRAST_Rules[i].ppem[1]; + } + } + i++; + } + return 0; + } + + + FT_LOCAL_DEF( FT_Int ) + get_brightness ( + FT_String* family, + int ppem) + { + FT_Int i; + if (verbose) printf("%s ", family); + + i = 0; + while ( i < AUTOHINT_BRIGHTNESS_RULES_SIZE ) + { + if ( family && + ( strcasecmp( BRIGHTNESS_Rules[i].family, family ) == 0 ) ) + { + if (ppem == BRIGHTNESS_Rules[i].ppem[0] || BRIGHTNESS_Rules[i].ppem[0] == 0) + { + return BRIGHTNESS_Rules[i].ppem[1]; + } + } + i++; + } + return 0; + } + + + /* Stem alignment for bitmaps; A hack with very nice results */ + /* Ideally this could be implemented on the outline, prior to + * rasterization. Possible future enhancement is to use the + * warper code to achieve this */ + static void + _lcd_stem_align ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_GlyphSlot slot, + FT_Long* translate_value, + float* scale_value, + FT_UInt alignment_strength, + FT_UInt fitting_strength, + float* embolden_value + ) + { + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + + Stem_Segment* segments; + Stem_Segment* leftmost_segment; + Stem_Segment* rightmost_segment; + Stem_Segment* leftmost_segment_not_extrema; + Stem_Segment* rightmost_segment_not_extrema; + Stem* stems; + Stem* possible_stems; + Stem* leftmost_stem; + Stem* rightmost_stem; + Stem_Data* known_stem_values; + Stem_Center* centers; + FT_Long leftmost_point = width * 256; + FT_Long rightmost_point = 0; + FT_Long leftmost_point_not_extrema = width * 256; + FT_Long rightmost_point_not_extrema = 0; + FT_Long num_segments = 0; + FT_Long num_centers = 0; + FT_Long stem_centers[width * 256]; + FT_UInt h; + FT_ULong valid_stems = 0, valid_possible_stems = 0; + FT_Long center, stem_matches, stem_matches_ledge; + FT_Long stem_matches_redge, next_center, last_matching_center; + FT_Long last_matching_ledge, last_matching_redge, this_center; + FT_Int max_strength; + FT_Byte* line = bitmap->buffer; + FT_UInt current_value = 0; + FT_UInt xx; + FT_Long linearHoriAdvance = slot->linearHoriAdvance >> 10; + + FT_Int m_horiBearingX = slot->metrics.horiBearingX; + FT_Int m_horiAdvance = slot->metrics.horiAdvance; + FT_Int m_width = slot->metrics.width; + FT_Pos one_pixel = 768; + FT_Pos one_third_pixel = 256; + FT_Int columns_per_pixel = 3; + /*FT_Int extra_columns = 6;*/ + + /* on / off flags for testing different features */ + FT_Bool strategy_translate_using_closest_stem = TRUE; + FT_Bool strategy_scale_to_closest_centers = FALSE; + FT_Bool strategy_scale_to_closest_centers_up_only = FALSE; + FT_Bool strategy_always_use_distance_ceiling = FALSE; + FT_Bool strategy_auto_change_center_offset = TRUE; + FT_Bool strategy_use_m_control = FALSE; + FT_Bool strategy_correct_out_of_bounds_outlines = FALSE; /*this needs work.. breaks some glyphs like verdana 12 */ + FT_Bool strategy_also_use_edge_detection_for_stems = FALSE; + FT_Bool strategy_use_strengths = TRUE; + FT_Bool strategy_synthesize_stems = FALSE; + FT_Bool strategy_bearing_correction = TRUE; + FT_Bool strategy_use_d_correction = TRUE; + FT_Bool strategy_fit_to_width = FALSE; + /*FT_Bool strategy_center_glyph = FALSE;*/ + FT_Bool strategy_use_verdana_12_hack = FALSE; /* not necessary anymore... maybe */ + FT_Bool has_serifs = FALSE; + FT_Bool autohinted = FALSE; + + const FT_Int MIN_PPEM = 7; + /*const FT_Int MAX_PPEM = 100;*/ + const FT_Int MAX_STEMS = 3; + FT_Int ppem = 0; + + int checked_use_known_settings_on_selected_fonts_env = 0; + FT_Bool use_known_settings_on_selected_fonts = FALSE; + + int cur_width; + char *cur_width_env = getenv( "CUR_WIDTH" ); + + if ( cur_width_env != NULL ){ + sscanf ( cur_width_env, "%d", &cur_width ); + if (cur_width != 0) autohinted = TRUE; + } + + /* An incoming scale value of 1.1 indicates to do certain things */ + /*if (*scale_value == 1.1) strategy_use_verdana_12_hack = TRUE;*/ + + /* reset to default */ + *scale_value = 1.0; + + if ( checked_use_known_settings_on_selected_fonts_env == 0 ) + { + char *use_known_settings_on_selected_fonts_env = getenv( "INFINALITY_FT_USE_KNOWN_SETTINGS_ON_SELECTED_FONTS" ); + if ( use_known_settings_on_selected_fonts_env != NULL ) + { + if ( strcasecmp(use_known_settings_on_selected_fonts_env, "default" ) != 0 ) + { + if ( strcasecmp(use_known_settings_on_selected_fonts_env, "true") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "1") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "on") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "yes") == 0) + use_known_settings_on_selected_fonts = TRUE; + } + } + checked_use_known_settings_on_selected_fonts_env = 1; + } + + + /* Simply return in odd cases where these don't seem to be set */ + /* Flash and some pdf viewers will crash otherwise */ + if ( !slot->face || !slot->face->size || !slot->face->size->metrics.x_ppem ) + return; + if ( slot->face->size->metrics.x_ppem > MAX_PPEM ) return; + /*if ( width < 4 ) return;*/ + + if ( slot->face->size->metrics.x_ppem < MIN_PPEM ) return; + + if ( !FT_IS_SCALABLE( slot->face ) ) return; + + ppem = slot->face->size->metrics.x_ppem; + + + /* only perform alignment on styles we know, that aren't bold or italic */ + /* perhaps detection could be added on those that are not set? */ + /* Require certain ppems for narrow and light fonts */ + if( slot->face->style_name ) + { + if ( strcasestr(slot->face->style_name, "Italic") + || strcasestr(slot->face->style_name, "Oblique") + || strcasestr(slot->face->style_name, "Script") + || strcasestr(slot->face->style_name, "Handwriting") + || strcasestr(slot->face->style_name, "Bold") + || strcasestr(slot->face->style_name, "Black") + || ( ( strcasestr(slot->face->style_name, "Extra Thin") + || strcasestr(slot->face->style_name, "Extra Light") ) + && ppem < 10 ) + || ( strcasestr(slot->face->style_name, "Thin") + && ppem < 10 ) + || ( strcasestr(slot->face->style_name, "Light") + && ppem < 10 ) + || ( strcasestr(slot->face->style_name, "Narrow") + && ppem < 15 ) + || ( strcasestr(slot->face->style_name, "Condensed") + && ppem < 20 ) ) + return; + } + + if( slot->face->family_name ) + { + if ( strcasestr(slot->face->family_name, "Italic") + || strcasestr(slot->face->family_name, "Oblique") + || strcasestr(slot->face->family_name, "Script") + || strcasestr(slot->face->family_name, "Handwriting") + || strcasestr(slot->face->family_name, "Bold") + || strcasestr(slot->face->family_name, "Black") + || ( ( strcasestr(slot->face->family_name, "Extra Thin") + || strcasestr(slot->face->family_name, "Extra Light") ) + && ppem < 10 ) + || ( strcasestr(slot->face->family_name, "Thin") + && ppem < 10 ) + || ( strcasestr(slot->face->family_name, "Light") + && ppem < 10 ) + || ( strcasestr(slot->face->family_name, "Narrow") + && ppem < 15 ) + || ( strcasestr(slot->face->family_name, "Condensed") + && ppem < 20 ) ) + return; + } + else if ( slot->face->style_flags ) + { + if ( slot->face->style_flags & FT_STYLE_FLAG_ITALIC + || slot->face->style_flags & FT_STYLE_FLAG_BOLD + || FT_IS_TRICKY( slot->face ) ) + return; + } + else return; + + if( slot->face->family_name ) + { + if ( strcasestr(slot->face->family_name, "Courier") + || strcasestr(slot->face->family_name, "Serif") + || strcasestr(slot->face->family_name, "Times")) + has_serifs = TRUE; + } + + if ( mode != FT_RENDER_MODE_LCD ) + { + columns_per_pixel = 1; + one_pixel = 256; + one_third_pixel = 85; + /*extra_columns = 0;*/ + /* until this can be figured out just return */ + /* There are issues with missing glyphs */ + return; + } + + known_stem_values = (Stem_Data*) malloc (columns_per_pixel * sizeof(Stem_Data)); /* only look at top 3 for now */ + known_stem_values->stem_spacing = -1; + known_stem_values->stem_width = -1; + known_stem_values->stem_start = -1; + known_stem_values->stem_scaling = -1; + known_stem_values->stem_translating_only = -1024; + known_stem_values->stem_translating = 0; + known_stem_values->brightness = 0; + known_stem_values->contrast = 0; + known_stem_values->use_100 = FALSE; + known_stem_values->m = -1; + known_stem_values->synth_stems = FALSE; + known_stem_values->bearing_correction = TRUE; + + if (use_known_settings_on_selected_fonts) + { + sa_fill_known_stem_values ( slot->face->family_name, + ppem, slot->face->style_name, + valid_stems, known_stem_values ); + if (verbose) + printf ("width:%d,spacing:%d,start:%d,scaling:%d,translate:%d ", + known_stem_values->stem_width, known_stem_values->stem_spacing, + known_stem_values->stem_start, known_stem_values->stem_scaling, + known_stem_values->stem_translating_only) ; + } + + /* translate value may be set for < 10 */ + if (use_known_settings_on_selected_fonts && known_stem_values->stem_translating_only > -1024 ) + { + *translate_value = known_stem_values->stem_translating_only; + return; + } + + if (use_known_settings_on_selected_fonts && known_stem_values->bearing_correction == FALSE ) + { + strategy_bearing_correction = FALSE; + } + + if ( known_stem_values->use_100 || known_stem_values->m >= 0) + { + alignment_strength = fitting_strength = 100; + strategy_use_m_control = TRUE; + } + + if ( known_stem_values->edge_detection ) + { + strategy_also_use_edge_detection_for_stems = TRUE; + } + + if ( ppem < 9 ) return; + if ( ppem > 20 ) strategy_use_m_control = TRUE; + + /* Allocate */ + segments = (Stem_Segment*) malloc( (1) * sizeof (Stem_Segment)); + leftmost_segment = (Stem_Segment*) malloc( sizeof (Stem_Segment)); + leftmost_segment_not_extrema = (Stem_Segment*) malloc( sizeof (Stem_Segment)); + rightmost_segment = (Stem_Segment*) malloc( sizeof (Stem_Segment)); + rightmost_segment_not_extrema = (Stem_Segment*) malloc( sizeof (Stem_Segment)); + + stems = (Stem*) malloc (MAX_STEMS * sizeof(Stem)); + possible_stems = (Stem*) malloc (MAX_STEMS * sizeof(Stem)); + leftmost_stem = (Stem*) malloc ( sizeof(Stem)); + rightmost_stem = (Stem*) malloc ( sizeof(Stem)); + centers = (Stem_Center*) malloc ( (1) * sizeof(Stem_Center)); + + if (verbose) printf("\n"); + + /* Initialize */ + for ( xx = 0; xx < width * 256; xx += 1 ) + { + stem_centers[xx] = 0; + } + for ( xx = 0; xx < num_segments; xx += 1 ) + { + segments[xx].x1 = 0; + segments[xx].x2 = 0; + segments[xx].y = 0; + } + rightmost_segment->x1 = 0; + rightmost_segment->x2 = 0; + rightmost_segment->y = 0; + leftmost_segment->x1 = 99999999; + leftmost_segment->x2 = 0; + leftmost_segment->y = 0; + + rightmost_segment_not_extrema->x1 = 0; + rightmost_segment_not_extrema->x2 = 0; + rightmost_segment_not_extrema->y = 0; + leftmost_segment_not_extrema->x1 = 99999999; + leftmost_segment_not_extrema->x2 = 0; + leftmost_segment_not_extrema->y = 0; + + /* Locate stem centers for later processing */ + for ( h = (FT_UInt)bitmap->rows; h > 0; h--, line += bitmap->pitch ) + { + current_value = 0; + /* Calculate various sums and stem widths of glyph */ + for ( xx = 0; xx < width; xx += 1 ) + { + /* Reallocate */ + segments = (Stem_Segment*) realloc( segments, (num_segments + 1) * sizeof (Stem_Segment)); + + /* if line is white, and now has color, it's the start of a stem */ + if (current_value == 0 && line[xx] > 0) + { + /* start of stem */ + segments[num_segments].x1 = 256 * (xx) + (255 - line[xx]); + segments[num_segments].y = h; + } + + /* otherwise, if it's currently black and the new value is 0, it's the end of a stem */ + else if ( ( current_value > 0 && line[xx] == 0 ) + || ( current_value > 0 && xx == width - 1 ) ) + { + FT_Long stem_center_x/*, stem_width*/; + segments[num_segments].x2 = 256 * (xx-1) + line[xx-1]; + + if (xx == width - 1) segments[num_segments].x2 += line[xx]; + + /*stem center is average of start and end of stem */ + stem_center_x = (segments[num_segments].x2 + segments[num_segments].x1) / 2; + /*stem_width = segments[num_segments].x2 - segments[num_segments].x1;*/ + /* Reallocate */ + centers = (Stem_Center*) realloc ( centers, (num_centers + 1) * sizeof(Stem_Center)); + centers[num_centers].x = stem_center_x; + centers[num_centers].y = h; + centers[num_centers].x1 = segments[num_segments].x1; + centers[num_centers].x2 = segments[num_segments].x2; + + num_centers++; + + stem_centers[stem_center_x] += 1; + + /* Find left and rightmost points for later calculations */ + /* OR - Favor ones that aren't on the top or bottom if possible to prevent v and w from getting caught later */ + if ( segments[num_segments].x1 < leftmost_segment->x1 + || ( segments[num_segments].y > 1 && segments[num_segments].y < height + && segments[num_segments].x1 == leftmost_segment->x1 ) ) + { + leftmost_segment->x1 = segments[num_segments].x1; + leftmost_segment->x2 = segments[num_segments].x2; + leftmost_segment->y = h; + } + if (segments[num_segments].x2 > rightmost_segment->x2 + || ( segments[num_segments].y > 1 && segments[num_segments].y < height + && segments[num_segments].x1 == rightmost_segment->x1 ) ) + { + rightmost_segment->x1 = segments[num_segments].x1; + rightmost_segment->x2 = segments[num_segments].x2; + rightmost_segment->y = h; + } + + if (segments[num_segments].x1 < leftmost_segment_not_extrema->x1 + || ( segments[num_segments].y > 1 && segments[num_segments].y < height + && segments[num_segments].x1 == leftmost_segment_not_extrema->x1 + && h < (FT_UInt)bitmap->rows && h > 0 ) ) + { + leftmost_segment_not_extrema->x1 = segments[num_segments].x1; + leftmost_segment_not_extrema->x2 = segments[num_segments].x2; + leftmost_segment_not_extrema->y = h; + } + if (segments[num_segments].x2 > rightmost_segment_not_extrema->x2 + || ( segments[num_segments].y > 1 && segments[num_segments].y < height + && segments[num_segments].x1 == rightmost_segment_not_extrema->x1 + && h < (FT_UInt)bitmap->rows && h > 0 ) ) + { + rightmost_segment_not_extrema->x1 = segments[num_segments].x1; + rightmost_segment_not_extrema->x2 = segments[num_segments].x2; + rightmost_segment_not_extrema->y = h; + } + + if (segments[num_segments].x1 < leftmost_point) + { + leftmost_point = segments[num_segments].x1; + } + if (segments[num_segments].x2 > rightmost_point) + { + rightmost_point = segments[num_segments].x2; + } + + if (segments[num_segments].x1 < leftmost_point_not_extrema + && h < (FT_UInt)bitmap->rows && h > 0) + { + leftmost_point_not_extrema = segments[num_segments].x1; + } + if (segments[num_segments].x2 > rightmost_point_not_extrema + && h < (FT_UInt)bitmap->rows && h > 0) + { + rightmost_point_not_extrema = segments[num_segments].x2; + } + + num_segments++; + } + /* else - other conditions - need some error checking here */ + + current_value = line[xx]; + } + } + + /* initialize */ + for ( xx = 0; xx < MAX_STEMS; xx +=1 ) + { + stems[xx].center = 0; + stems[xx].count = 0; + stems[xx].width = 0; + stems[xx].height = 0; + possible_stems[xx].center = 0; + possible_stems[xx].count = 0; + possible_stems[xx].width = 0; + possible_stems[xx].height = 0; + } + valid_stems = 0; + valid_possible_stems = 0; + + /* Determine which centers belong to stems */ + center = 0; + + while ( center < num_centers ) + { + /* slope at within which to consider a point part of a stem */ + /*const FT_UInt slope = 1; + const FT_UInt topslope = (256 * 3) / 10; */ + FT_Int deviation1 = 5; /* 10 to 20 wiith 4 matches seems good, but 1 or 2 with 3 stems needs to somehow get included */ + FT_Int deviation2=-1, requirement1 = 4, stem_match_requirement = 3; + FT_Int best_height = 0, center_difference_in_height; + FT_Int center_difference_in_width, valid_center_average; + FT_Int smallest_width_ledge, smallest_width_redge; + FT_Int x1_difference_in_width, x2_difference_in_width; + FT_Bool large_gap_found = FALSE, no_gap_found = FALSE; + FT_Bool large_gap_found_ledge = FALSE, no_gap_found_ledge = FALSE; + FT_Bool large_gap_found_redge = FALSE, no_gap_found_redge = FALSE; + FT_Bool stem_detected = FALSE; + FT_Int set_width_to, set_center_to; + + /* seems to not do damage */ + /* May not be effective */ + requirement1 = height / 4; + if (requirement1 < 5) requirement1 = 5; + deviation1 = 20; + deviation2 = 20; + + if (columns_per_pixel == 1) + { + deviation1 = deviation2 = 10; + } + + if ((FT_Int)bitmap->rows <= 6) deviation1 = 25; + if ((FT_Int)bitmap->rows <= 6) deviation2 = 25; + + if (columns_per_pixel == 1 && (FT_Int)bitmap->rows <= 6) + { + deviation1 = deviation2 = 12; + } + + /* THIS WORKS, BUT NEED TO PUNISH DIAGONALS like W */ + /*requirement2 = height / 5; + if (requirement2 < 3) requirement2 = 3; + deviation2 = 1 ;*/ + valid_center_average = 0; + /* if (deviation2 < 1) deviation2 = 1;*/ + + large_gap_found = large_gap_found_ledge = large_gap_found_redge = FALSE; + no_gap_found = no_gap_found_ledge = no_gap_found_redge = FALSE; + stem_detected = FALSE; + + if (ppem < 11) + { + requirement1 = 4; + } + if (ppem > 18 ) + { + stem_match_requirement = height / 4; + if (stem_match_requirement < 3) stem_match_requirement = 3; + } + + smallest_width_ledge = smallest_width_redge = width * 256; + stem_matches = 0; + stem_matches_ledge = 0; + stem_matches_redge = 0; + last_matching_center = -1; + last_matching_ledge = -1; + last_matching_redge = -1; + + /* set currently looked at center to center value */ + this_center = center; + next_center = 0; + + /* For each center, compare with all other centers to see if others match the properties of this one */ + while ( next_center < num_centers ) + { + + /* calculate differences */ + center_difference_in_width = abs (centers[this_center].x - centers[next_center].x); + center_difference_in_height = abs (centers[this_center].y - centers[next_center].y); + x1_difference_in_width = abs (centers[this_center].x1 - centers[next_center].x1); + x2_difference_in_width = abs (centers[this_center].x2 - centers[next_center].x2); + + + /* property - stem center points that align */ + /* if the center is within range, the center is less than 1/2 the height away, and at least one edge is also within range */ + if ( center_difference_in_width < center_difference_in_height * deviation1 + && center_difference_in_height <= (FT_Int)bitmap->rows / 2 + /* prevents w from getting caught ---- but also kills m */ + && (x1_difference_in_width < center_difference_in_height * deviation2 + || x2_difference_in_width < center_difference_in_height * deviation2 ) + + ) + { + stem_matches += 1; + valid_center_average += centers[next_center].x; + /* try to find where the matching centers are far apart */ + if (last_matching_center >= 0 + && abs(centers[last_matching_center].y - centers[next_center].y) >= (FT_Int)bitmap->rows / 2) + large_gap_found = TRUE; + /* try to find where matching centers are next to each other */ + if (last_matching_center >= 0 + && abs(centers[last_matching_center].y - centers[next_center].y) == 1) + no_gap_found = TRUE; + last_matching_center = next_center; + } + + if (strategy_also_use_edge_detection_for_stems){ + /* property - stem left edge points that align */ + /* if the center is within range, the center is less than 1/2 the height away */ + if ( x1_difference_in_width < center_difference_in_height * deviation1 + && center_difference_in_height <= (FT_Int)bitmap->rows / 2 ) + { + stem_matches_ledge += 1; + /* may not need for edges */ + /*valid_center_average += centers[next_center].x; */ + + if (centers[next_center].x2 - centers[next_center].x1 < smallest_width_ledge ) + smallest_width_ledge = centers[next_center].x2 - centers[next_center].x1; + + /* try to find where the matching centers are far apart */ + if (last_matching_ledge >= 0 + && abs(centers[last_matching_ledge].y - centers[next_center].y) >= (FT_Int)bitmap->rows / 2) + large_gap_found_ledge = TRUE; + /* try to find where matching centers are next to each other */ + if (last_matching_ledge >= 0 + && abs(centers[last_matching_ledge].y - centers[next_center].y) == 1) + no_gap_found_ledge = TRUE; + last_matching_ledge = next_center; + } + } + + if (strategy_also_use_edge_detection_for_stems){ + /* property - stem right edge points that align */ + /* if the center is within range, the center is less than 1/2 the height away */ + if ( x2_difference_in_width < center_difference_in_height * deviation1 + && center_difference_in_height <= (FT_Int)bitmap->rows / 2 ) + { + stem_matches_redge += 1; + /* may not need for edges */ + /*valid_center_average += centers[next_center].x; */ + + if (centers[next_center].x2 - centers[next_center].x1 < smallest_width_redge ) + smallest_width_redge = centers[next_center].x2 - centers[next_center].x1; + + /* try to find where the matching centers are far apart */ + if (last_matching_redge >= 0 + && abs(centers[last_matching_redge].y - centers[next_center].y) >= (FT_Int)bitmap->rows / 2) + large_gap_found_redge = TRUE; + /* try to find where matching centers are next to each other */ + if (last_matching_redge >= 0 + && abs(centers[last_matching_redge].y - centers[next_center].y) == 1) + no_gap_found_redge = TRUE; + last_matching_redge = next_center; + } + } + + next_center++; + } + + if (stem_matches > 0 ) valid_center_average /= stem_matches; + + best_height = stem_matches; + + /* new version */ + if ( ( stem_matches >= stem_match_requirement + || ( ( (FT_Int)bitmap->rows <= 6 || ppem < 11) + && stem_matches >= 2 + && abs(valid_center_average - centers[center].x) < deviation1 /2 ) + /* try to catch tightly aligned stuff where the matching centers are next to each other only */ + || ( stem_matches == 2 + && abs(valid_center_average - centers[center].x) <= deviation1 /2 + && no_gap_found && ppem < 18 ) /* catches things like times 16 u but gets a lot of w's too */ + /* stem width is less than 1/3 of the bitmap width, or bitmap_width is small */ + ) + && + ( centers[center].x2 - centers[center].x1 < (m_horiAdvance * 12) / 2 + || m_horiAdvance * 12 <= columns_per_pixel * one_pixel ) ) + { + stem_detected = TRUE; + set_width_to = centers[center].x2 - centers[center].x1; + best_height = stem_matches; + set_center_to = centers[center].x; + + } + /* see if edges found anything */ + if (strategy_also_use_edge_detection_for_stems && !stem_detected) + { + if (( + /* Require no gap for edges */ + stem_matches_ledge >= stem_match_requirement && no_gap_found_ledge + /* stem width is less than 1/3 of the bitmap width, or bitmap_width is small */ + ) && ( centers[center].x2 - centers[center].x1 < (m_horiAdvance * 12) / 2 + || m_horiAdvance * 12 <= columns_per_pixel * one_pixel) + /* The stem occurs on the left side of glyph only */ + && centers[center].x < (m_horiAdvance * 12) / 2 + + ) + { + stem_detected = TRUE; + set_width_to = smallest_width_ledge; + best_height = stem_matches_ledge; + set_center_to = centers[center].x1 + set_width_to / 2; + stem_matches = stem_matches_ledge; + } + else if (( + /* Require no gap for edges */ + stem_matches_redge >= stem_match_requirement && no_gap_found_redge + /* stem width is less than 1/3 of the bitmap width, or bitmap_width is small */ + ) && ( centers[center].x2 - centers[center].x1 < (m_horiAdvance * 12) / 2 + || m_horiAdvance * 12 <= columns_per_pixel * one_pixel) + /* The stem occurs on the right side of glyph only */ + && centers[center].x > (m_horiAdvance * 12) / 2 + ) + { + stem_detected = TRUE; + set_width_to = smallest_width_redge; + best_height = stem_matches_redge; + set_center_to = centers[center].x2 - set_width_to / 2; + stem_matches = stem_matches_redge; + } + } + + + /*store and/or replace highest occurrences with 3 or more centers */ + /* because this matched, it will become the top dog regardless */ + if ( stem_detected ) + if ( stem_matches > possible_stems[0].height ) + { + /* if this is the first stem just go ahead */ + if (valid_possible_stems == 0) + { + valid_possible_stems = 1; + possible_stems[0].center = set_center_to; + possible_stems[0].count = stem_matches; + possible_stems[0].width = set_width_to; + possible_stems[0].height = stem_matches; + } + + /* otherwise, if there is already a stem */ + else if (valid_possible_stems == 1 ) + { + /* if the stem is within the range of existing one, replace existing one */ + + /* if the stem isn't within the range of this one swap it with next one first */ + if (abs(set_center_to - possible_stems[0].center) >= one_pixel * 2) + { + swap_stem ( &possible_stems[0], &possible_stems[1] ); + valid_possible_stems = 2; + } + possible_stems[0].center = set_center_to; + possible_stems[0].count = stem_matches; + possible_stems[0].width = set_width_to; + possible_stems[0].height = stem_matches; + } + + /* otherwise if there are already 2 stems */ + else if (valid_possible_stems >= 2 ) + { + /* if the stem is within the range of existing one, replace existing one */ + if ( abs(set_center_to - possible_stems[0].center) <= one_pixel * 2) + { + possible_stems[0].center = set_center_to; + possible_stems[0].count = stem_matches; + possible_stems[0].width = set_width_to; + possible_stems[0].height = stem_matches; + } + /* if the stem isn't within the range of this one */ + else + { + /* see if within range of next one and swap if so and proceed overwriting it */ + if ( abs(set_center_to - possible_stems[1].center) <= one_pixel * 2) + { + swap_stem ( &possible_stems[0], &possible_stems[1] ); + } + + /* otherwise see if in range of third one */ + else if ( abs(set_center_to - possible_stems[2].center) <= one_pixel * 2) + { + swap_stem ( &possible_stems[0], &possible_stems[2] ); + } + + /* otherwise this is the new top dog, so demote everything */ + else + { + swap_stem ( &possible_stems[1], &possible_stems[2] ); + swap_stem ( &possible_stems[0], &possible_stems[1] ); + valid_possible_stems += 1; + } + possible_stems[0].center = set_center_to; + possible_stems[0].count = stem_matches; + possible_stems[0].width = set_width_to; + possible_stems[0].height = stem_matches; + } + } + } + + else if ( stem_matches > possible_stems[1].height && set_center_to != 0) + { + + /* make sure it doesn't match the first stem */ + if ( abs(set_center_to - possible_stems[0].center) >= one_pixel * 2 ) + { + + /* if this is the second stem */ + if (valid_possible_stems == 1) valid_possible_stems = 2; + + /* otherwise if there is already a stem here */ + else if (valid_possible_stems >= 2 ) + { + /* if it doesn't match the second stem, proceed to swap out with the third */ + /* if it does, replace it */ + if ( abs(set_center_to - possible_stems[1].center) >= one_pixel * 2 ) + { + swap_stem ( &possible_stems[1], &possible_stems[2] ); + valid_possible_stems +=1; + } + } + possible_stems[1].center = set_center_to; + possible_stems[1].count = stem_matches; + possible_stems[1].width = set_width_to; + possible_stems[1].height = stem_matches; + } + } + + else if ( stem_matches > possible_stems[2].height && set_center_to != 0) + { + /* if it doesn't match the first or second one */ + if ( abs(set_center_to - possible_stems[0].center) >= one_pixel * 2 + && abs(set_center_to - possible_stems[1].center) >= one_pixel * 2) + + { + if (valid_possible_stems == 2) + { + valid_possible_stems += 1; + } + possible_stems[2].center = set_center_to; + possible_stems[2].count = stem_matches; + possible_stems[2].width = set_width_to; + possible_stems[1].height = stem_matches; + } + } + if (valid_possible_stems > 3) valid_possible_stems = 3; + + center++; + } + + /* promote to stem */ + if (valid_possible_stems > 0) + { + stems[0].center = possible_stems[0].center; + stems[0].count = possible_stems[0].count; + stems[0].width = possible_stems[0].width; + stems[0].height = possible_stems[0].height; + stems[0].generated = FALSE; + valid_stems++; + } + + if (valid_stems == 1 && valid_possible_stems > 1) + { + stems[1].center = possible_stems[1].center; + stems[1].count = possible_stems[1].count; + stems[1].width = possible_stems[1].width; + stems[1].height = possible_stems[1].height; + stems[1].generated = FALSE; + valid_stems++; + } + + if (valid_stems == 2 && valid_possible_stems > 2 && possible_stems[2].center != 0 ) + { + stems[2].center = possible_stems[2].center; + stems[2].count = possible_stems[2].count; + stems[2].width = possible_stems[2].width; + stems[2].height = possible_stems[2].height; + stems[2].generated = FALSE; + valid_stems++; + } + + /* sort stems in x direction */ + if ( valid_stems == 3) + { + if (stems[0].center > stems[1].center) + swap_stem ( &stems[0], &stems[1] ); + if (stems[0].center > stems[2].center) + swap_stem ( &stems[1], &stems[2] ); + if (stems[1].center > stems[2].center) + swap_stem ( &stems[1], &stems[2] ); + if (stems[0].center > stems[1].center) + swap_stem ( &stems[0], &stems[1] ); + + /* only look at first and last stem for now */ + swap_stem ( &stems[1], &stems[2] ); + } + + if (strategy_use_verdana_12_hack + && strcasestr(slot->face->family_name, "Verdana") + && ppem == 12 + && valid_stems == 1 + && (stems[0].center + m_horiBearingX * 12 - one_pixel < m_horiAdvance * 4 + ||stems[0].center + m_horiBearingX * 12 - one_pixel > m_horiAdvance * 8) ) + { + if (stems[0].center + m_horiBearingX * 12 - one_pixel < m_horiAdvance * 4) + { + stems[1].center = rightmost_point - one_pixel / 2; + stems[1].width = 1; + stems[1].generated = TRUE; + valid_stems += 1; + } + else + { + stems[1].center = leftmost_point + one_pixel / 2; + stems[1].width = 1; + stems[1].generated = TRUE; + valid_stems += 1; + } + strategy_always_use_distance_ceiling = TRUE; + } + + /* synthesize stems - Works, but needs work */ + if ( (strategy_synthesize_stems || known_stem_values->synth_stems) && valid_stems == 0 && ppem > 10 ) + { + /* if the leftmost segment's leftmost point is the same as the glyph's leftmost point, and it is of reasonable width, and is not on the top or bottom of the bitmap */ + if (leftmost_segment_not_extrema->x1 == leftmost_point_not_extrema + && abs(leftmost_segment_not_extrema->x2 - leftmost_segment_not_extrema->x1) + < (rightmost_point_not_extrema - leftmost_point_not_extrema)/3 + && leftmost_segment_not_extrema->y + < height && leftmost_segment_not_extrema->y > 1 ) + { + stems[valid_stems].center = (leftmost_segment_not_extrema->x2 + leftmost_segment_not_extrema->x1) / 2; + stems[valid_stems].width = leftmost_segment_not_extrema->x2 - leftmost_segment_not_extrema->x1; + stems[valid_stems].generated = TRUE; + valid_stems += 1; + } + + + if (rightmost_segment_not_extrema->x2 == rightmost_point_not_extrema + && abs(rightmost_segment_not_extrema->x2 - rightmost_segment_not_extrema->x1) + < (rightmost_point_not_extrema - leftmost_point_not_extrema)/3 + && rightmost_segment_not_extrema->y < height && rightmost_segment_not_extrema->y > 1 ) + { + stems[valid_stems].center = (rightmost_segment_not_extrema->x2 + rightmost_segment_not_extrema->x1) / 2; + stems[valid_stems].width = rightmost_segment_not_extrema->x2 - rightmost_segment_not_extrema->x1; + stems[valid_stems].generated = TRUE; + valid_stems += 1; + } + + } + + /* sort stems in x direction */ + if (valid_stems > 1 && stems[0].center > stems[1].center) + swap_stem ( &stems[0], &stems[1] ); + + if ( valid_stems == 0 && known_stem_values->stem_translating != 0 ) + { + *translate_value += known_stem_values->stem_translating; + + if (strategy_use_strengths ) + { + /* consider 1/2 pixel the max when strength is at 100%, unless translate is already greater than that */ + FT_Int strength_cutoff = 32; + if (abs(*translate_value) > strength_cutoff) strength_cutoff = *translate_value; + max_strength = (strength_cutoff * alignment_strength) / 100; + if (*translate_value < -max_strength) *translate_value = -max_strength; + else if (*translate_value > max_strength) *translate_value = max_strength; + } + } + else + /* Start snapping */ + { + FT_Int center_offset; + FT_Int modulus; + FT_Int delta, delta2; + FT_Long stem_distance = 1, new_distance = 1; + FT_Int distance_floor, distance_ceiling; + FT_Int translate_value2 = 0; + FT_Int main_stem = 0; + FT_Int lbearing = m_horiBearingX * 12; + FT_Int bitmap_stem_location = stems[0].center; + FT_Int advance_stem_location = bitmap_stem_location + lbearing - one_pixel; + FT_Int advance_width = m_horiAdvance * 12; + FT_Int original_advance_width = 12 * (slot->linearHoriAdvance >> 10); + FT_Int glyph_width = rightmost_point - leftmost_point; + FT_Int stem_width = stems[0].width; + FT_Int advance_leftmost_location = leftmost_point + lbearing - one_pixel; + FT_Int advance_rightmost_location = rightmost_point + lbearing - one_pixel; + +#define proposed_transformed_point(point) \ + point * (float)(new_distance) / (float)(stem_distance) \ + + *translate_value * 12 - ( stems[main_stem].center * (float)(new_distance) \ + / (float)(stem_distance) - stems[main_stem].center) + +#define proposed_translated_point(point) point + *translate_value * 12 + + center_offset = one_pixel / 2; /* half pixel */ + modulus = one_pixel; /* whole pixel */ + + /* Determine center_offset via known values */ + if (known_stem_values->stem_width >= 0) + { + if (known_stem_values->stem_width % 2 == 0) + { + center_offset = 0; + } + else + { + center_offset = one_pixel / 2; + } + } + /* otherwise do intelligent guessing, if set */ + else if ( strategy_auto_change_center_offset + && ppem >= STEM_WIDTH_2_PPEM + && stems[0].width < one_pixel * 1.45) + { + center_offset = one_pixel / 2; + } + else if ( strategy_auto_change_center_offset + && ppem >= STEM_WIDTH_2_PPEM + && stems[0].width >= one_pixel * 1.45 + && stems[0].width < one_pixel * 2.6) + { + center_offset = 0; + } + else if ( strategy_auto_change_center_offset + && ppem >= STEM_WIDTH_2_PPEM + && stems[0].width >= one_pixel * 2.6 + && stems[0].width < one_pixel * 3.6) + { + center_offset = one_pixel / 2; + } + else if ( strategy_auto_change_center_offset && ppem >= STEM_WIDTH_2_PPEM ) + center_offset = (one_pixel * ((((int)(stems[0].width + one_pixel / 2)) / one_pixel ) % 2) ) / 2; + + /* Snap to closest translate and scale values by default */ + if (valid_stems >= 1) + { + /* closest snapping point for stem 0 */ + delta = (stems[0].center + center_offset) % (modulus); + + if (delta < modulus / 2 ) *translate_value = ( - delta ) / (columns_per_pixel * 4); /* snap left */ + else *translate_value = (modulus -delta) / (columns_per_pixel * 4); /* snap right */ + } + + if (strategy_use_d_correction) + { + /* if the only stem is in the last 1/3 of glyph width, the advance is + * 6 pixels, the ppem 11, and doing so doesn't violate bitmap boundaries, + * force it to snap right */ + if (valid_stems == 1 && advance_stem_location > (advance_width * 2) / 3 + && advance_width == 6 * one_pixel + && rightmost_point + modulus - delta <= ( width - (columns_per_pixel * 2) / 3) * 256 + && ppem == 11 + ) + *translate_value = (modulus -delta) / (columns_per_pixel * 4); + } + + if (strategy_use_strengths ) + { + /* consider 1/2 pixel the max when strength is at 100%, unless translate is already greater than that */ + FT_Int strength_cutoff = 32; + if (abs(*translate_value) > strength_cutoff) strength_cutoff = *translate_value; + max_strength = (strength_cutoff * alignment_strength) / 100; + if (*translate_value < -max_strength) *translate_value = -max_strength; + else if (*translate_value > max_strength) *translate_value = max_strength; + } + + /* If 2 stems is detected, scale distance between in order to land on pixels */ + if ( valid_stems >= 2) + { + stem_distance = abs(stems[1].center - stems[0].center); + + delta = stem_distance % ( modulus ); + new_distance = stem_distance - delta; + + distance_floor = stem_distance - delta; + distance_ceiling = stem_distance + (modulus - delta); + + if (delta < modulus / 2 ) new_distance = distance_floor; + else new_distance = distance_ceiling; + + if ( columns_per_pixel == 3 && valid_stems == 3 && strategy_use_m_control && valid_stems == 3 + && (width - 2 * columns_per_pixel) > 6 * columns_per_pixel + && ppem > 8 + && (advance_stem_location - advance_leftmost_location) < stems[main_stem].width * 2 ) + { + FT_Int mod_factor = 2; /*Possibly use 2 only when compatible widths is on? */ + + if (verbose) printf ("USING M CONTROL "); + distance_floor = stem_distance - stem_distance % ( modulus * mod_factor) ; + distance_ceiling = distance_floor + modulus * mod_factor; + + new_distance = distance_ceiling; + + /* force certain ideal situations */ + /* these 2 are mostly safe to do */ + if (distance_ceiling + one_pixel * columns_per_pixel == advance_width + && (stem_width < one_pixel * 1.25 )) + new_distance = distance_ceiling; + /* NEED TO FIGURE OUT A WAY TO DETERMINE WHETHER THAT NUDGE IS UP OR DOWN */ + else if (stem_distance + one_pixel * 2.6 >= advance_width + && (stem_width < one_pixel * 1.25 )) + new_distance = distance_ceiling; + + if (proposed_transformed_point(leftmost_point) < one_third_pixel * 2 + || proposed_transformed_point(rightmost_point) > (width -2 ) * one_third_pixel) + new_distance = distance_floor; + + /* NEED TO IGNORE SERIF Ms HERE */ + /* perhaps check bitmap boundaries instead??? */ + if (strategy_bearing_correction && new_distance == distance_ceiling) + { + /* Correct if bearings are made substantially worse (more than 1/3 a pixel beyond advance) */ + if (proposed_transformed_point(advance_rightmost_location) > advance_width + one_third_pixel + && proposed_transformed_point(advance_rightmost_location) > advance_rightmost_location + && -proposed_transformed_point(advance_leftmost_location ) < advance_rightmost_location - advance_width + ) + new_distance = distance_floor; + } + + if ( known_stem_values->m >= 0 ) + { + if ( known_stem_values->m == 0 ) new_distance = distance_floor; + else new_distance = distance_ceiling; + } + + if ( (rightmost_point - leftmost_point) - ((rightmost_point * *scale_value) - (leftmost_point * *scale_value)) >= one_pixel * 1.5 ) + { + *scale_value = 1.0; + *translate_value = 0; + goto Exit; + } + + } + else if ( columns_per_pixel == 1 && valid_stems == 3 && strategy_use_m_control && valid_stems == 3 + && width >= 6 * columns_per_pixel + && ppem > 8 + && (advance_stem_location - advance_leftmost_location) < stems[main_stem].width * 2 ) + { + FT_Int mod_factor = 2; /*Possibly use 2 only when compatible widths is on? */ + + if (verbose) printf ("USING M CONTROL "); + distance_floor = stem_distance - stem_distance % ( modulus * mod_factor) ; + distance_ceiling = distance_floor + modulus * mod_factor; + + new_distance = distance_ceiling; + + /* force certain ideal situations */ + /* these 2 are mostly safe to do */ + if (distance_ceiling + one_pixel * columns_per_pixel == advance_width + && (stem_width < one_pixel * 1.25 )) new_distance = distance_ceiling; + /* NEED TO FIGURE OUT A WAY TO DETERMINE WHETHER THAT NUDGE IS UP OR DOWN */ + else if (stem_distance + one_pixel * 2.6 >= advance_width + && (stem_width < one_pixel * 1.25 )) + new_distance = distance_ceiling; + + if (proposed_transformed_point(leftmost_point) < 0 + || proposed_transformed_point(rightmost_point) > (width) * one_pixel - 2*one_third_pixel) + new_distance = distance_floor; + + /* NEED TO IGNORE SERIF Ms HERE */ + /* perhaps check bitmap boundaries instead??? */ + if (strategy_bearing_correction && new_distance == distance_ceiling) + { + /* Correct if bearings are made substantially worse (more than 1/3 a pixel beyond advance) */ + if (proposed_transformed_point(advance_rightmost_location) > advance_width + one_third_pixel + && proposed_transformed_point(advance_rightmost_location) > advance_rightmost_location + && -proposed_transformed_point(advance_leftmost_location ) < advance_rightmost_location - advance_width + ) + new_distance = distance_floor; + } + + if ( known_stem_values->m >= 0 ) + { + if ( known_stem_values->m == 0 ) new_distance = distance_floor; + else new_distance = distance_ceiling; + } + + + if ( (rightmost_point - leftmost_point) - ((rightmost_point * *scale_value) - (leftmost_point * *scale_value)) >= one_pixel * 1.5 ) + { + *scale_value = 1.0; + *translate_value = 0; + goto Exit; + } + + } + else + { + if (strategy_fit_to_width) + { + new_distance = advance_width - 3 * one_pixel; + } + else if (known_stem_values->stem_scaling >= 0) + { + if (known_stem_values->stem_scaling > 0) new_distance = distance_ceiling; + else new_distance = distance_floor; + + /* enforce advance width boundaries */ + /* TOO RESTRICTIVE ON SERIF FONTS */ + if ( proposed_transformed_point(advance_rightmost_location) >= advance_width + || proposed_transformed_point(advance_leftmost_location) <= 0 + ) new_distance = distance_floor; + + /* enforce literal bitmap boundaries if there is no translate room */ + if ( ( proposed_transformed_point(rightmost_point) >= width * 256 + || proposed_transformed_point(leftmost_point ) <= one_pixel ) + && new_distance + one_pixel * 3 > advance_width ) + new_distance = distance_floor; + + } + else if (strategy_translate_using_closest_stem) + { + /* closest snapping point for stem 1 */ + delta2 = (stems[1].center + center_offset) % (modulus); + + if (delta2 < modulus / 2 ) translate_value2 = ( - delta2 ) / (columns_per_pixel * 4); /* snap left */ + else translate_value2 = (modulus -delta2) / (columns_per_pixel * 4); /* snap right */ + + if (abs(translate_value2) < abs(*translate_value)) + { + *translate_value = translate_value2; + main_stem = 1; + } + + } + else if (strategy_scale_to_closest_centers) + { + /* closest snapping point for stem 0 */ + delta = (stems[0].center + center_offset) % (modulus); + delta2 = (stems[1].center + center_offset) % (modulus); + + if (delta < modulus / 2 ) new_distance = delta + stem_distance; /* stretch left */ + else new_distance = delta - modulus + stem_distance; /* stretch right */ + + if (delta2 < modulus / 2 ) new_distance -= delta2; /* stretch left */ + else new_distance += modulus - delta2; /* stretch right */ + + } + else if (strategy_scale_to_closest_centers_up_only) + { + FT_Int net_change = 0; + + /* closest snapping point for stem 0 */ + delta = (stems[0].center + center_offset) % (modulus); + delta2 = (stems[1].center + center_offset) % (modulus); + + if (delta < modulus / 2 ) net_change = delta; /* stretch left */ + else net_change = -(modulus - delta); /* stretch right */ + + if (delta2 < modulus / 2 ) net_change -= delta2; /* stretch left */ + else net_change += modulus - delta2; /* stretch right */ + + if (net_change > 0 + && proposed_transformed_point(advance_rightmost_location) < advance_width + && proposed_transformed_point(advance_leftmost_location) > 0 + ) new_distance = distance_ceiling; + } + + else if (strategy_always_use_distance_ceiling) + { + if ( proposed_transformed_point(advance_rightmost_location) < advance_width + && proposed_transformed_point(advance_leftmost_location) > 0 + ) + new_distance = distance_ceiling; + } + } + + if (strategy_use_strengths) + { + FT_Int strength_cutoff = center_offset; + delta2 = new_distance - stem_distance; + if (abs(delta2) > strength_cutoff) strength_cutoff = delta2; + + max_strength = (strength_cutoff * fitting_strength) / 100; + if (delta2 < -max_strength ) new_distance = stem_distance - max_strength; + else if (delta2 > max_strength) new_distance = stem_distance + max_strength; + } + + *scale_value = (float)(new_distance + 0) / (float)(stem_distance + 0 ); + *translate_value = *translate_value - ((float)(stems[main_stem].center * (float)new_distance) / (float)stem_distance - stems[main_stem].center) / 12; + + if (valid_stems == 2) *embolden_value = (64.0 / *scale_value - 64.0); + if (valid_stems == 3) *embolden_value = (64.0 / *scale_value - 64.0) / 1.5; + } + + if (verbose) printf ("%lu stems:", valid_stems); + + if (valid_stems == 1 && verbose) + printf ("1 stem: bitmapwidth:%d glyphwidth:%f glyph_width:%f center:%f bearing:%f advance:%f lhadvance:%f stemwidth:%f %d %d", + (width - 6) / columns_per_pixel, + (float)m_width / 64.0, + (float)glyph_width / (float)one_pixel, + (float)((float)advance_stem_location) / (float)one_pixel, + (float)m_horiBearingX / 64.0, + (float)m_horiAdvance / 64.0, + (float)linearHoriAdvance / 64.0, + (float)stems[0].width / (float)one_pixel, + advance_width, original_advance_width + ); + else if (valid_stems >= 2 && verbose) + printf ("%lu stems: bitmapwidth:%d center1:%f center2:%f difference:%f bearing:%f advance:%f advstemloc:%f ", + valid_stems, + (width - 6) / columns_per_pixel, + ((float)advance_stem_location) / (float)one_pixel, + ((float)advance_stem_location + (float)abs(stems[1].center - stems[0].center)) / (float)one_pixel, + ((float)abs(stems[1].center - stems[0].center)) / (float)one_pixel, + (float)m_horiBearingX / 64.0, + (float)m_horiAdvance / 64.0, + (float)advance_stem_location / (float)one_pixel); + + if (strategy_bearing_correction) + { + /* Correct if negative bearings are made substantially worse (more than 1/3 a pixel) */ + if (proposed_transformed_point(advance_rightmost_location) > advance_width + && proposed_transformed_point(advance_rightmost_location) > advance_rightmost_location + && -proposed_transformed_point(advance_leftmost_location ) < advance_rightmost_location - advance_width + && *translate_value > one_third_pixel / (columns_per_pixel * 4) ) + { + *translate_value -=64 ; + if (verbose) printf ("TRANSLATING -64 "); + } + } + + if ( strategy_use_verdana_12_hack + && strcasestr(slot->face->family_name, "Verdana") + && ppem == 12 + && *scale_value == 1.0 && valid_stems == 0 + && height < 8 + && advance_rightmost_location * 1.1 < advance_width ) + *scale_value = 1.1; + goto Exit; + } - if ( matrix ) - FT_Outline_Transform( &slot->outline, matrix ); + Exit: - if ( delta ) - FT_Outline_Translate( &slot->outline, delta->x, delta->y ); +#define transformed_point( point ) point * *scale_value + *translate_value * 12 + + if (strategy_correct_out_of_bounds_outlines) + { + /* Correct if outside bitmap */ + if (transformed_point(rightmost_point) >= width * 256 - 2 * one_third_pixel + && transformed_point(leftmost_point ) > one_pixel + 2 * one_third_pixel ) + { + *translate_value -=64 ; + } + else if (transformed_point(leftmost_point) <= one_pixel / 2 + && transformed_point(rightmost_point ) <= width * 256 -(one_pixel + one_pixel / 2) ) + { + *translate_value += 64; + } + } + + STVALUES + + free(segments); + free(leftmost_segment); + free(rightmost_segment); + + free(known_stem_values); + free(stems); + free(possible_stems); + free(leftmost_stem); + free(rightmost_stem); + + free(centers); - Exit: - return error; } - /* return the glyph's control box */ + /* Gamma correction */ static void - ft_smooth_get_cbox( FT_Renderer render, - FT_GlyphSlot slot, - FT_BBox* cbox ) + _ft_lcd_gamma_correction_correction ( FT_Bitmap* bitmap, + FT_Render_Mode mode, + FT_GlyphSlot slot, + float gamma_correction_lt, + float gamma_correction_value) { - FT_MEM_ZERO( cbox, sizeof ( *cbox ) ); + if ( gamma_correction_value != 1.0 ) + { + FT_UInt width = (FT_UInt)bitmap->width; + FT_UInt height = (FT_UInt)bitmap->rows; + FT_Byte* line = bitmap->buffer; + float ppem = (float)slot->face->size->metrics.x_ppem; - if ( slot->format == render->glyph_format ) - FT_Outline_Get_CBox( &slot->outline, cbox ); + if ( !slot->face || !slot->face->size ) return; + + if (ppem >= 5 ) + for (height = (FT_UInt)bitmap->rows; height > 0; height--, line += bitmap->pitch ) + { + FT_UInt xx; + + for ( xx = 0; xx < width; xx += 1 ) + { + /*normal*/ + /*line[xx] = gamma2 ( line[xx], gamma_correction_value );*/ + + /* sloped */ + /*line[xx] = gamma2 ( line[xx], gamma_correction_value - 5 + * (1-gamma_correction_value)/(gamma_correction_lt -5) + + ((1-gamma_correction_value)/(gamma_correction_lt -5)) * ppem );*/ + + /* 1/3-sloped */ + line[xx] = gamma2 ( line[xx], gamma_correction_value - 5 + * ((1-gamma_correction_value)/(3*(gamma_correction_lt -5))) + * + ((1-gamma_correction_value)/(3*(gamma_correction_lt -5))) * ppem ); + } + } + } } +#endif + /* convert a slot's glyph image into a bitmap */ static FT_Error @@ -104,19 +2871,406 @@ { FT_Error error; FT_Outline* outline = NULL; + FT_Outline* outline_orig = NULL; FT_BBox cbox; - FT_Pos width, height, pitch; + FT_Pos width=0, height=0, pitch=0, ppem; #ifndef FT_CONFIG_OPTION_SUBPIXEL_RENDERING FT_Pos height_org, width_org; #endif - FT_Bitmap* bitmap; - FT_Memory memory; + FT_Bitmap* bitmap = 0; + FT_Memory memory = 0; FT_Int hmul = mode == FT_RENDER_MODE_LCD; FT_Int vmul = mode == FT_RENDER_MODE_LCD_V; - FT_Pos x_shift, y_shift, x_left, y_top; + FT_Pos x_shift = 0, y_shift = 0, x_left = 0, y_top = 0; FT_Raster_Params params; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + FT_Matrix scaleMat; + FT_Long translate_value = 0; + float scale_value = 1.0; + FT_Int align_called = 0; + + + int chromeos_style_sharpening_strength = 0; + int checked_chromeos_style_sharpening_strength = 0; + int alignment_strength = 0; + int fitting_strength = 0; + FT_UInt checked_alignment_strength = 0; + FT_UInt checked_fitting_strength = 0; + FT_UInt checked_fringe_filter_strength = 0; + int fringe_filter_strength = 0; + FT_UInt checked_grayscale_filter_strength = 0; + int grayscale_filter_strength = 0; + + FT_UInt checked_autohint_horizontal_stem_darken_strength = 0; + int autohint_horizontal_stem_darken_strength = 0; + + FT_UInt checked_autohint_vertical_stem_darken_strength = 0; + int autohint_vertical_stem_darken_strength = 0; + + int windows_style_sharpening_strength = 0; + FT_UInt checked_windows_style_sharpening_strength = 0; + float gamma_correction_value = 1; + float gamma_correction_lt = 0; + FT_UInt checked_gamma_correction_value = 0; + + FT_Int brightness_value = 0.0; + FT_UInt checked_brightness_value = 0; + + FT_Int contrast_value = 0.0; + FT_UInt checked_contrast_value = 0; + + FT_Int snapping_sliding_scale_value = 0; + FT_UInt checked_snapping_sliding_scale_value = 0; + + FT_Int global_embolden_x_value = 0; + FT_UInt checked_global_embolden_x_value = 0; + + FT_Int global_embolden_y_value = 0; + FT_UInt checked_global_embolden_y_value = 0; + + FT_Int bold_embolden_x_value = 0; + FT_UInt checked_bold_embolden_x_value = 0; + + FT_Int bold_embolden_y_value = 0; + FT_UInt checked_bold_embolden_y_value = 0; + + FT_Byte chromeos_cutoff; + double chromeos_gamma_value; + + float embolden_value = 0.0; + FT_Bool autohinted = FALSE; + + FT_UInt autohint_minimum_stem_height = 0; + FT_UInt checked_autohint_minimum_stem_height = 0; + + int checked_use_various_tweaks_env = 0; + FT_Bool use_various_tweaks = FALSE; + + int cur_width; + char *cur_width_env = getenv( "CUR_WIDTH" ); + + const FT_Int MIN_PPEM = 1; + /*const FT_Int MAX_PPEM = 100; */ + + int checked_use_known_settings_on_selected_fonts_env = 0; + FT_Bool use_known_settings_on_selected_fonts = FALSE; + + if ( slot->face && slot->face->size && slot->face->size->metrics.x_ppem ) + ppem = slot->face->size->metrics.x_ppem; + else ppem = 0; + + if ( cur_width_env != NULL ){ + sscanf ( cur_width_env, "%d", &cur_width ); + if (cur_width != 0) autohinted = TRUE; + } + + if ( checked_use_known_settings_on_selected_fonts_env == 0 ) + { + char *use_known_settings_on_selected_fonts_env = getenv( "INFINALITY_FT_USE_KNOWN_SETTINGS_ON_SELECTED_FONTS" ); + if ( use_known_settings_on_selected_fonts_env != NULL ) + { + if ( strcasecmp(use_known_settings_on_selected_fonts_env, "default" ) != 0 ) + { + if ( strcasecmp(use_known_settings_on_selected_fonts_env, "true") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "1") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "on") == 0) + use_known_settings_on_selected_fonts = TRUE; + else if ( strcasecmp(use_known_settings_on_selected_fonts_env, "yes") == 0) + use_known_settings_on_selected_fonts = TRUE; + } + } + checked_use_known_settings_on_selected_fonts_env = 1; + } + + if ( checked_use_various_tweaks_env == 0 ) + { + char *use_various_tweaks_env = getenv( "INFINALITY_FT_USE_VARIOUS_TWEAKS" ); + if ( use_various_tweaks_env != NULL ) + { + if ( strcasecmp(use_various_tweaks_env, "default" ) != 0 ) + { + if ( strcasecmp(use_various_tweaks_env, "true") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "1") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "on") == 0) + use_various_tweaks = TRUE; + else if ( strcasecmp(use_various_tweaks_env, "yes") == 0) + use_various_tweaks = TRUE; + } + } + checked_use_various_tweaks_env = 1; + } + + if ( checked_autohint_minimum_stem_height == 0) + { + char *autohint_minimum_stem_height_env = getenv( "INFINALITY_FT_AUTOHINT_MINIMUM_STEM_WIDTH" ); + if ( autohint_minimum_stem_height_env != NULL ) + { + sscanf ( autohint_minimum_stem_height_env, "%u", &autohint_minimum_stem_height ); + if (autohint_minimum_stem_height > 100 ) autohint_minimum_stem_height = 100; + else if (autohint_minimum_stem_height < 0 ) autohint_minimum_stem_height = 0; + } + checked_autohint_minimum_stem_height = 1; + } + + if ( checked_snapping_sliding_scale_value == 0) + { + char *snapping_sliding_scale_env = getenv ( "INFINALITY_FT_STEM_SNAPPING_SLIDING_SCALE" ); + if ( snapping_sliding_scale_env != NULL ) + { + sscanf ( snapping_sliding_scale_env, "%d", &snapping_sliding_scale_value ); + if (snapping_sliding_scale_value > MAX_PPEM ) snapping_sliding_scale_value = 0; + else if (snapping_sliding_scale_value < 0 ) snapping_sliding_scale_value = 0; + + if (snapping_sliding_scale_value < 11 && snapping_sliding_scale_value > 0 ) snapping_sliding_scale_value = 11; + } + checked_snapping_sliding_scale_value = 1; + } + + if ( checked_alignment_strength == 0) + { + char *alignment_strength_env = getenv ( "INFINALITY_FT_STEM_ALIGNMENT_STRENGTH" ); + if ( alignment_strength_env != NULL ) + { + sscanf ( alignment_strength_env, "%d", &alignment_strength ); + if (alignment_strength > 100 ) alignment_strength = 100; + else if (alignment_strength < 0 ) alignment_strength = 0; + } + if (alignment_strength > 100 ) alignment_strength = 100; + checked_alignment_strength = 1; + if (snapping_sliding_scale_value != 0) + alignment_strength = sliding_scale ( 10, snapping_sliding_scale_value, alignment_strength, 100, ppem); + } + + if ( checked_fitting_strength == 0) + { + char *fitting_strength_env = getenv( "INFINALITY_FT_STEM_FITTING_STRENGTH" ); + if ( fitting_strength_env != NULL ) + { + sscanf ( fitting_strength_env, "%d", &fitting_strength ); + if (fitting_strength > 100 ) fitting_strength = 100; + else if (fitting_strength < 0 ) fitting_strength = 0; + } + if (fitting_strength > 100 ) fitting_strength = 100; + checked_fitting_strength = 1; + if (snapping_sliding_scale_value != 0) + fitting_strength = sliding_scale ( 10, snapping_sliding_scale_value, fitting_strength, 100, ppem); + } + + if ( checked_chromeos_style_sharpening_strength == 0) + { + char *chromeos_style_sharpening_strength_env = getenv( "INFINALITY_FT_CHROMEOS_STYLE_SHARPENING_STRENGTH" ); + if ( chromeos_style_sharpening_strength_env != NULL ) + { + sscanf ( chromeos_style_sharpening_strength_env, "%d", &chromeos_style_sharpening_strength ); + if (chromeos_style_sharpening_strength > 100 ) + chromeos_style_sharpening_strength = 100; + else if (chromeos_style_sharpening_strength < 0 ) + chromeos_style_sharpening_strength = 0; + } + if (ppem > 10) + chromeos_style_sharpening_strength = + (chromeos_style_sharpening_strength * ppem) / 10; + if (chromeos_style_sharpening_strength > 100 ) + chromeos_style_sharpening_strength = 100; + checked_chromeos_style_sharpening_strength = 1; + } + + + if ( checked_brightness_value == 0) + { + char *brightness_env = getenv( "INFINALITY_FT_BRIGHTNESS" ); + if ( brightness_env != NULL ) + { + sscanf ( brightness_env, "%d", &brightness_value ); + if (brightness_value > 100 ) + brightness_value = 100; + else if (brightness_value < -100 ) + brightness_value = 0; + } + checked_brightness_value = 1; + } + + if ( checked_contrast_value == 0) + { + char *contrast_env = getenv( "INFINALITY_FT_CONTRAST" ); + if ( contrast_env != NULL ) + { + sscanf ( contrast_env, "%d", &contrast_value ); + if (contrast_value > 100 ) + contrast_value = 100; + else if (contrast_value < -100 ) + contrast_value = 100; + } + checked_contrast_value = 1; + } + + if ( checked_windows_style_sharpening_strength == 0) + { + char *windows_style_sharpening_strength_env = getenv( "INFINALITY_FT_WINDOWS_STYLE_SHARPENING_STRENGTH" ); + if ( windows_style_sharpening_strength_env != NULL ) + { + sscanf ( windows_style_sharpening_strength_env, "%d", &windows_style_sharpening_strength ); + if (windows_style_sharpening_strength > 100 ) windows_style_sharpening_strength = 100; + else if (windows_style_sharpening_strength < 0 ) windows_style_sharpening_strength = 0; + } + /* Decrease the effect slightly in order to have a more linear increase in sharpness */ + windows_style_sharpening_strength = + (( windows_style_sharpening_strength * windows_style_sharpening_strength ) / 100 + windows_style_sharpening_strength) / 2; + checked_windows_style_sharpening_strength = 1; + } + + if ( checked_gamma_correction_value == 0 ) + { + char *gamma_correction_value_env = getenv( "INFINALITY_FT_GAMMA_CORRECTION" ); + if ( gamma_correction_value_env != NULL ) + { + float f1, f2; + + if ( strcasecmp(gamma_correction_value_env, "default" ) != 0) + { + sscanf ( gamma_correction_value_env, "%f %f", &f1, &f2 ); + gamma_correction_lt = f1; + gamma_correction_value = f2 / 100.0; + } + if ( gamma_correction_value < .01 ) gamma_correction_value = 1.0; + } + checked_gamma_correction_value = 1; + } + + /* set gamma value to 1 if out of range */ + if ( slot->face && slot->face->size && slot->face->size->metrics.x_ppem ) + { + if ( slot->face->size->metrics.x_ppem >= gamma_correction_lt ) + { + gamma_correction_value = 1; + } + } + else gamma_correction_value = 1; + + + if ( checked_fringe_filter_strength == 0) + { + char *fringe_filter_strength_env = getenv( "INFINALITY_FT_FRINGE_FILTER_STRENGTH" ); + if ( fringe_filter_strength_env != NULL ) + { + sscanf ( fringe_filter_strength_env, "%d", &fringe_filter_strength ); + if (fringe_filter_strength > 100 ) fringe_filter_strength = 100; + else if (fringe_filter_strength < 0 ) fringe_filter_strength = 0; + } + checked_fringe_filter_strength = 1; + } + + + if ( checked_grayscale_filter_strength == 0) + { + char *grayscale_filter_strength_env = getenv( "INFINALITY_FT_GRAYSCALE_FILTER_STRENGTH" ); + if ( grayscale_filter_strength_env != NULL ) + { + sscanf ( grayscale_filter_strength_env, "%d", &grayscale_filter_strength ); + if (grayscale_filter_strength > 100 ) grayscale_filter_strength = 100; + else if (grayscale_filter_strength < 0 ) grayscale_filter_strength = 0; + } + checked_grayscale_filter_strength = 1; + } + + + if ( checked_autohint_horizontal_stem_darken_strength == 0) + { + char *autohint_horizontal_stem_darken_strength_env = getenv( "INFINALITY_FT_AUTOHINT_HORIZONTAL_STEM_DARKEN_STRENGTH" ); + if ( autohint_horizontal_stem_darken_strength_env != NULL ) + { + sscanf ( autohint_horizontal_stem_darken_strength_env, "%d", &autohint_horizontal_stem_darken_strength ); + if (autohint_horizontal_stem_darken_strength > 100 ) autohint_horizontal_stem_darken_strength = 100; + else if (autohint_horizontal_stem_darken_strength < 0 ) autohint_horizontal_stem_darken_strength = 0; + } + checked_autohint_horizontal_stem_darken_strength = 1; + } + + if ( checked_autohint_vertical_stem_darken_strength == 0) + { + char *autohint_vertical_stem_darken_strength_env = getenv( "INFINALITY_FT_AUTOHINT_VERTICAL_STEM_DARKEN_STRENGTH" ); + if ( autohint_vertical_stem_darken_strength_env != NULL ) + { + sscanf ( autohint_vertical_stem_darken_strength_env, "%d", &autohint_vertical_stem_darken_strength ); + if (autohint_vertical_stem_darken_strength > 100 ) autohint_vertical_stem_darken_strength = 100; + else if (autohint_horizontal_stem_darken_strength < 0 ) autohint_vertical_stem_darken_strength = 0; + } + checked_autohint_vertical_stem_darken_strength = 1; + } + + if ( checked_global_embolden_x_value == 0) + { + char *global_embolden_x_env = getenv ( "INFINALITY_FT_GLOBAL_EMBOLDEN_X_VALUE" ); + if ( global_embolden_x_env != NULL ) + { + sscanf ( global_embolden_x_env, "%d", &global_embolden_x_value ); + if (global_embolden_x_value > 128 ) global_embolden_x_value = 128; + else if (global_embolden_x_value < -128 ) global_embolden_x_value = -128; + } + checked_global_embolden_x_value = 1; + } + + if ( checked_global_embolden_y_value == 0) + { + char *global_embolden_y_env = getenv ( "INFINALITY_FT_GLOBAL_EMBOLDEN_Y_VALUE" ); + if ( global_embolden_y_env != NULL ) + { + sscanf ( global_embolden_y_env, "%d", &global_embolden_y_value ); + if (global_embolden_y_value > 128 ) global_embolden_y_value = 128; + else if (global_embolden_y_value < -128 ) global_embolden_y_value = -128; + } + checked_global_embolden_y_value = 1; + } + + + if ( checked_bold_embolden_x_value == 0) + { + char *bold_embolden_x_env = getenv ( "INFINALITY_FT_BOLD_EMBOLDEN_X_VALUE" ); + if ( bold_embolden_x_env != NULL ) + { + sscanf ( bold_embolden_x_env, "%d", &bold_embolden_x_value ); + if (bold_embolden_x_value > 128 ) bold_embolden_x_value = 128; + else if (bold_embolden_x_value < -128 ) bold_embolden_x_value = -128; + + } + checked_bold_embolden_x_value = 1; + } + + if ( checked_bold_embolden_y_value == 0) + { + char *bold_embolden_y_env = getenv ( "INFINALITY_FT_BOLD_EMBOLDEN_Y_VALUE" ); + if ( bold_embolden_y_env != NULL ) + { + sscanf ( bold_embolden_y_env, "%d", &bold_embolden_y_value ); + if (bold_embolden_y_value > 128 ) bold_embolden_y_value = 128; + else if (bold_embolden_y_value < -128 ) bold_embolden_y_value = -128; + + } + checked_bold_embolden_y_value = 1; + } + + + + if( use_various_tweaks && slot->face && slot->face->style_name ) + { + /* needs to also check for artifical italics */ + if ( strcasestr(slot->face->style_name, "Italic") + || strcasestr(slot->face->style_name, "Oblique") ) + { + windows_style_sharpening_strength = 0; + chromeos_style_sharpening_strength = 0; + } + } + + /*if (fitting_strength == 100) scale_value = 1.1;*/ + +#endif /* check glyph image format */ if ( slot->format != render->glyph_format ) @@ -129,92 +3283,174 @@ if ( mode != required_mode ) return Smooth_Err_Cannot_Render_Glyph; - outline = &slot->outline; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET +RERENDER: + if (align_called == 1){ - /* translate the outline to the new origin if needed */ - if ( origin ) - FT_Outline_Translate( outline, origin->x, origin->y ); + scaleMat.xx = FT_FixedFromFloat(scale_value); + scaleMat.xy = 0; + scaleMat.yx = 0; + scaleMat.yy = (1 << 16); - /* compute the control box, and grid fit it */ - FT_Outline_Get_CBox( outline, &cbox ); + FT_Outline_Copy(outline_orig, outline); - cbox.xMin = FT_PIX_FLOOR( cbox.xMin ); - cbox.yMin = FT_PIX_FLOOR( cbox.yMin ); - cbox.xMax = FT_PIX_CEIL( cbox.xMax ); - cbox.yMax = FT_PIX_CEIL( cbox.yMax ); + if (scale_value != 1.0) + FT_Outline_Transform( outline, &scaleMat ); - if ( cbox.xMin < 0 && cbox.xMax > FT_INT_MAX + cbox.xMin ) - { - FT_ERROR(( "ft_smooth_render_generic: glyph too large:" - " xMin = %d, xMax = %d\n", - cbox.xMin >> 6, cbox.xMax >> 6 )); - return Smooth_Err_Raster_Overflow; + FT_Outline_Translate( outline, translate_value+0, 0 ); + + FT_Outline_EmboldenXY( outline, embolden_value, 0 ); } else - width = ( cbox.xMax - cbox.xMin ) >> 6; - - if ( cbox.yMin < 0 && cbox.yMax > FT_INT_MAX + cbox.yMin ) { - FT_ERROR(( "ft_smooth_render_generic: glyph too large:" - " yMin = %d, yMax = %d\n", - cbox.yMin >> 6, cbox.yMax >> 6 )); - return Smooth_Err_Raster_Overflow; +#endif + outline = &slot->outline; +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + /* Need to get this PRIOR to embolden, otherwise bad things happen */ + FT_Outline_Get_CBox( outline, &cbox ); + + /* Various hacks that need to be turned into a new rule set */ + /*if ( !autohinted + && use_known_settings_on_selected_fonts + && mode == FT_RENDER_MODE_LCD && slot->face->family_name && slot->face->style_name + && ( strcasestr(slot->face->family_name, "Courier New" ) + && ( strcasestr(slot->face->style_name, "Regular" ) + || strcasestr(slot->face->style_name, "Italic" ) ) ) ) + FT_Outline_Embolden( outline, 24 );*/ + + if (!autohinted + && use_known_settings_on_selected_fonts + && mode == FT_RENDER_MODE_LCD && slot->face->family_name && slot->face->style_name + && strcasestr(slot->face->family_name, "Times New Roman" ) + && strcasestr(slot->face->style_name, "Italic" ) ) + FT_Outline_EmboldenXY( outline, 12, 0 ); + + if ( use_known_settings_on_selected_fonts + && autohinted && mode == FT_RENDER_MODE_LCD && slot->face->family_name && slot->face->style_name + && strcasestr(slot->face->family_name, "FreeSerif" ) + && strcasestr(slot->face->style_name, "Italic" ) ) + FT_Outline_EmboldenXY( outline, 8, 0 ); + + if( global_embolden_x_value != 0 || global_embolden_y_value != 0 ) + FT_Outline_EmboldenXY( outline, global_embolden_x_value, global_embolden_y_value ); + + if( (bold_embolden_x_value != 0 || bold_embolden_y_value != 0) + && (slot->face->style_name + && ( strcasestr(slot->face->style_name, "Bold") + || strcasestr(slot->face->style_name, "Black") ) + || ( slot->face->style_flags + && slot->face->style_flags & FT_STYLE_FLAG_BOLD ) ) ) + FT_Outline_EmboldenXY( outline, bold_embolden_x_value, bold_embolden_y_value ); + + FT_Outline_Copy(outline, outline_orig); } - else - height = ( cbox.yMax - cbox.yMin ) >> 6; - bitmap = &slot->bitmap; - memory = render->root.memory; + /* translate the outline to the new origin if needed */ + if (align_called == 0) + { + FT_Pos enlarge_cbox = 0; -#ifndef FT_CONFIG_OPTION_SUBPIXEL_RENDERING - width_org = width; - height_org = height; -#endif + /* enlarge for grayscale rendering */ + if ( mode == FT_RENDER_MODE_NORMAL ) enlarge_cbox = 64; - /* release old bitmap buffer */ - if ( slot->internal->flags & FT_GLYPH_OWN_BITMAP ) - { - FT_FREE( bitmap->buffer ); - slot->internal->flags &= ~FT_GLYPH_OWN_BITMAP; - } + if ( origin ) + FT_Outline_Translate( outline, origin->x, origin->y ); - /* allocate new one */ - pitch = width; - if ( hmul ) - { - width = width * 3; - pitch = FT_PAD_CEIL( width, 4 ); - } + /* compute the control box, and grid fit it */ + /*FT_Outline_Get_CBox( outline, &cbox );*/ - if ( vmul ) - height *= 3; + cbox.xMin = FT_PIX_FLOOR( cbox.xMin - enlarge_cbox ); + cbox.yMin = FT_PIX_FLOOR( cbox.yMin ); + cbox.xMax = FT_PIX_CEIL( cbox.xMax + enlarge_cbox ); + cbox.yMax = FT_PIX_CEIL( cbox.yMax ); +#else + if ( origin ) + FT_Outline_Translate( outline, origin->x, origin->y ); - x_shift = (FT_Int) cbox.xMin; - y_shift = (FT_Int) cbox.yMin; - x_left = (FT_Int)( cbox.xMin >> 6 ); - y_top = (FT_Int)( cbox.yMax >> 6 ); + /* compute the control box, and grid fit it */ + FT_Outline_Get_CBox( outline, &cbox ); -#ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING + cbox.xMin = FT_PIX_FLOOR( cbox.xMin ); + cbox.yMin = FT_PIX_FLOOR( cbox.yMin ); + cbox.xMax = FT_PIX_CEIL( cbox.xMax ); + cbox.yMax = FT_PIX_CEIL( cbox.yMax ); +#endif - if ( slot->library->lcd_filter_func ) - { - FT_Int extra = slot->library->lcd_extra; + if ( cbox.xMin < 0 && cbox.xMax > FT_INT_MAX + cbox.xMin ) + { + FT_ERROR(( "ft_smooth_render_generic: glyph too large:" + " xMin = %d, xMax = %d\n", + cbox.xMin >> 6, cbox.xMax >> 6 )); + return Smooth_Err_Raster_Overflow; + } + else + width = (FT_UInt)( ( cbox.xMax - cbox.xMin ) >> 6 ); + if ( cbox.yMin < 0 && cbox.yMax > FT_INT_MAX + cbox.yMin ) + { + FT_ERROR(( "ft_smooth_render_generic: glyph too large:" + " yMin = %d, yMax = %d\n", + cbox.yMin >> 6, cbox.yMax >> 6 )); + return Smooth_Err_Raster_Overflow; + } + else + height = (FT_UInt)( ( cbox.yMax - cbox.yMin ) >> 6 ); + + bitmap = &slot->bitmap; + memory = render->root.memory; + +#ifndef FT_CONFIG_OPTION_SUBPIXEL_RENDERING + width_org = width; + height_org = height; +#endif + + /* release old bitmap buffer */ + if ( slot->internal->flags & FT_GLYPH_OWN_BITMAP ) + { + FT_FREE( bitmap->buffer ); + slot->internal->flags &= ~FT_GLYPH_OWN_BITMAP; + } + /* allocate new one */ + pitch = width; if ( hmul ) { - x_shift -= 64 * ( extra >> 1 ); - width += 3 * extra; - pitch = FT_PAD_CEIL( width, 4 ); - x_left -= extra >> 1; + width = width * 3; + pitch = FT_PAD_CEIL( width, 4 ); } if ( vmul ) + height *= 3; + + x_shift = (FT_Int) cbox.xMin; + y_shift = (FT_Int) cbox.yMin; + x_left = (FT_Int)( cbox.xMin >> 6 ); + y_top = (FT_Int)( cbox.yMax >> 6 ); + +#ifdef FT_CONFIG_OPTION_SUBPIXEL_RENDERING + + if ( slot->library->lcd_filter_func ) { - y_shift -= 64 * ( extra >> 1 ); - height += 3 * extra; - y_top += extra >> 1; + FT_Int extra = slot->library->lcd_extra; + + + if ( hmul ) + { + x_shift -= 64 * ( extra >> 1 ); + width += 3 * extra; + pitch = FT_PAD_CEIL( width, 4 ); + x_left -= extra >> 1; + } + + if ( vmul ) + { + y_shift -= 64 * ( extra >> 1 ); + height += 3 * extra; + y_top += extra >> 1; + } } +#endif +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET } #endif @@ -239,6 +3475,9 @@ bitmap->pitch = pitch; /* translate outline to render it into the bitmap */ +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if (align_called == 0) +#endif FT_Outline_Translate( outline, -x_shift, -y_shift ); if ( FT_ALLOC( bitmap->buffer, (FT_ULong)pitch * height ) ) @@ -288,9 +3527,107 @@ vec->y /= 3; } +#ifdef FT_CONFIG_OPTION_INFINALITY_PATCHSET + if ( ppem <= MAX_PPEM && ppem >= MIN_PPEM ) + { + if ( align_called == 0 && (alignment_strength > 0 || fitting_strength > 0)) + _lcd_stem_align ( bitmap, mode, slot, &translate_value, &scale_value, + alignment_strength, fitting_strength, &embolden_value ); + + if ((translate_value != 0 || scale_value != 1.0) && align_called == 0) + { + align_called = 1; + goto RERENDER; + } + + if ( mode == FT_RENDER_MODE_LCD ) + { + + if (fringe_filter_strength > 0 /*&& autohinted*/) + _ft_lcd_fringe_filter( bitmap, mode, fringe_filter_strength, slot->library ); + + /*if (autohinted) + _ft_lcd_stem_end_filter( bitmap, mode, 100, slot->library );*/ + + if ( gamma_correction_lt > 0 && gamma_correction_value != 1.0 ) + _ft_lcd_gamma_correction_correction( bitmap, mode, slot, gamma_correction_lt, gamma_correction_value ); + + chromeos_cutoff = (FT_Byte)(0.5 * 255.0) * (chromeos_style_sharpening_strength / 100.0); + chromeos_gamma_value = 1; + + if (chromeos_style_sharpening_strength > 0) + _ft_lcd_chromeos_sharpen( bitmap, mode, chromeos_cutoff, chromeos_gamma_value ); + + if (ppem > 8) + if (windows_style_sharpening_strength > 0) + _ft_lcd_windows_sharpen( bitmap, mode, windows_style_sharpening_strength, slot->library ); + + if (autohinted && (cur_width * 100) / 64 > autohint_horizontal_stem_darken_strength + && autohint_horizontal_stem_darken_strength != 0) + autohint_horizontal_stem_darken_strength = (cur_width * 100) / 64; + + if (autohint_horizontal_stem_darken_strength > 100) + autohint_horizontal_stem_darken_strength = 100; + + /* only do on autohinted fonts */ + /* Necessary to do on some non-thin fonts, which is why it is outside */ + /* of the below conditional */ + if (autohint_horizontal_stem_darken_strength > 0 && autohinted ) + _ft_lcd_darken_x ( bitmap, mode, autohint_horizontal_stem_darken_strength, slot->library ); + + /* Enhance thin fonts */ + if (autohinted) + { + /* if forcibly set use that, otherwise make a good estimate */ + if ( !_ft_bitmap_bc ( bitmap, (float)get_brightness(slot->face->family_name, ppem) / 300.0, + (float)get_contrast(slot->face->family_name, ppem) / 300.0)) + { + FT_Bool is_fixed_name = FALSE; + if ( slot->face->family_name + && strcasestr(slot->face->family_name, "Mono") ) + is_fixed_name = TRUE; + + /* Darken vertical stems */ + _ft_lcd_darken_y ( bitmap, mode, autohint_vertical_stem_darken_strength, slot->library); + + /* Adjust brightness and contrast automatically based on stem width */ + if (cur_width != 0 && cur_width < 30 ) cur_width = 30; + if (cur_width >= 30 && cur_width <= 60 ) + { + float ppem_factor = sliding_scale ( 5, 11, 0.0, 1.0, ppem); + float brightness_factor = sliding_scale ( 30, 52, -.3, 0.0, cur_width); + float contrast_factor = sliding_scale ( 30, 52, .45, 0.0, cur_width); + _ft_bitmap_bc ( bitmap, ppem_factor * brightness_factor, ppem_factor * contrast_factor); + + /* Only cap variable width thin-stemmed fonts */ + if (!FT_IS_FIXED_WIDTH( slot->face ) && !is_fixed_name) + _ft_bitmap_cap ( bitmap, (cur_width * 150) / 64, slot->library ); + } + } + } + + + if ( slot->library->lcd_filter_func ) + slot->library->lcd_filter_func( bitmap, mode, slot->library ); + + if (grayscale_filter_strength > 0) + _ft_lcd_grayscale_filter( bitmap, mode, grayscale_filter_strength, slot->library ); + + } + + /* Global values */ + if (brightness_value != 0 || contrast_value != 0) + _ft_bitmap_bc ( bitmap, (float)brightness_value / 300.0, (float)contrast_value / 300.0); + + FT_Outline_Done( slot->library, outline_orig ); + } + else if ( mode == FT_RENDER_MODE_LCD && slot->library->lcd_filter_func ) + slot->library->lcd_filter_func( bitmap, mode, slot->library ); +#else if ( slot->library->lcd_filter_func ) slot->library->lcd_filter_func( bitmap, mode, slot->library ); +#endif /* FT_CONFIG_OPTION_INFINALITY_PATCHSET */ #else /* !FT_CONFIG_OPTION_SUBPIXEL_RENDERING */ /* render outline into bitmap */ diff --git a/src/truetype/truetype.c b/src/truetype/truetype.c index 4bd1209..a6e4ba7 100644 --- a/src/truetype/truetype.c +++ b/src/truetype/truetype.c @@ -27,6 +27,7 @@ #ifdef TT_USE_BYTECODE_INTERPRETER #include "ttinterp.c" +#include "ttsubpix.c" #endif #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT diff --git a/src/truetype/ttgload.c b/src/truetype/ttgload.c index 22cbc09..5b7cbe9 100644 --- a/src/truetype/ttgload.c +++ b/src/truetype/ttgload.c @@ -32,6 +32,7 @@ #endif #include "tterrors.h" +#include "ttsubpix.h" /*************************************************************************/ @@ -149,6 +150,15 @@ loader->top_bearing = top_bearing; loader->vadvance = advance_height; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( loader->exec ) + loader->exec->sph_tweak_flags = 0; + + /* this may not be the right place for this, but it works */ + if ( loader->exec && loader->exec->ignore_x_mode ) + sph_set_tweaks( loader, glyph_index ); +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + if ( !loader->linear_def ) { loader->linear_def = 1; @@ -813,6 +823,13 @@ loader->pp4 = zone->cur[zone->n_points - 1]; } +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( loader->exec->sph_tweak_flags & SPH_TWEAK_DEEMBOLDEN ) + FT_Outline_EmboldenXY( &loader->gloader->current.outline, -24, 0 ); + + else if ( loader->exec->sph_tweak_flags & SPH_TWEAK_EMBOLDEN ) + FT_Outline_EmboldenXY( &loader->gloader->current.outline, 24, 0 ); +#endif return TT_Err_Ok; } @@ -835,6 +852,13 @@ FT_Outline* outline; FT_Int n_points; + TT_Face face = (TT_Face)loader->face; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_String* family = face->root.family_name; + int ppem = loader->size->metrics.x_ppem; + FT_String* style = face->root.style_name; + float x_scale_factor = 1.0; +#endif outline = &gloader->current.outline; n_points = outline->n_points; @@ -889,6 +913,26 @@ loader->zone.n_points + 4 ); } +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* scale, but only if enabled and only if TT hinting is being used */ + if ( IS_HINTED( loader->load_flags ) ) + x_scale_factor = scale_test_tweak( face, family, ppem, style, + loader->glyph_index, X_SCALING_Rules, + X_SCALING_RULES_SIZE ); + /* scale the glyph */ + if ( ( loader->load_flags & FT_LOAD_NO_SCALE ) == 0 || + x_scale_factor != 1.0 ) + { + FT_Vector* vec = outline->points; + FT_Vector* limit = outline->points + n_points; + FT_Fixed x_scale = ((TT_Size)loader->size)->metrics.x_scale * x_scale_factor; + FT_Fixed y_scale = ((TT_Size)loader->size)->metrics.y_scale; + + /* compensate for any scaling by de/emboldening */ + if ( x_scale_factor != 1.0 && ppem > 11 ) + FT_Outline_EmboldenXY( outline, + (FT_Int) ( 16.0 * (float)ppem * ( 1.0 - x_scale_factor) ), 0 ); +#else /* scale the glyph */ if ( ( loader->load_flags & FT_LOAD_NO_SCALE ) == 0 ) { @@ -896,7 +940,7 @@ FT_Vector* limit = outline->points + n_points; FT_Fixed x_scale = ((TT_Size)loader->size)->metrics.x_scale; FT_Fixed y_scale = ((TT_Size)loader->size)->metrics.y_scale; - +#endif for ( ; vec < limit; vec++ ) { @@ -1648,12 +1692,26 @@ { FT_Byte* widthp; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Bool ignore_x_mode; + + + ignore_x_mode = FT_BOOL( FT_LOAD_TARGET_MODE( loader->load_flags ) != + FT_RENDER_MODE_MONO ); +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ widthp = tt_face_get_device_metrics( face, size->root.metrics.x_ppem, glyph_index ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( widthp && + ( ( ignore_x_mode && loader->exec->compatible_widths ) || + !ignore_x_mode || + SPH_OPTION_BITMAP_WIDTHS ) ) +#else if ( widthp ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ glyph->metrics.horiAdvance = *widthp << 6; } @@ -1848,6 +1906,15 @@ { TT_ExecContext exec; FT_Bool grayscale; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Bool subpixel_hinting; + FT_Bool grayscale_hinting; +#if 0 + FT_Bool compatible_widths; + FT_Bool symmetrical_smoothing; + FT_Bool bgr; +#endif +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ if ( !size->cvt_ready ) @@ -1865,11 +1932,88 @@ if ( !exec ) return TT_Err_Could_Not_Find_Context; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + + subpixel_hinting = FT_BOOL( ( FT_LOAD_TARGET_MODE( load_flags ) + != FT_RENDER_MODE_MONO ) && + SPH_OPTION_SET_SUBPIXEL ); + + if ( subpixel_hinting ) + grayscale = grayscale_hinting = FALSE; + + else if ( SPH_OPTION_SET_GRAYSCALE ) + { + grayscale = grayscale_hinting = TRUE; + subpixel_hinting = FALSE; + } + + if ( FT_IS_TRICKY( glyph->face ) ) + { + subpixel_hinting = grayscale_hinting = FALSE; + } + + exec->ignore_x_mode = subpixel_hinting || grayscale_hinting; + exec->rasterizer_version = SPH_OPTION_SET_RASTERIZER_VERSION; + if ( exec->sph_tweak_flags & SPH_TWEAK_RASTERIZER_35 ) + exec->rasterizer_version = 35; + +#if 1 + exec->compatible_widths = SPH_OPTION_SET_COMPATIBLE_WIDTHS; + exec->symmetrical_smoothing = FALSE; + exec->bgr = FALSE; +#else /* 0 */ + exec->compatible_widths = + FT_BOOL( FT_LOAD_TARGET_MODE( load_flags ) != + TT_LOAD_COMPATIBLE_WIDTHS ); + exec->symmetrical_smoothing = + FT_BOOL( FT_LOAD_TARGET_MODE( load_flags ) != + TT_LOAD_SYMMETRICAL_SMOOTHING ); + exec->bgr = + FT_BOOL( FT_LOAD_TARGET_MODE( load_flags ) != + TT_LOAD_BGR ); +#endif /* 0 */ + +#else /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + grayscale = FT_BOOL( FT_LOAD_TARGET_MODE( load_flags ) != FT_RENDER_MODE_MONO ); +#endif /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + TT_Load_Context( exec, face, size ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + + /* a change from mono to subpixel rendering (and vice versa) */ + /* requires a re-execution of the CVT program */ + if ( subpixel_hinting != exec->subpixel_hinting ) + { + FT_UInt i; + + + exec->subpixel_hinting = subpixel_hinting; + + for ( i = 0; i < size->cvt_size; i++ ) + size->cvt[i] = FT_MulFix( face->cvt[i], size->ttmetrics.scale ); + tt_size_run_prep( size, pedantic ); + } + + /* a change from mono to grayscale rendering (and vice versa) */ + /* requires a re-execution of the CVT program */ + if ( grayscale != exec->grayscale_hinting ) + { + FT_UInt i; + + + exec->grayscale_hinting = grayscale_hinting; + + for ( i = 0; i < size->cvt_size; i++ ) + size->cvt[i] = FT_MulFix( face->cvt[i], size->ttmetrics.scale ); + tt_size_run_prep( size, pedantic ); + } + +#else /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* a change from mono to grayscale rendering (and vice versa) */ /* requires a re-execution of the CVT program */ if ( grayscale != exec->grayscale ) @@ -1887,6 +2031,8 @@ tt_size_run_prep( size, pedantic ); } +#endif /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* see whether the cvt program has disabled hinting */ if ( exec->GS.instruct_control & 1 ) load_flags |= FT_LOAD_NO_HINTING; diff --git a/src/truetype/ttinterp.c b/src/truetype/ttinterp.c index 3a8680f..b1bc6c0 100644 --- a/src/truetype/ttinterp.c +++ b/src/truetype/ttinterp.c @@ -27,13 +27,16 @@ #include FT_SYSTEM_H #include "ttinterp.h" - #include "tterrors.h" +#include "ttsubpix.h" #ifdef TT_USE_BYTECODE_INTERPRETER +#define xxxSPH_DEBUG +#define xxxSPH_DEBUG_MORE_VERBOSE + #define TT_MULFIX FT_MulFix #define TT_MULDIV FT_MulDiv #define TT_MULDIV_NO_ROUND FT_MulDiv_No_Round @@ -153,11 +156,11 @@ #define NORMalize( x, y, v ) \ Normalize( EXEC_ARG_ x, y, v ) -#define SET_SuperRound( scale, flags ) \ - SetSuperRound( EXEC_ARG_ scale, flags ) +#define SET_SuperRound( scale, flags, res ) \ + SetSuperRound( EXEC_ARG_ scale, flags, res ) -#define ROUND_None( d, c ) \ - Round_None( EXEC_ARG_ d, c ) +#define ROUND_None( d, c, e ) \ + Round_None( EXEC_ARG_ d, c, e ) #define INS_Goto_CodeRange( range, ip ) \ Ins_Goto_CodeRange( EXEC_ARG_ range, ip ) @@ -168,8 +171,8 @@ #define CUR_Func_move_orig( z, p, d ) \ CUR.func_move_orig( EXEC_ARG_ z, p, d ) -#define CUR_Func_round( d, c ) \ - CUR.func_round( EXEC_ARG_ d, c ) +#define CUR_Func_round( d, c, e ) \ + CUR.func_round( EXEC_ARG_ d, c, e ) #define CUR_Func_read_cvt( index ) \ CUR.func_read_cvt( EXEC_ARG_ index ) @@ -1850,6 +1853,10 @@ if ( v != 0 ) { +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( !CUR.ignore_x_mode || + ( CUR.sph_tweak_flags & SPH_TWEAK_ALLOW_X_DMOVE ) ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ zone->cur[point].x += TT_MULDIV( distance, v * 0x10000L, CUR.F_dot_P ); @@ -1932,6 +1939,10 @@ { FT_UNUSED_EXEC; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( !CUR.ignore_x_mode || + ( CUR.sph_tweak_flags & SPH_TWEAK_ALLOW_X_DMOVEX ) ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ zone->cur[point].x += distance; zone->tags[point] |= FT_CURVE_TAG_TOUCH_X; } @@ -1994,6 +2005,8 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* The compensated distance. */ /* */ @@ -2005,11 +2018,13 @@ /* */ static FT_F26Dot6 Round_None( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; FT_UNUSED_EXEC; + FT_UNUSED( resolution ); if ( distance >= 0 ) @@ -2024,6 +2039,7 @@ if ( val > 0 ) val = 0; } + return val; } @@ -2041,12 +2057,15 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ static FT_F26Dot6 Round_To_Grid( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; @@ -2055,15 +2074,15 @@ if ( distance >= 0 ) { - val = distance + compensation + 32; + val = distance + compensation + 32 / resolution; if ( distance && val > 0 ) - val &= ~63; + val &= ~( 64 / resolution - 1 ); else val = 0; } else { - val = -FT_PIX_ROUND( compensation - distance ); + val = -FT_PIX_ROUND_GRID( compensation - distance, resolution ); if ( val > 0 ) val = 0; } @@ -2085,12 +2104,15 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ static FT_F26Dot6 Round_To_Half_Grid( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; @@ -2099,13 +2121,15 @@ if ( distance >= 0 ) { - val = FT_PIX_FLOOR( distance + compensation ) + 32; + val = FT_PIX_FLOOR_GRID( distance + compensation, resolution ) + + 32 / resolution; if ( distance && val < 0 ) val = 0; } else { - val = -( FT_PIX_FLOOR( compensation - distance ) + 32 ); + val = -( FT_PIX_FLOOR_GRID( compensation - distance, resolution ) + + 32 / resolution ); if ( val > 0 ) val = 0; } @@ -2127,12 +2151,15 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ static FT_F26Dot6 Round_Down_To_Grid( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; @@ -2143,13 +2170,13 @@ { val = distance + compensation; if ( distance && val > 0 ) - val &= ~63; + val &= ~( 64 / resolution - 1 ); else val = 0; } else { - val = -( ( compensation - distance ) & -64 ); + val = -( ( compensation - distance ) & -( 64 / resolution ) ); if ( val > 0 ) val = 0; } @@ -2171,12 +2198,15 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ static FT_F26Dot6 Round_Up_To_Grid( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; @@ -2185,15 +2215,15 @@ if ( distance >= 0 ) { - val = distance + compensation + 63; + val = distance + compensation + ( 64 / resolution - 1 ); if ( distance && val > 0 ) - val &= ~63; + val &= ~( 64 / resolution - 1 ); else val = 0; } else { - val = - FT_PIX_CEIL( compensation - distance ); + val = -FT_PIX_CEIL_GRID( compensation - distance, resolution ); if ( val > 0 ) val = 0; } @@ -2215,12 +2245,15 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ static FT_F26Dot6 Round_To_Double_Grid( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; @@ -2229,15 +2262,15 @@ if ( distance >= 0 ) { - val = distance + compensation + 16; + val = distance + compensation + 16 / resolution; if ( distance && val > 0 ) - val &= ~31; + val &= ~( 32 / resolution - 1 ); else val = 0; } else { - val = -FT_PAD_ROUND( compensation - distance, 32 ); + val = -FT_PAD_ROUND( compensation - distance, 32 / resolution ); if ( val > 0 ) val = 0; } @@ -2259,6 +2292,8 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ @@ -2270,10 +2305,13 @@ /* */ static FT_F26Dot6 Round_Super( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; + FT_UNUSED( resolution ); + if ( distance >= 0 ) { @@ -2309,6 +2347,8 @@ /* */ /* compensation :: The engine compensation. */ /* */ + /* resolution :: The number of grid lines per pixel. */ + /* */ /* */ /* Rounded distance. */ /* */ @@ -2318,10 +2358,13 @@ /* */ static FT_F26Dot6 Round_Super_45( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ) + FT_F26Dot6 compensation, + FT_Int resolution ) { FT_F26Dot6 val; + FT_UNUSED( resolution ); + if ( distance >= 0 ) { @@ -2404,13 +2447,19 @@ /* Sets Super Round parameters. */ /* */ /* */ - /* GridPeriod :: Grid period */ - /* selector :: SROUND opcode */ + /* GridPeriod :: The grid period. */ + /* */ + /* selector :: The SROUND opcode. */ + /* */ + /* resolution :: The number of grid lines per pixel. */ /* */ static void SetSuperRound( EXEC_OP_ FT_F26Dot6 GridPeriod, - FT_Long selector ) + FT_Long selector, + FT_Int resolution ) { + FT_UNUSED( resolution ); + switch ( (FT_Int)( selector & 0xC0 ) ) { case 0: @@ -3080,13 +3129,13 @@ #define DO_SROUND \ - SET_SuperRound( 0x4000, args[0] ); \ + SET_SuperRound( 0x4000, args[0], 1 ); \ CUR.GS.round_state = TT_Round_Super; \ CUR.func_round = (TT_Round_Func)Round_Super; #define DO_S45ROUND \ - SET_SuperRound( 0x2D41, args[0] ); \ + SET_SuperRound( 0x2D41, args[0], 1 ); \ CUR.GS.round_state = TT_Round_Super_45; \ CUR.func_round = (TT_Round_Func)Round_Super_45; @@ -3257,12 +3306,12 @@ args[0] = ( args[0] != args[1] ); -#define DO_ODD \ - args[0] = ( ( CUR_Func_round( args[0], 0 ) & 127 ) == 64 ); +#define DO_ODD \ + args[0] = ( ( CUR_Func_round( args[0], 0, 1 ) & 127 ) == 64 ); -#define DO_EVEN \ - args[0] = ( ( CUR_Func_round( args[0], 0 ) & 127 ) == 0 ); +#define DO_EVEN \ + args[0] = ( ( CUR_Func_round( args[0], 0, 1 ) & 127 ) == 0 ); #define DO_AND \ @@ -3311,6 +3360,34 @@ #define DO_CEILING \ args[0] = FT_PIX_CEIL( args[0] ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + +#define DO_RS \ + { \ + FT_ULong I = (FT_ULong)args[0]; \ + \ + \ + if ( BOUNDSL( I, CUR.storeSize ) ) \ + { \ + if ( CUR.pedantic_hinting ) \ + ARRAY_BOUND_ERROR; \ + else \ + args[0] = 0; \ + } \ + else \ + { \ + /* subpixel hinting - avoid Typeman Dstroke and */ \ + /* IStroke and Vacuform rounds */ \ + \ + if ( CUR.compatibility_mode && \ + ( I == 24 || I == 22 || I == 8 ) ) \ + args[0] = 0; \ + else \ + args[0] = CUR.storage[I]; \ + } \ + } + +#else /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ #define DO_RS \ { \ @@ -3330,6 +3407,8 @@ args[0] = CUR.storage[I]; \ } +#endif /* !TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + #define DO_WS \ { \ @@ -3405,15 +3484,17 @@ CUR.error = TT_Err_Debug_OpCode; -#define DO_ROUND \ - args[0] = CUR_Func_round( \ - args[0], \ - CUR.tt_metrics.compensations[CUR.opcode - 0x68] ); +#define DO_ROUND \ + args[0] = CUR_Func_round( \ + args[0], \ + CUR.tt_metrics.compensations[CUR.opcode - 0x68], \ + 1 ); -#define DO_NROUND \ - args[0] = ROUND_None( args[0], \ - CUR.tt_metrics.compensations[CUR.opcode - 0x6C] ); +#define DO_NROUND \ + args[0] = ROUND_None( args[0], \ + CUR.tt_metrics.compensations[CUR.opcode - 0x6C], \ + 1 ); #define DO_MAX \ @@ -4587,6 +4668,24 @@ TT_DefRecord* rec; TT_DefRecord* limit; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING +#if 0 + int opcode_pattern[4][12] = { + /* VacuFormRound function */ + {0x45,0x23,0x46,0x60,0x20}, + /* inline delta function 1 */ + {0x4B,0x53,0x23,0x4B,0x51,0x5A,0x58,0x38,0x1B,0x21,0x21,0x59}, + /* inline delta function 2 */ + {0x4B,0x54,0x58,0x38,0x1B,0x5A,0x21,0x21,0x59}, + /* diagonal stroke function */ + {0x20,0x20,0x40,0x60,0x47,0x40,0x23,0x42}, + }; + int opcode_patterns = 4; + int i; + int opcode_pointer[4] = {0,0,0,0}; +#endif +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* some font programs are broken enough to redefine functions! */ /* We will then parse the current table. */ @@ -4620,10 +4719,11 @@ return; } - rec->range = CUR.curRange; - rec->opc = (FT_UInt16)n; - rec->start = CUR.IP + 1; - rec->active = TRUE; + rec->range = CUR.curRange; + rec->opc = (FT_UInt16)n; + rec->start = CUR.IP + 1; + rec->active = TRUE; + rec->inline_delta = FALSE; if ( n > CUR.maxFunc ) CUR.maxFunc = (FT_UInt16)n; @@ -4633,6 +4733,78 @@ while ( SKIP_Code() == SUCCESS ) { +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING +#if 0 +#ifdef SPH_DEBUG_MORE_VERBOSE + printf ("Opcode: %d ", CUR.opcode); +#endif + + for ( i = 0; i < opcode_patterns; i++ ) + { + if ( CUR.opcode == opcode_pattern[i][opcode_pointer[i]] ) + { +#ifdef SPH_DEBUG_MORE_VERBOSE + printf( "function %d, opcode ptrn: %d" + " op# %d: %d FOUND \n", + n, i, opcode_pointer[i], CUR.opcode ); +#endif + opcode_pointer[i] += 1; + + if ( i == 0 && opcode_pointer[0] == 5 ) + { + + CUR.inline_delta_funcs[CUR.num_delta_funcs] = n; + CUR.num_delta_funcs++; +#ifdef SPH_DEBUG + printf( "Vacuform Round FUNCTION %d detected\n", n); +#endif + /*rec->active = FALSE;*/ + opcode_pointer[i] = 0; + } + + if ( i == 1 && opcode_pointer[1] == 12 ) + { + CUR.inline_delta_funcs[CUR.num_delta_funcs] = n; + CUR.num_delta_funcs++; +#ifdef SPH_DEBUG + printf( "inline delta FUNCTION1 %d detected\n", + n, CUR.num_delta_funcs); +#endif + rec->inline_delta = TRUE; + opcode_pointer[i] = 0; + } + + if ( i == 2 && opcode_pointer[1] == 9 ) + { + CUR.inline_delta_funcs[CUR.num_delta_funcs] = n; + CUR.num_delta_funcs++; + rec->inline_delta = TRUE; +#ifdef SPH_DEBUG + printf( "inline delta2 FUNCTION2 %d detected\n", + n, CUR.num_delta_funcs); +#endif + opcode_pointer[i] = 0; + } + + if ( i == 4 && opcode_pointer[1] == 8 ) + { + CUR.inline_delta_funcs[CUR.num_delta_funcs] = n; + CUR.num_delta_funcs++; + /*rec->active = FALSE;*/ +#ifdef SPH_DEBUG + printf( "diagonal stroke function %d detected\n", + n, CUR.num_delta_funcs); +#endif + opcode_pointer[i] = 0; + } + } + + else + opcode_pointer[i] = 0; + } +#endif +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + switch ( CUR.opcode ) { case 0x89: /* IDEF */ @@ -4676,6 +4848,15 @@ CUR.step_ins = FALSE; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* + * CUR.ignore_x_mode may be turned off prior to function calls. This + * ensures it is turned back on. + */ + CUR.ignore_x_mode = ( CUR.subpixel_hinting || CUR.grayscale_hinting ) + && !( CUR.sph_tweak_flags & SPH_TWEAK_PIXEL_HINTING ); +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + if ( pRec->Cur_Count > 0 ) { CUR.callTop++; @@ -4709,6 +4890,10 @@ TT_CallRec* pCrec; TT_DefRecord* def; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Bool oldF; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* first of all, check the index */ @@ -4746,6 +4931,20 @@ if ( !def->active ) goto Fail; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* This is test code used to detect inline delta functions */ + oldF = def->inline_delta; + if ( CUR.ignore_x_mode ) + { + if ( def->inline_delta ) + CUR.in_delta_function = TRUE; + } + +#ifdef SPH_DEBUG + printf("Entering function %d\n", F); +#endif +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* check the call stack */ if ( CUR.callTop >= CUR.callSize ) { @@ -4767,6 +4966,13 @@ def->start ); CUR.step_ins = FALSE; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + CUR.in_delta_function = oldF; + +#ifdef SPH_DEBUG + printf("Leaving function %d\n", F); +#endif +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ return; Fail: @@ -4787,6 +4993,10 @@ TT_CallRec* pCrec; TT_DefRecord* def; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Bool oldF; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* first of all, check the index */ F = args[1]; @@ -4823,6 +5033,15 @@ if ( !def->active ) goto Fail; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + oldF = def->inline_delta; + if ( CUR.ignore_x_mode ) + { + if ( def->inline_delta ) + CUR.in_delta_function = TRUE; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* check stack */ if ( CUR.callTop >= CUR.callSize ) { @@ -4846,6 +5065,11 @@ CUR.step_ins = FALSE; } + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + CUR.in_delta_function = oldF; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + return; Fail: @@ -5195,6 +5419,12 @@ } } +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* Disable Type 2 Vacuform Rounds - e.g. Arial Narrow */ + if ( CUR.ignore_x_mode && FT_ABS( D ) == 64 ) + D += 1; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + args[0] = D; } @@ -5691,7 +5921,12 @@ if ( CUR.GS.freeVector.x != 0 ) { - CUR.zp2.cur[point].x += dx; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( !CUR.ignore_x_mode || + ( CUR.ignore_x_mode && + ( CUR.sph_tweak_flags & SPH_TWEAK_ALLOW_X_MOVE_ZP2 ) ) ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + CUR.zp2.cur[point].x += dx; if ( touch ) CUR.zp2.tags[point] |= FT_CURVE_TAG_TOUCH_X; } @@ -5872,6 +6107,9 @@ { FT_F26Dot6 dx, dy; FT_UShort point; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Int B1, B2; +#endif if ( CUR.top < CUR.GS.loop + 1 ) @@ -5917,7 +6155,77 @@ } } else +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + { + /* If not using ignore_x_mode rendering, allow ZP2 move. */ + /* If inline deltas aren't allowed, skip ZP2 move. */ + /* If using ignore_x_mode rendering, allow ZP2 point move if: */ + /* - freedom vector is y and compatibility_mode is off */ + /* - the glyph is composite and the move is in the Y direction */ + /* - the glyph is specifically set to allow SHPIX moves */ + /* - the move is on a previously Y-touched point */ + + if ( CUR.ignore_x_mode ) + { + /* save point for later comparison */ + if ( CUR.GS.freeVector.y != 0 ) + B1 = CUR.zp2.cur[point].y; + else + B1 = CUR.zp2.cur[point].x; + + if ( CUR.GS.freeVector.y != 0 && + ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_INLINE_DELTAS ) ) + goto Skip; + + if ( CUR.ignore_x_mode && + !CUR.compatibility_mode && CUR.GS.freeVector.y != 0 ) + MOVE_Zp2_Point( point, dx, dy, TRUE ); + + else if ( CUR.ignore_x_mode && CUR.compatibility_mode ) + { + if ( CUR.ignore_x_mode && + ( CUR.sph_tweak_flags & SPH_TWEAK_ROUND_NONPIXEL_Y_MOVES ) ) + { + dx = FT_PIX_ROUND ( B1 + dx ) - B1; + dy = FT_PIX_ROUND ( B1 + dy ) - B1; + } + + if ( !( CUR.sph_tweak_flags & SPH_TWEAK_ALWAYS_SKIP_DELTAP ) && + ( ( CUR.is_composite && CUR.GS.freeVector.y != 0 ) || + ( CUR.zp2.tags[point] & FT_CURVE_TAG_TOUCH_Y ) || + ( CUR.sph_tweak_flags & SPH_TWEAK_DO_SHPIX ) ) + ) + MOVE_Zp2_Point( point, dx, dy, TRUE ); + } + + /* save new point */ + if ( CUR.GS.freeVector.y != 0 ) + B2 = CUR.zp2.cur[point].y; + else B2 = CUR.zp2.cur[point].x; + + /* reverse any disallowed moves */ + if ( ( ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_NONPIXEL_Y_MOVES ) && + CUR.GS.freeVector.y != 0 && + B1 % 64 != 0 && + B2 % 64 != 0 && B1 != B2 ) || + ( ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_OFFPIXEL_Y_MOVES ) && + CUR.GS.freeVector.y != 0 && + B1 % 64 == 0 && + B2 % 64 != 0 && B1 != B2 ) ) + { +#ifdef SPH_DEBUG + printf( "Reversing ZP2 move\n" ); +#endif + MOVE_Zp2_Point( point, -dx, -dy, TRUE ); + } + } + else + MOVE_Zp2_Point( point, dx, dy, TRUE ); + } + Skip: +#else MOVE_Zp2_Point( point, dx, dy, TRUE ); +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ CUR.GS.loop--; } @@ -5939,7 +6247,18 @@ { FT_UShort point; FT_F26Dot6 distance; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Int gridlines_per_pixel = 1; + + if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.x != 0 ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_X; + else if ( CUR.GS.freeVector.y != 0 ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_Y; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ point = (FT_UShort)args[0]; @@ -5963,6 +6282,15 @@ distance = CUR_Func_project( CUR.zp1.cur + point, CUR.zp0.cur + CUR.GS.rp0 ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* subpixel hinting - make MSIRP respect CVT cut-in; */ + if ( CUR.ignore_x_mode && + CUR.GS.freeVector.x != 0 && + FT_ABS( distance - args[1] ) >= + CUR.GS.control_value_cutin / gridlines_per_pixel ) + distance = args[1]; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + CUR_Func_move( &CUR.zp1, point, args[1] - distance ); CUR.GS.rp1 = CUR.GS.rp0; @@ -5985,7 +6313,21 @@ FT_UShort point; FT_F26Dot6 cur_dist, distance; + FT_Int gridlines_per_pixel = 1; + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.x != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_X; + + else if ( CUR.GS.freeVector.y != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_Y; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ point = (FT_UShort)args[0]; @@ -6000,7 +6342,8 @@ { cur_dist = CUR_fast_project( &CUR.zp0.cur[point] ); distance = CUR_Func_round( cur_dist, - CUR.tt_metrics.compensations[0] ) - cur_dist; + CUR.tt_metrics.compensations[0], + gridlines_per_pixel ) - cur_dist; } else distance = 0; @@ -6025,8 +6368,22 @@ FT_UShort point; FT_F26Dot6 distance, org_dist; + FT_Int gridlines_per_pixel = 1; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.x != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_X; + + else if ( CUR.GS.freeVector.y != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_Y; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + cvtEntry = (FT_ULong)args[1]; point = (FT_UShort)args[0]; @@ -6062,21 +6419,34 @@ if ( CUR.GS.gep0 == 0 ) /* If in twilight zone */ { +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* only adjust legacy fonts x otherwise breaks Calibri italic */ + if ( CUR.compatibility_mode ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ CUR.zp0.org[point].x = TT_MulFix14( (FT_UInt32)distance, CUR.GS.freeVector.x ); CUR.zp0.org[point].y = TT_MulFix14( (FT_UInt32)distance, CUR.GS.freeVector.y ), CUR.zp0.cur[point] = CUR.zp0.org[point]; } +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( distance > 0 && + ( CUR.sph_tweak_flags & SPH_TWEAK_MIAP_HACK ) && + CUR.GS.freeVector.y != 0 ) + distance = 0 ; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ org_dist = CUR_fast_project( &CUR.zp0.cur[point] ); if ( ( CUR.opcode & 1 ) != 0 ) /* rounding and control cutin flag */ { - if ( FT_ABS( distance - org_dist ) > CUR.GS.control_value_cutin ) + if ( FT_ABS( distance - org_dist ) > + CUR.GS.control_value_cutin / gridlines_per_pixel ) distance = org_dist; - distance = CUR_Func_round( distance, CUR.tt_metrics.compensations[0] ); + distance = CUR_Func_round( distance, + CUR.tt_metrics.compensations[0], + gridlines_per_pixel ); } CUR_Func_move( &CUR.zp0, point, distance - org_dist ); @@ -6098,6 +6468,24 @@ { FT_UShort point; FT_F26Dot6 org_dist, distance; + FT_Int minimum_distance_factor = 64; + FT_Int gridlines_per_pixel = 1; + + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.x != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + { + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_X; + minimum_distance_factor = 64 - gridlines_per_pixel / 3; + } + else if ( CUR.GS.freeVector.y != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_Y; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ point = (FT_UShort)args[0]; @@ -6163,11 +6551,13 @@ if ( ( CUR.opcode & 4 ) != 0 ) distance = CUR_Func_round( org_dist, - CUR.tt_metrics.compensations[CUR.opcode & 3] ); + CUR.tt_metrics.compensations[CUR.opcode & 3], + gridlines_per_pixel ); else distance = ROUND_None( org_dist, - CUR.tt_metrics.compensations[CUR.opcode & 3] ); + CUR.tt_metrics.compensations[CUR.opcode & 3], + gridlines_per_pixel ); /* minimum distance flag */ @@ -6175,13 +6565,17 @@ { if ( org_dist >= 0 ) { - if ( distance < CUR.GS.minimum_distance ) - distance = CUR.GS.minimum_distance; + if ( distance < FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ) ) + distance = FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ); } else { - if ( distance > -CUR.GS.minimum_distance ) - distance = -CUR.GS.minimum_distance; + if ( distance > -FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ) ) + distance = -FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ); } } @@ -6218,10 +6612,37 @@ cur_dist, org_dist; + FT_Int minimum_distance_factor = 64; + FT_Int gridlines_per_pixel = 1; + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_Int B1; + FT_Int B2; + FT_Bool reverse_move = FALSE; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ point = (FT_UShort)args[0]; cvtEntry = (FT_ULong)( args[1] + 1 ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.x != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + { + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_X; + /* high value emboldens glyphs at lower ppems (< 14); */ + /* Courier looks better with 52 -- */ + /* MS ClearType Rasterizer supposedly uses 32 */ + minimum_distance_factor = 64 - gridlines_per_pixel / 3; + } + + else if ( CUR.GS.freeVector.y != 0 && + !( CUR.sph_tweak_flags & SPH_TWEAK_NORMAL_ROUND ) ) + gridlines_per_pixel = SPH_OPTION_GRIDLINES_PER_PIXEL_Y; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* XXX: UNDOCUMENTED! cvt[-1] = 0 always */ if ( BOUNDS( point, CUR.zp1.n_points ) || @@ -6237,6 +6658,10 @@ cvt_dist = 0; else cvt_dist = CUR_Func_read_cvt( cvtEntry - 1 ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.sph_tweak_flags & SPH_TWEAK_MIRP_CVT_ZERO ) + cvt_dist = 0; +#endif /* single width test */ @@ -6274,8 +6699,15 @@ if ( ( org_dist ^ cvt_dist ) < 0 ) cvt_dist = -cvt_dist; } - - /* control value cutin and round */ +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.GS.freeVector.y != 0 && + ( CUR.sph_tweak_flags & SPH_TWEAK_TIMES_NEW_ROMAN_HACK ) ) + { + if ( cur_dist < -64 ) cvt_dist -= 16; + else if ( cur_dist > 64 && cur_dist < 84) cvt_dist += 32; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* control value cut-in and round */ if ( ( CUR.opcode & 4 ) != 0 ) { @@ -6296,18 +6728,21 @@ /* `ttinst2.doc', version 1.66, is thus incorrect since */ /* it implies `>=' instead of `>'. */ - if ( FT_ABS( cvt_dist - org_dist ) > CUR.GS.control_value_cutin ) + if ( FT_ABS( cvt_dist - org_dist ) > + CUR.GS.control_value_cutin / gridlines_per_pixel ) cvt_dist = org_dist; } distance = CUR_Func_round( cvt_dist, - CUR.tt_metrics.compensations[CUR.opcode & 3] ); + CUR.tt_metrics.compensations[CUR.opcode & 3], + gridlines_per_pixel ); } else distance = ROUND_None( cvt_dist, - CUR.tt_metrics.compensations[CUR.opcode & 3] ); + CUR.tt_metrics.compensations[CUR.opcode & 3], + gridlines_per_pixel ); /* minimum distance test */ @@ -6315,18 +6750,63 @@ { if ( org_dist >= 0 ) { - if ( distance < CUR.GS.minimum_distance ) - distance = CUR.GS.minimum_distance; + if ( distance < FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ) ) + distance = FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ); } else { - if ( distance > -CUR.GS.minimum_distance ) - distance = -CUR.GS.minimum_distance; + if ( distance > -FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ) ) + distance = -FT_MulDiv( minimum_distance_factor, + CUR.GS.minimum_distance, 64 ); } } +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + B1 = CUR.zp1.cur[point].y; + + /* Round moves if necessary */ + if ( CUR.ignore_x_mode && + CUR.GS.freeVector.y != 0 && + ( CUR.sph_tweak_flags & SPH_TWEAK_ROUND_NONPIXEL_Y_MOVES ) ) + distance = FT_PIX_ROUND( B1 + distance - cur_dist ) - B1 + cur_dist; + + if ( CUR.GS.freeVector.y != 0 && + ( CUR.opcode & 16 ) == 0 && + ( CUR.opcode & 8 ) == 0 && + ( CUR.sph_tweak_flags & SPH_TWEAK_COURIER_NEW_2_HACK ) ) + distance +=64; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + CUR_Func_move( &CUR.zp1, point, distance - cur_dist ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + B2 = CUR.zp1.cur[point].y; + + /* Reverse move if necessary */ + if ( CUR.ignore_x_mode ) + { + if ( ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_OFFPIXEL_Y_MOVES ) && + CUR.GS.freeVector.y != 0 && B1 % 64 == 0 && B2 % 64 != 0 ) + reverse_move = TRUE; + + if ( ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_NONPIXEL_Y_MOVES ) && + CUR.GS.freeVector.y != 0 && B2 % 64 != 0 && B1 % 64 != 0 ) + reverse_move = TRUE; + + if ( ( CUR.sph_tweak_flags & SPH_TWEAK_DELTAP_SKIP_EXAGGERATED_VALUES ) && + !reverse_move && + abs ( B1 - B2 ) >= 64 ) + reverse_move = TRUE; + } + + if ( reverse_move ) + CUR_Func_move( &CUR.zp1, point, -( distance - cur_dist ) ); + +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + Fail: CUR.GS.rp1 = CUR.GS.rp0; @@ -6350,8 +6830,14 @@ FT_F26Dot6 distance; FT_UNUSED_ARG; - - +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode && CUR.iup_called && + ( CUR.sph_tweak_flags & SPH_TWEAK_NO_ALIGNRP_AFTER_IUP ) ) + { + CUR.error = TT_Err_Invalid_Reference; + goto Fail; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ if ( CUR.top < CUR.GS.loop || BOUNDS( CUR.GS.rp0, CUR.zp0.n_points ) ) { @@ -6846,6 +7332,15 @@ contour = 0; point = 0; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + CUR.iup_called = 1; + if ( CUR.sph_tweak_flags & SPH_TWEAK_SKIP_IUP ) + return; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + do { end_point = CUR.pts.contours[contour] - CUR.pts.first_point; @@ -6915,7 +7410,9 @@ FT_UShort A; FT_ULong C; FT_Long B; - +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + FT_UShort B1, B2; +#endif #ifdef TT_CONFIG_OPTION_UNPATENTED_HINTING /* Delta hinting is covered by US Patent 5159668. */ @@ -6988,7 +7485,67 @@ B++; B = B * 64 / ( 1L << CUR.GS.delta_shift ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /* + * Allow delta move if + * + * - not using ignore_x_mode rendering + * - glyph is specifically set to allow it + * - glyph is composite and freedom vector is not subpixel vector + */ + if ( !CUR.ignore_x_mode || + ( CUR.sph_tweak_flags & SPH_TWEAK_ALWAYS_DO_DELTAP ) || + ( CUR.is_composite && CUR.GS.freeVector.y != 0 )) + CUR_Func_move( &CUR.zp0, A, B ); + + /* Otherwise apply subpixel hinting and compatibility mode rules */ + else if ( CUR.ignore_x_mode ) + { + if ( CUR.GS.freeVector.y != 0 ) + B1 = CUR.zp0.cur[A].y; + else + B1 = CUR.zp0.cur[A].x; + + /* Standard Subpixel Hinting: Allow y move */ + if ( !CUR.compatibility_mode && CUR.GS.freeVector.y != 0 ) + CUR_Func_move( &CUR.zp0, A, B ); + + /* Compatibility Mode: Allow x or y move if point touched in + Y direction */ + else if ( CUR.compatibility_mode && + !( CUR.sph_tweak_flags & SPH_TWEAK_ALWAYS_SKIP_DELTAP )) + { + /* save the y value of the point now; compare after move */ + B1 = CUR.zp0.cur[A].y; + + if ( ( CUR.sph_tweak_flags & SPH_TWEAK_ROUND_NONPIXEL_Y_MOVES ) ) + B = FT_PIX_ROUND( B1 + B ) - B1; + + /* + * Allow delta move if using compatibility_mode, IUP has not + * been called, and point is touched on Y. + */ + if ( !CUR.iup_called && + ( CUR.zp0.tags[A] & FT_CURVE_TAG_TOUCH_Y ) ) + CUR_Func_move( &CUR.zp0, A, B ); + } + B2 = CUR.zp0.cur[A].y; + + /* Reverse this move if it results in a disallowed move */ + if ( CUR.GS.freeVector.y != 0 && + ( ( ( CUR.sph_tweak_flags & + SPH_TWEAK_SKIP_OFFPIXEL_Y_MOVES ) && + B1 % 64 == 0 && + B2 % 64 != 0 ) || + ( ( CUR.sph_tweak_flags & + SPH_TWEAK_SKIP_NONPIXEL_Y_MOVES ) && + B1 % 64 != 0 && + B2 % 64 != 0 ))) + CUR_Func_move( &CUR.zp0, A, -B ); + } +#else CUR_Func_move( &CUR.zp0, A, B ); +#endif /* *TT_CONFIG_OPTION_SUBPIXEL_HINTING */ } } else @@ -7114,26 +7671,116 @@ Ins_GETINFO( INS_ARG ) { FT_Long K; - - K = 0; - /* We return MS rasterizer version 1.7 for the font scaler. */ +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + /********************************/ + /* RASTERIZER VERSION */ + /* Selector Bit: 0 */ + /* Return Bit(s): 0-7 */ + /* */ + if ( ( args[0] & 1 ) != 0 && CUR.ignore_x_mode ) + { + K = CUR.rasterizer_version; +#ifdef SPH_DEBUG_MORE_VERBOSE + printf(" SETTING AS %d\n", CUR.rasterizer_version ); +#endif + } + else +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ if ( ( args[0] & 1 ) != 0 ) K = 35; - /* Has the glyph been rotated? */ + /********************************/ + /* GLYPH ROTATED */ + /* Selector Bit: 1 */ + /* Return Bit(s): 8 */ + /* */ if ( ( args[0] & 2 ) != 0 && CUR.tt_metrics.rotated ) K |= 0x80; - /* Has the glyph been stretched? */ + /********************************/ + /* GLYPH STRETCHED */ + /* Selector Bit: 2 */ + /* Return Bit(s): 9 */ + /* */ if ( ( args[0] & 4 ) != 0 && CUR.tt_metrics.stretched ) K |= 1 << 8; - /* Are we hinting for grayscale? */ + /********************************/ + /* HINTING FOR GRAYSCALE */ + /* Selector Bit: 5 */ + /* Return Bit(s): 12 */ + /* */ if ( ( args[0] & 32 ) != 0 && CUR.grayscale ) K |= 1 << 12; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode && CUR.rasterizer_version >= 35 ) + { + /********************************/ + /* HINTING FOR GRAYSCALE */ + /* Selector Bit: 5 */ + /* Return Bit(s): 12 */ + /* */ + if ( ( args[0] & 32 ) != 0 && CUR.grayscale_hinting ) + K |= 1 << 12; + + /********************************/ + /* HINTING FOR SUBPIXEL */ + /* Selector Bit: 6 */ + /* Return Bit(s): 13 */ + /* */ + if ( ( args[0] & 64 ) != 0 && + CUR.subpixel_hinting && + CUR.rasterizer_version >= 37 ) + { + K |= 1 << 13; + + /* the stuff below is irrelevant if subpixel_hinting is not set */ + + /********************************/ + /* COMPATIBLE WIDTHS ENABLED */ + /* Selector Bit: 7 */ + /* Return Bit(s): 14 */ + /* */ + /* Functionality still needs to be added */ + if ( ( args[0] & 128 ) != 0 && CUR.compatible_widths ) + K |= 1 << 14; + + /********************************/ + /* SYMMETRICAL SMOOTHING */ + /* Selector Bit: 8 */ + /* Return Bit(s): 15 */ + /* */ + /* Functionality still needs to be added */ + if ( ( args[0] & 256 ) != 0 && CUR.symmetrical_smoothing ) + K |= 1 << 15; + + /********************************/ + /* HINTING FOR BGR? */ + /* Selector Bit: 9 */ + /* Return Bit(s): 16 */ + /* */ + /* Functionality still needs to be added */ + if ( ( args[0] & 512 ) != 0 && CUR.bgr ) + K |= 1 << 16; + + if ( CUR.rasterizer_version >= 38 ) + { + + /********************************/ + /* SUBPIXEL POSITIONED? */ + /* Selector Bit: 10 */ + /* Return Bit(s): 17 */ + /* */ + /* Functionality still needs to be added */ + if ( ( args[0] & 1024 ) != 0 && CUR.subpixel_positioned ) + K |= 1 << 17; + } + } + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ args[0] = K; } @@ -7509,6 +8156,15 @@ cur = *exc; #endif +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) + { + /* ensure some variables are set for this run */ + CUR.iup_called = FALSE; + CUR.in_delta_function = FALSE; + } +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + /* set CVT functions */ CUR.tt_metrics.ratio = 0; if ( CUR.metrics.x_ppem != CUR.metrics.y_ppem ) @@ -7780,7 +8436,13 @@ break; case 0x2B: /* CALL */ - Ins_CALL( EXEC_ARG_ args ); +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( !CUR.ignore_x_mode || + !CUR.iup_called || + ( CUR.iup_called && + !( CUR.sph_tweak_flags & SPH_TWEAK_NO_CALL_AFTER_IUP ) ) ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + Ins_CALL( EXEC_ARG_ args ); break; case 0x2C: /* FDEF */ @@ -7799,6 +8461,9 @@ case 0x30: /* IUP */ case 0x31: /* IUP */ +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( CUR.ignore_x_mode ) CUR.iup_called = TRUE; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ Ins_IUP( EXEC_ARG_ args ); break; @@ -7958,6 +8623,12 @@ break; case 0x5D: /* DELTAP1 */ +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + if ( !CUR.ignore_x_mode || + !CUR.iup_called || + ( CUR.iup_called && + !( CUR.sph_tweak_flags & SPH_TWEAK_NO_DELTAP_AFTER_IUP ) ) ) +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ Ins_DELTAP( EXEC_ARG_ args ); break; diff --git a/src/truetype/ttinterp.h b/src/truetype/ttinterp.h index 6d0fc03..9ee2b48 100644 --- a/src/truetype/ttinterp.h +++ b/src/truetype/ttinterp.h @@ -68,7 +68,8 @@ FT_BEGIN_HEADER /* Rounding function */ typedef FT_F26Dot6 (*TT_Round_Func)( EXEC_OP_ FT_F26Dot6 distance, - FT_F26Dot6 compensation ); + FT_F26Dot6 compensation, + FT_Int resolution ); /* Point displacement along the freedom vector routine */ typedef void @@ -107,6 +108,44 @@ FT_BEGIN_HEADER } TT_CallRec, *TT_CallStack; +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + + /*************************************************************************/ + /* */ + /* This structure defines a rule used to tweak subpixel hinting for */ + /* various fonts. "", 0, "", NULL value indicates to match any value. */ + /* */ + + typedef struct SPH_TweakRule_ + { + const char family[32]; + const int ppem; + const char style[32]; + const FT_ULong glyph; + + } SPH_TweakRule; + + + typedef struct SPH_ScaleRule_ + { + const char family[32]; + const int ppem; + const char style[32]; + const FT_ULong glyph; + const float scale; + } SPH_ScaleRule; + +#define MAX_CLASS_MEMBERS 100 + + typedef struct Font_Class_ + { + const char name[32]; + const char member[MAX_CLASS_MEMBERS][32]; + } Font_Class; + +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + + /*************************************************************************/ /* */ /* The main structure for the interpreter which collects all necessary */ @@ -218,6 +257,37 @@ FT_BEGIN_HEADER FT_Bool grayscale; /* are we hinting for grayscale? */ +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + TT_Round_Func func_round_sphn; /* subpixel rounding function */ + + FT_Bool grayscale_hinting; /* Using grayscale hinting? */ + FT_Bool subpixel_hinting; /* Using subpixel hinting? */ + FT_Bool native_hinting; /* Using native hinting? */ + FT_Bool ignore_x_mode; /* Standard rendering mode for */ + /* subpixel hinting. On if gray */ + /* or subpixel hinting is on ) */ + FT_Bool compatibility_mode;/* Additional exceptions to */ + /* native TT rules for legacy */ + /* fonts. Implies ignore_x_mode. */ + + /* The following 3 aren't fully implemented but here for MS rasterizer */ + /* compatibility. */ + FT_Bool compatible_widths; /* compatible widths? */ + FT_Bool symmetrical_smoothing;/* symmetrical_smoothing? */ + FT_Bool bgr; /* bgr instead of rgb? */ + FT_Bool subpixel_positioned; /* MS DW subpixel positioned */ + + FT_Int rasterizer_version; /* MS rasterizer version */ + + FT_Bool iup_called; /* IUP called for glyph? */ + FT_Bool in_delta_function; /* inside an inline delta func? */ + + FT_ULong sph_tweak_flags; /* flags to control hint tweaks */ + + FT_Int num_delta_funcs; + FT_ULong inline_delta_funcs[5]; +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + } TT_ExecContextRec; diff --git a/src/truetype/ttobjs.h b/src/truetype/ttobjs.h index 47e4129..49d5b3f 100644 --- a/src/truetype/ttobjs.h +++ b/src/truetype/ttobjs.h @@ -178,6 +178,7 @@ FT_BEGIN_HEADER FT_Long end; /* where does it end? */ FT_UInt opc; /* function #, or instruction code */ FT_Bool active; /* is it active? */ + FT_Bool inline_delta; /* is function that defines inline delta? */ } TT_DefRecord, *TT_DefArray; @@ -190,7 +191,7 @@ FT_BEGIN_HEADER { FT_Fixed xx, xy; /* transformation matrix coefficients */ FT_Fixed yx, yy; - FT_F26Dot6 ox, oy; /* offsets */ + FT_F26Dot6 ox, oy; /* offsets */ } TT_Transform; diff --git a/src/truetype/ttsubpix.c b/src/truetype/ttsubpix.c new file mode 100644 index 0000000..2dc6c2f --- /dev/null +++ b/src/truetype/ttsubpix.c @@ -0,0 +1,261 @@ +/***************************************************************************/ +/* */ +/* ttsubpix.c */ +/* */ +/* TrueType Subpixel Hinting. */ +/* */ +/* Copyright 2010-2011 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#include +#include FT_INTERNAL_DEBUG_H +#include FT_INTERNAL_CALC_H +#include FT_INTERNAL_STREAM_H +#include FT_INTERNAL_SFNT_H +#include FT_TRUETYPE_TAGS_H +#include FT_OUTLINE_H + +#include "ttsubpix.h" + + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + + FT_LOCAL_DEF( FT_Bool ) + is_member_of_family_class( const FT_String* detected_font_name, + const FT_String* rule_font_name ) + { + FT_UInt i, j; + + /* If font name matches rule family */ + if ( strcmp( detected_font_name, rule_font_name ) == 0 ) return TRUE; + + /* If font name is a wildcard "" */ + if ( strcmp( rule_font_name, "" ) == 0 ) return TRUE; + + /* If font name is contained in a class list */ + for ( i = 0; i < FAMILY_CLASS_RULES_SIZE; i++ ) + { + if ( strcmp( FAMILY_CLASS_Rules[i].name, rule_font_name ) == 0 ) + { + for ( j = 0; j < MAX_CLASS_MEMBERS; j++ ) + { + if ( strcmp( FAMILY_CLASS_Rules[i].member[j], "" ) == 0 ) + continue; + if ( strcmp( FAMILY_CLASS_Rules[i].member[j], detected_font_name ) == 0 ) + return TRUE; + } + } + } + return FALSE; + } + + + FT_LOCAL_DEF( FT_Bool ) + is_member_of_style_class( const FT_String* detected_font_style, + const FT_String* rule_font_style ) + { + FT_UInt i, j; + + /* If font style matches rule style */ + if ( strcmp( detected_font_style, rule_font_style ) == 0 ) return TRUE; + + /* If font style is a wildcard "" */ + if ( strcmp( rule_font_style, "" ) == 0 ) return TRUE; + + /* If font style is contained in a class list */ + for ( i = 0; i < STYLE_CLASS_RULES_SIZE; i++ ) + { + if ( strcmp( STYLE_CLASS_Rules[i].name, rule_font_style ) == 0 ) + { + for ( j = 0; j < MAX_CLASS_MEMBERS; j++ ) + { + if ( strcmp( STYLE_CLASS_Rules[i].member[j], "" ) == 0 ) + continue; + if ( strcmp( STYLE_CLASS_Rules[i].member[j], detected_font_style ) == 0 ) + return TRUE; + } + } + } + return FALSE; + } + + + FT_LOCAL_DEF( FT_Bool ) + sph_test_tweak( TT_Face face, + FT_String* family, + int ppem, + FT_String* style, + FT_UInt glyph_index, + SPH_TweakRule* rule, + FT_UInt num_rules ) + { + FT_UInt i; + + + /* rule checks may be able to be optimized further */ + for ( i = 0; i < num_rules; i++ ) + { + if ( family && + ( is_member_of_family_class ( family, rule[i].family ) ) ) + if ( rule[i].ppem == 0 || + rule[i].ppem == ppem ) + if ( style && + is_member_of_style_class ( style, rule[i].style ) ) + if ( rule[i].glyph == 0 || + FT_Get_Char_Index( (FT_Face)face, + rule[i].glyph ) == glyph_index ) + return TRUE; + } + return FALSE; + } + + + FT_LOCAL_DEF( float ) + scale_test_tweak( TT_Face face, + FT_String* family, + int ppem, + FT_String* style, + FT_UInt glyph_index, + SPH_ScaleRule* rule, + FT_UInt num_rules ) + { + FT_UInt i; + + + /* rule checks may be able to be optimized further */ + for ( i = 0; i < num_rules; i++ ) + { + if ( family && + ( is_member_of_family_class ( family, rule[i].family ) ) ) + if ( rule[i].ppem == 0 || + rule[i].ppem == ppem ) + if ( style && + is_member_of_style_class( style, rule[i].style ) ) + if ( rule[i].glyph == 0 || + FT_Get_Char_Index( (FT_Face)face, + rule[i].glyph ) == glyph_index ) + return rule[i].scale; + } + return 1.0; + } + +#define TWEAK_RULES( x ) \ + if ( sph_test_tweak( face, family, ppem, style, glyph_index, \ + x##_Rules, x##_RULES_SIZE ) ) \ + loader->exec->sph_tweak_flags |= SPH_TWEAK_##x; + +#define TWEAK_RULES_EXCEPTIONS( x ) \ + if ( sph_test_tweak( face, family, ppem, style, glyph_index, \ + x##_Rules_Exceptions, x##_RULES_EXCEPTIONS_SIZE ) ) \ + loader->exec->sph_tweak_flags &= ~SPH_TWEAK_##x; + + FT_LOCAL_DEF( void ) + sph_set_tweaks( TT_Loader loader, + FT_UInt glyph_index ) + { + TT_Face face = (TT_Face)loader->face; + FT_String* family = face->root.family_name; + int ppem = loader->size->metrics.x_ppem; + FT_String* style = face->root.style_name; + + /* Don't apply rules if style isn't set */ + if ( !face->root.style_name ) return; + +#ifdef SPH_DEBUG_MORE_VERBOSE + printf( "%s,%d,%s,%c=%d ", family, ppem, style, glyph_index, glyph_index ); +#endif + + TWEAK_RULES( PIXEL_HINTING ); + + if ( loader->exec->sph_tweak_flags & SPH_TWEAK_PIXEL_HINTING ) + { + loader->exec->ignore_x_mode = FALSE; + return; + } + + TWEAK_RULES( ALLOW_X_DMOVE ); + TWEAK_RULES( ALLOW_X_DMOVEX ); + TWEAK_RULES( ALLOW_X_MOVE_ZP2 ); + TWEAK_RULES( ALWAYS_DO_DELTAP ); + TWEAK_RULES( ALWAYS_SKIP_DELTAP ); + TWEAK_RULES( DEEMBOLDEN ); + TWEAK_RULES( DELTAP_SKIP_EXAGGERATED_VALUES ); + TWEAK_RULES( DO_SHPIX ); + TWEAK_RULES( EMBOLDEN ); + TWEAK_RULES( MIAP_HACK ); + TWEAK_RULES( NORMAL_ROUND ); + TWEAK_RULES( NO_ALIGNRP_AFTER_IUP ); + TWEAK_RULES( NO_CALL_AFTER_IUP ); + TWEAK_RULES( NO_DELTAP_AFTER_IUP ); + TWEAK_RULES( RASTERIZER_35 ); + TWEAK_RULES( SKIP_INLINE_DELTAS ); + TWEAK_RULES( SKIP_IUP ); + TWEAK_RULES( MIRP_CVT_ZERO ); + + TWEAK_RULES( SKIP_OFFPIXEL_Y_MOVES ); + TWEAK_RULES_EXCEPTIONS( SKIP_OFFPIXEL_Y_MOVES ); + + TWEAK_RULES( SKIP_NONPIXEL_Y_MOVES ); + TWEAK_RULES_EXCEPTIONS( SKIP_NONPIXEL_Y_MOVES ); + + TWEAK_RULES( ROUND_NONPIXEL_Y_MOVES ); + TWEAK_RULES_EXCEPTIONS( ROUND_NONPIXEL_Y_MOVES ); + + if ( loader->exec->sph_tweak_flags & SPH_TWEAK_RASTERIZER_35 ) + { + if ( loader->exec->rasterizer_version != 35 ) + { + loader->exec->rasterizer_version = 35; + /* must re-execute fpgm */ + loader->exec->size->cvt_ready = FALSE; + tt_size_ready_bytecode( loader->exec->size, + FT_BOOL( loader->load_flags & FT_LOAD_PEDANTIC ) ); + } + } + else + { + if ( loader->exec->rasterizer_version == 35 ) + { + loader->exec->rasterizer_version = 37; + /* must re-execute fpgm */ + loader->exec->size->cvt_ready = FALSE; + tt_size_ready_bytecode( loader->exec->size, + FT_BOOL( loader->load_flags & FT_LOAD_PEDANTIC ) ); + } + } + + if ( IS_HINTED( loader->load_flags ) ) + { + TWEAK_RULES( TIMES_NEW_ROMAN_HACK ); + TWEAK_RULES( COURIER_NEW_2_HACK ); + } + + if ( sph_test_tweak( face, family, ppem, style, glyph_index, + COMPATIBILITY_MODE_Rules, COMPATIBILITY_MODE_RULES_SIZE ) ) + { + loader->exec->compatibility_mode |= TRUE; + loader->exec->ignore_x_mode |= TRUE; + } + else + loader->exec->compatibility_mode &= FALSE; + + if ( IS_HINTED( loader->load_flags ) ) + { + if ( sph_test_tweak( face, family, ppem, style, glyph_index, + COMPATIBLE_WIDTHS_Rules, COMPATIBLE_WIDTHS_RULES_SIZE ) ) + loader->exec->compatible_widths |= TRUE; + } + } + +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + + +/* END */ diff --git a/src/truetype/ttsubpix.h b/src/truetype/ttsubpix.h new file mode 100644 index 0000000..df23040 --- /dev/null +++ b/src/truetype/ttsubpix.h @@ -0,0 +1,778 @@ +/***************************************************************************/ +/* */ +/* ttsubpix.h */ +/* */ +/* TrueType Subpixel Hinting. */ +/* */ +/* Copyright 2010-2011 by */ +/* David Turner, Robert Wilhelm, and Werner Lemberg. */ +/* */ +/* This file is part of the FreeType project, and may only be used, */ +/* modified, and distributed under the terms of the FreeType project */ +/* license, LICENSE.TXT. By continuing to use, modify, or distribute */ +/* this file you indicate that you have read the license and */ +/* understand and accept it fully. */ +/* */ +/***************************************************************************/ + +#ifndef __TTSUBPIX_H__ +#define __TTSUBPIX_H__ + +#include +#include "ttobjs.h" +#include "ttinterp.h" + +#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING + + /*************************************************************************/ + /* */ + /* Tweak flags that are set for each glyph by the below rules */ + /* */ + /* */ +#define SPH_TWEAK_ALLOW_X_DMOVE 0x0000001 +#define SPH_TWEAK_ALLOW_X_DMOVEX 0x0000002 +#define SPH_TWEAK_ALLOW_X_MOVE_ZP2 0x0000004 +#define SPH_TWEAK_ALWAYS_DO_DELTAP 0x0000008 +#define SPH_TWEAK_ALWAYS_SKIP_DELTAP 0x0000010 +#define SPH_TWEAK_COURIER_NEW_2_HACK 0x0000020 +#define SPH_TWEAK_DEEMBOLDEN 0x0000040 +#define SPH_TWEAK_DELTAP_SKIP_EXAGGERATED_VALUES 0x0000080 +#define SPH_TWEAK_DO_SHPIX 0x0000100 +#define SPH_TWEAK_EMBOLDEN 0x0000200 +#define SPH_TWEAK_MIAP_HACK 0x0000400 +#define SPH_TWEAK_NORMAL_ROUND 0x0000800 +#define SPH_TWEAK_NO_ALIGNRP_AFTER_IUP 0x0001000 +#define SPH_TWEAK_NO_CALL_AFTER_IUP 0x0002000 +#define SPH_TWEAK_NO_DELTAP_AFTER_IUP 0x0004000 +#define SPH_TWEAK_PIXEL_HINTING 0x0008000 +#define SPH_TWEAK_RASTERIZER_35 0x0010000 +#define SPH_TWEAK_ROUND_NONPIXEL_Y_MOVES 0x0020000 +#define SPH_TWEAK_SKIP_INLINE_DELTAS 0x0040000 +#define SPH_TWEAK_SKIP_IUP 0x0080000 +#define SPH_TWEAK_SKIP_NONPIXEL_Y_MOVES 0x0100000 +#define SPH_TWEAK_SKIP_OFFPIXEL_Y_MOVES 0x0200000 +#define SPH_TWEAK_TIMES_NEW_ROMAN_HACK 0x0400000 +#define SPH_TWEAK_MIRP_CVT_ZERO 0x0800000 + + + FT_LOCAL( FT_Bool ) + sph_test_tweak( TT_Face face, + FT_String* family, + int ppem, + FT_String* style, + FT_UInt glyph_index, + SPH_TweakRule* rule, + FT_UInt num_rules ); + + FT_LOCAL_DEF( float ) + scale_test_tweak( TT_Face face, + FT_String* family, + int ppem, + FT_String* style, + FT_UInt glyph_index, + SPH_ScaleRule* rule, + FT_UInt num_rules ); + + FT_LOCAL( void ) + sph_set_tweaks( TT_Loader loader, + FT_UInt glyph_index ); + + + /*************************************************************************/ + /* */ + /* These rules affect how the TT Interpreter does hinting, with the */ + /* goal of doing subpixel hinting by (in general) ignoring x moves. */ + /* Some of these rules are fixes that go above and beyond the */ + /* stated techniques in the MS whitepaper on Cleartype, due to */ + /* artifacts in many glyphs. So, these rules make some glyphs render */ + /* better than they do in the MS rasterizer. */ + /* */ + /* "" string or 0 int/char indicates to apply to all glyphs. */ + /* "-" used as dummy placeholders, but any non-matching string works. */ + /* */ + /* Some of this could arguably be implemented in fontconfig, however: */ + /* */ + /* - Fontconfig can't set things on a glyph-by-glyph basis. */ + /* - The tweaks that happen here are very low-level, from an average */ + /* user's point of view and are best implemented in the hinter */ + /* */ + /* The goal is to make the subpixel hinting techniques as generalized */ + /* as possible across all fonts to prevent the need for extra rules such */ + /* as these. */ + /* */ + /* The rule structure is designed so that entirely new rules can easily */ + /* be added when a new compatibility feature is discovered. */ + /* */ + /* The rule structures could also use some enhancement to handle ranges. */ + /* */ + /* ****************** WORK IN PROGRESS ******************* */ + /* */ + +#define SPH_OPTION_BITMAP_WIDTHS FALSE +#define SPH_OPTION_SET_SUBPIXEL TRUE +#define SPH_OPTION_SET_GRAYSCALE FALSE +#define SPH_OPTION_SET_COMPATIBLE_WIDTHS FALSE +#define SPH_OPTION_SET_RASTERIZER_VERSION 37 +#define SPH_OPTION_GRIDLINES_PER_PIXEL_X 64 +#define SPH_OPTION_GRIDLINES_PER_PIXEL_Y 1 + +#define MAX_CLASS_MEMBERS 100 + +/* Define this to force natural (i.e. not bitmap-compatible) widths. */ +/* The default leans strongly towards natural widths except for a few */ +/* legacy fonts where a selective combination produces nicer results. */ +/* #define FORCE_NATURAL_WIDTHS */ + + + /* These are "classes" of fonts that can be grouped together and used in */ + /* rules below. A blank entry "" is required at the end of these! */ +#define FAMILY_CLASS_RULES_SIZE 7 + Font_Class FAMILY_CLASS_Rules + [FAMILY_CLASS_RULES_SIZE] = + { + { "MS Legacy Fonts", { "Aharoni", + "Andale Mono", + "Andalus", + "Angsana New", + "AngsanaUPC", + "Arabic Transparent", + "Arial Black", + "Arial Narrow", + "Arial Unicode MS", + "Arial", + "Batang", + "Browallia New", + "BrowalliaUPC", + "Comic Sans MS", + "Cordia New", + "CordiaUPC", + "Courier New", + "DFKai-SB", + "David Transparent", + "David", + "DilleniaUPC", + "Estrangelo Edessa", + "EucrosiaUPC", + "FangSong_GB2312", + "Fixed Miriam Transparent", + "FrankRuehl", + "Franklin Gothic Medium", + "FreesiaUPC", + "Garamond", + "Gautami", + "Georgia", + "Gulim", + "Impact", + "IrisUPC", + "JasmineUPC", + "KaiTi_GB2312", + "KodchiangUPC", + "Latha", + "Levenim MT", + "LilyUPC", + "Lucida Console", + "Lucida Sans Unicode", + "MS Gothic", + "MS Mincho", + "MV Boli", + "Mangal", + "Marlett", + "Microsoft Sans Serif", + "Mingliu", + "Miriam Fixed", + "Miriam Transparent", + "Miriam", + "Narkisim", + "Palatino Linotype", + "Raavi", + "Rod Transparent", + "Rod", + "Shruti", + "SimHei", + "Simplified Arabic Fixed", + "Simplified Arabic", + "Simsun", + "Sylfaen", + "Symbol", + "Tahoma", + "Times New Roman", + "Traditional Arabic", + "Trebuchet MS", + "Tunga", + "Verdana", + "Webdings", + "Wingdings", "", }, }, + { "Core MS Legacy Fonts", { "Arial Black", + "Arial Narrow", + "Arial Unicode MS", + "Arial", + "Comic Sans MS", + "Courier New", + "Garamond", + "Georgia", + "Impact", + "Lucida Console", + "Lucida Sans Unicode", + "Microsoft Sans Serif", + "Palatino Linotype", + "Tahoma", + "Times New Roman", + "Trebuchet MS", + "Verdana", "", }, }, + { "Apple Legacy Fonts", { "Geneva", + "Times", + "Monaco", + "Century", + "Chalkboard", + "Lobster", + "Century Gothic", + "Optima", + "Lucida Grande", + "Gill Sans", + "Baskerville", + "Helvetica", + "Helvetica Neue", "", }, }, + { "Legacy Sans Fonts", { "Andale Mono", + "Arial Unicode MS", + "Arial", + "Century Gothic", + "Comic Sans MS", + "Franklin Gothic Medium", + "Geneva", + "Lucida Console", + "Lucida Grande", + "Lucida Sans Unicode", + "Microsoft Sans Serif", + "Monaco", + "Tahoma", + "Trebuchet MS", + "Verdana", "", }, }, + { "Misc Legacy Fonts", { "Dark Courier", "", }, }, + { "Verdana Clones", { "DejaVu Sans", + "Bitstream Vera Sans", "", }, }, + { "Verdana and Clones", { "DejaVu Sans", + "Bitstream Vera Sans", + "Verdana", "", }, }, +}; + + + /* Define "classes" of styles that can be grouped together and used in */ + /* rules below. A blank entry "" is required at the end of these! */ +#define STYLE_CLASS_RULES_SIZE 5 + Font_Class STYLE_CLASS_Rules + [STYLE_CLASS_RULES_SIZE] = + { + { "Regular Class", { "Regular", + "Book", + "Medium", + "Roman", + "Normal", "", }, }, + { "Regular/Italic Class", { "Regular", + "Book", + "Medium", + "Italic", + "Oblique", + "Roman", + "Normal", "", }, }, + { "Bold/BoldItalic Class", { "Bold", + "Bold Italic", + "Black", "", }, }, + { "Bold/Italic/BoldItalic Class", { "Bold", + "Bold Italic", + "Black", + "Italic", + "Oblique", "", }, }, + { "Regular/Bold Class", { "Regular", + "Book", + "Medium", + "Normal", + "Roman", + "Bold", + "Black", "", }, }, + }; + + + + /* Special fixes for known legacy fonts */ + /* This is the primary workhorse rule for legacy fonts */ +#define COMPATIBILITY_MODE_RULES_SIZE 4 + SPH_TweakRule COMPATIBILITY_MODE_Rules + [COMPATIBILITY_MODE_RULES_SIZE] = + { + { "MS Legacy Fonts", 0, "", 0 }, + { "Apple Legacy Fonts", 0, "", 0 }, + { "Misc Legacy Fonts", 0, "", 0 }, + { "Verdana Clones", 0, "", 0 }, + }; + + + /* Don't do subpixel (ignore_x_mode) hinting; do normal hinting */ +#define PIXEL_HINTING_RULES_SIZE 4 + SPH_TweakRule PIXEL_HINTING_Rules + [PIXEL_HINTING_RULES_SIZE] = + { + /* These characters are almost always safe */ + { "", 0, "", '<' }, + { "", 0, "", '>' }, + /* Fixes the vanishing stem */ + { "Times New Roman", 0, "Bold", 'A' }, + { "Times New Roman", 0, "Bold", 'V' }, + }; + + + /* According to Greg Hitchcock and the MS whitepaper, this should work */ + /* on all legacy MS fonts, but creates artifacts with some. Only using */ + /* where absolutely necessary. */ +#define SKIP_INLINE_DELTAS_RULES_SIZE 1 + SPH_TweakRule SKIP_INLINE_DELTAS_Rules + [SKIP_INLINE_DELTAS_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + + + /* Subpixel hinting ignores SHPIX rules on X. Force SHPIX for these. */ +#define DO_SHPIX_RULES_SIZE 1 + SPH_TweakRule DO_SHPIX_Rules + [DO_SHPIX_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + + + /* Skip Y moves that start with a point that is not on a Y pixel */ + /* boundary and don't move that point to a Y pixel boundary. */ +#define SKIP_NONPIXEL_Y_MOVES_RULES_SIZE 8 + SPH_TweakRule SKIP_NONPIXEL_Y_MOVES_Rules + [SKIP_NONPIXEL_Y_MOVES_RULES_SIZE] = + { + /* fix vwxyz thinness*/ + { "Consolas", 0, "Regular", 0 }, + /* fix tiny gap at top of m */ + { "Arial", 0, "Regular", 'm' }, + /* Fix thin middle stems */ + { "Core MS Legacy Fonts", 0, "Regular/Bold Class", 'N' }, + { "Lucida Grande", 0, "", 'N' }, + { "Legacy Sans Fonts", 0, "", L'и' }, + { "Verdana Clones", 0, "",'N' }, + { "Ubuntu", 0, "Regular Class", 'N' }, + /* Fix misshapen x */ + { "Verdana", 0, "Bold", 'x' }, + }; + +#define SKIP_NONPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE 4 + SPH_TweakRule SKIP_NONPIXEL_Y_MOVES_Rules_Exceptions + [SKIP_NONPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE] = + { + { "Tahoma", 0, "", 'N' }, + { "Comic Sans MS", 0, "", 'N' }, + { "Verdana", 0, "Regular/Bold Class", 'N' }, + { "Verdana", 11, "Bold", 'x' }, + }; + + + + /* Skip Y moves that move a point off a Y pixel boundary */ + /* This fixes Tahoma, Trebuchet oddities and some issues with `$' */ +#define SKIP_OFFPIXEL_Y_MOVES_RULES_SIZE 5 + SPH_TweakRule SKIP_OFFPIXEL_Y_MOVES_Rules + [SKIP_OFFPIXEL_Y_MOVES_RULES_SIZE] = + { + { "MS Legacy Fonts", 0, "", 0 }, + { "Apple Legacy Fonts", 0, "", 0 }, + { "Misc Legacy Fonts", 0, "", 0 }, + { "Ubuntu", 0, "Regular Class", 0 }, + { "Verdana Clones", 0, "", 0 }, + }; + + +#define SKIP_OFFPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE 1 + SPH_TweakRule SKIP_OFFPIXEL_Y_MOVES_Rules_Exceptions + [SKIP_OFFPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE] = + { + { "-", 0, "", 0 }, + }; + + + /* Round moves that don't move a point to a Y pixel boundary */ +#define ROUND_NONPIXEL_Y_MOVES_RULES_SIZE 3 + SPH_TweakRule ROUND_NONPIXEL_Y_MOVES_Rules + [ROUND_NONPIXEL_Y_MOVES_RULES_SIZE] = + { + /* Droid font instructions don't snap Y to pixels */ + { "Droid Sans", 0, "Regular/Italic Class", 0 }, + { "Droid Sans Mono", 0, "", 0 }, + { "Ubuntu", 0, "", 0 }, + }; + +#define ROUND_NONPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE 3 + SPH_TweakRule ROUND_NONPIXEL_Y_MOVES_Rules_Exceptions + [ROUND_NONPIXEL_Y_MOVES_RULES_EXCEPTIONS_SIZE] = + { + { "Droid Sans", 12, "Bold", 0 }, + { "Droid Sans", 13, "Bold", 0 }, + { "Droid Sans", 16, "Bold", 0 }, + }; + + /* Allow a Direct_Move_X along X freedom vector if matched */ +#define ALLOW_X_DMOVEX_RULES_SIZE 2 + SPH_TweakRule ALLOW_X_DMOVEX_Rules + [ALLOW_X_DMOVEX_RULES_SIZE] = + { + /* Creates a more consistent appearance for these */ + { "Arial", 13, "Regular", 'e' }, + { "Arial", 13, "Regular", 'o' }, + }; + + /* Allow a Direct_Move along X freedom vector if matched */ +#define ALLOW_X_DMOVE_RULES_SIZE 3 + SPH_TweakRule ALLOW_X_DMOVE_Rules + [ALLOW_X_DMOVE_RULES_SIZE] = + { + /* Creates a more consistent appearance for these */ + { "Arial", 13, "Regular", 'e' }, + { "Arial", 13, "Regular", 'o' }, + /* Fixes vanishing diagonal in 4 */ + { "Verdana", 0, "Regular", '4' }, + }; + + /* Allow a ZP2 move along freedom vector if matched; */ + /* This is called from SHP, SHPIX, SHC, SHZ */ +#define ALLOW_X_MOVE_ZP2_RULES_SIZE 1 + SPH_TweakRule ALLOW_X_MOVE_ZP2_Rules + [ALLOW_X_MOVE_ZP2_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + + /* Return MS rasterizer version 35 if matched */ +#define RASTERIZER_35_RULES_SIZE 8 + SPH_TweakRule RASTERIZER_35_Rules + [RASTERIZER_35_RULES_SIZE] = + { + /* This seems to be the only way to make these look good */ + { "Times New Roman", 0, "Regular", 'i' }, + { "Times New Roman", 0, "Regular", 'j' }, + { "Times New Roman", 0, "Regular", 'm' }, + { "Times New Roman", 0, "Regular", 'r' }, + { "Times New Roman", 0, "Regular", 'a' }, + { "Times New Roman", 0, "Regular", 'n' }, + { "Times New Roman", 0, "Regular", 'p' }, + { "Times", 0, "", 0 }, + }; + + /* Don't round to the subpixel grid. Round to pixel grid. */ +#define NORMAL_ROUND_RULES_SIZE 2 + SPH_TweakRule NORMAL_ROUND_Rules + [NORMAL_ROUND_RULES_SIZE] = + { + /* Fix point "explosions" */ + { "Courier New", 0, "", 0 }, + { "Verdana", 10, "Regular", '4' }, + }; + + /* Skip IUP instructions if matched */ +#define SKIP_IUP_RULES_SIZE 1 + SPH_TweakRule SKIP_IUP_Rules + [SKIP_IUP_RULES_SIZE] = + { + { "Arial", 13, "Regular", 'a' }, + }; + + /* Skip MIAP Twilight hack if matched */ +#define MIAP_HACK_RULES_SIZE 1 + SPH_TweakRule MIAP_HACK_Rules + [MIAP_HACK_RULES_SIZE] = + { + { "Geneva", 12, "", 0 }, + }; + + /* Skip DELTAP instructions if matched */ +#define ALWAYS_SKIP_DELTAP_RULES_SIZE 13 + SPH_TweakRule ALWAYS_SKIP_DELTAP_Rules + [ALWAYS_SKIP_DELTAP_RULES_SIZE] = + { + { "Georgia", 0, "Regular", 'k' }, + /* fixes problems with W M w */ + { "Trebuchet MS", 0, "Italic", 0 }, + { "Trebuchet MS", 14, "Regular", 'e' }, + { "Arial", 11, "Regular", 's' }, + { "Verdana", 10, "Regular", 0 }, + { "Verdana", 9, "Regular", 0 }, + { "Legacy Sans Fonts", 0, "", L'й' }, + { "Arial", 10, "Regular", '6' }, + { "Arial", 0, "Bold/BoldItalic Class", 'a' }, + /* Make horizontal stems consistent with the rest */ + { "Arial", 24, "Bold", 's' }, + { "Arial", 25, "Bold", 's' }, + { "Arial", 24, "Bold", 'a' }, + { "Arial", 25, "Bold", 'a' }, + }; + + /* Always do DELTAP instructions if matched */ +#define ALWAYS_DO_DELTAP_RULES_SIZE 2 + SPH_TweakRule ALWAYS_DO_DELTAP_Rules + [ALWAYS_DO_DELTAP_RULES_SIZE] = + { + { "Verdana Clones", 17, "Regular Class", 'K' }, + { "Verdana Clones", 17, "Regular Class", 'k' }, + }; + + /* Do an extra RTG instruction in DELTAP if matched */ +#define DELTAP_RTG_RULES_SIZE 1 + SPH_TweakRule DELTAP_RTG_Rules + [DELTAP_RTG_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + + /* Force CVT distance to zero in MIRP */ +#define MIRP_CVT_ZERO_RULES_SIZE 1 + SPH_TweakRule MIRP_CVT_ZERO_Rules + [MIRP_CVT_ZERO_RULES_SIZE] = + { + { "Verdana", 0, "Regular", 0 }, + }; + + /* Skip moves that meet or exceed 1 pixel */ +#define DELTAP_SKIP_EXAGGERATED_VALUES_RULES_SIZE 2 + SPH_TweakRule DELTAP_SKIP_EXAGGERATED_VALUES_Rules + [DELTAP_SKIP_EXAGGERATED_VALUES_RULES_SIZE] = + { + /* Fix vanishing stems */ + { "Ubuntu", 0, "Regular", 'M' }, + /* Fix X at larger ppems */ + { "Segoe UI", 0, "Light", 0 }, + }; + + /* Don't allow ALIGNRP after IUP */ +#define NO_ALIGNRP_AFTER_IUP_RULES_SIZE 4 + SPH_TweakRule NO_ALIGNRP_AFTER_IUP_Rules + [NO_ALIGNRP_AFTER_IUP_RULES_SIZE] = + { + /* Prevent creation of dents in outline */ + { "Courier New", 0, "Bold", 'C' }, + { "Courier New", 0, "Bold", 'D' }, + { "Courier New", 0, "Bold", 'Q' }, + { "Courier New", 0, "Bold", '0' }, + }; + + /* Don't allow DELTAP after IUP */ +#define NO_DELTAP_AFTER_IUP_RULES_SIZE 2 + SPH_TweakRule NO_DELTAP_AFTER_IUP_Rules + [NO_DELTAP_AFTER_IUP_RULES_SIZE] = + { + { "Arial", 0, "Bold", 'N' }, + { "Verdana", 0, "Regular", '4' }, + }; + + /* Don't allow CALL after IUP */ +#define NO_CALL_AFTER_IUP_RULES_SIZE 4 + SPH_TweakRule NO_CALL_AFTER_IUP_Rules + [NO_CALL_AFTER_IUP_RULES_SIZE] = + { + /* Prevent creation of dents in outline */ + { "Courier New", 0, "Bold", 'O' }, + { "Courier New", 0, "Bold", 'Q' }, + { "Courier New", 0, "Bold", 'k' }, + { "Courier New", 0, "Bold Italic", 'M' }, + }; + + /* De-embolden these glyphs slightly */ +#define DEEMBOLDEN_RULES_SIZE 9 + SPH_TweakRule DEEMBOLDEN_Rules + [DEEMBOLDEN_RULES_SIZE] = + { + { "Courier New", 0, "Bold", 'A' }, + { "Courier New", 0, "Bold", 'W' }, + { "Courier New", 0, "Bold", 'w' }, + { "Courier New", 0, "Bold", 'M' }, + { "Courier New", 0, "Bold", 'X' }, + { "Courier New", 0, "Bold", 'K' }, + { "Courier New", 0, "Bold", 'x' }, + { "Courier New", 0, "Bold", 'z' }, + { "Courier New", 0, "Bold", 'v' }, + }; + + /* Embolden these glyphs slightly */ +#define EMBOLDEN_RULES_SIZE 5 + SPH_TweakRule EMBOLDEN_Rules + [EMBOLDEN_RULES_SIZE] = + { + { "Courier New", 12, "Italic", 'z' }, + { "Courier New", 11, "Italic", 'z' }, + { "Courier New", 10, "Italic", 'z' }, + { "Courier New", 0, "Regular", 0 }, + { "Courier New", 0, "Italic", 0 }, + }; + + /* Do an extra RDTG instruction in DELTAP if matched */ +#define DELTAP_RDTG_RULES_SIZE 1 + SPH_TweakRule DELTAP_RDTG_Rules + [DELTAP_RDTG_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + + /* This is a CVT hack that makes thick horizontal stems on 2, 5, 7 */ + /* similar to Windows XP. */ +#define TIMES_NEW_ROMAN_HACK_RULES_SIZE 12 + SPH_TweakRule TIMES_NEW_ROMAN_HACK_Rules + [TIMES_NEW_ROMAN_HACK_RULES_SIZE] = + { + { "Times New Roman", 16, "Italic", '2' }, + { "Times New Roman", 16, "Italic", '5' }, + { "Times New Roman", 16, "Italic", '7' }, + { "Times New Roman", 16, "Regular", '2' }, + { "Times New Roman", 16, "Regular", '5' }, + { "Times New Roman", 16, "Regular", '7' }, + { "Times New Roman", 17, "Italic", '2' }, + { "Times New Roman", 17, "Italic", '5' }, + { "Times New Roman", 17, "Italic", '7' }, + { "Times New Roman", 17, "Regular", '2' }, + { "Times New Roman", 17, "Regular", '5' }, + { "Times New Roman", 17, "Regular", '7' }, + }; + + + /* This fudges distance on 2 to get rid of the vanishing stem issue. */ + /* A real solution to this is certainly welcome. */ +#define COURIER_NEW_2_HACK_RULES_SIZE 15 + SPH_TweakRule COURIER_NEW_2_HACK_Rules + [COURIER_NEW_2_HACK_RULES_SIZE] = + { + { "Courier New", 10, "Regular", '2' }, + { "Courier New", 11, "Regular", '2' }, + { "Courier New", 12, "Regular", '2' }, + { "Courier New", 13, "Regular", '2' }, + { "Courier New", 14, "Regular", '2' }, + { "Courier New", 15, "Regular", '2' }, + { "Courier New", 16, "Regular", '2' }, + { "Courier New", 17, "Regular", '2' }, + { "Courier New", 18, "Regular", '2' }, + { "Courier New", 19, "Regular", '2' }, + { "Courier New", 20, "Regular", '2' }, + { "Courier New", 21, "Regular", '2' }, + { "Courier New", 22, "Regular", '2' }, + { "Courier New", 23, "Regular", '2' }, + { "Courier New", 24, "Regular", '2' }, + }; + + +#ifndef FORCE_NATURAL_WIDTHS + + /* Use compatible widths with these glyphs. Compatible widths is always */ + /* on when doing B/W TrueType instructing, but is used selectively here, */ + /* typically on glyphs with 3 or more vertical stems. */ +#define COMPATIBLE_WIDTHS_RULES_SIZE 36 + SPH_TweakRule COMPATIBLE_WIDTHS_Rules + [COMPATIBLE_WIDTHS_RULES_SIZE] = + { + { "Arial Unicode MS", 12, "Regular Class", 'm' }, + { "Arial Unicode MS", 14, "Regular Class", 'm' }, + { "Arial", 10, "Regular Class", L'ш' }, + { "Arial", 11, "Regular Class", 'm' }, + { "Arial", 12, "Regular Class", 'm' }, + { "Arial", 12, "Regular Class", L'ш' }, + { "Arial", 13, "Regular Class", L'ш' }, + { "Arial", 14, "Regular Class", 'm' }, + { "Arial", 14, "Regular Class", L'ш' }, + { "Arial", 15, "Regular Class", L'ш' }, + { "Arial", 17, "Regular Class", 'm' }, + { "DejaVu Sans", 15, "Regular Class", 0 }, + { "Microsoft Sans Serif", 11, "Regular Class", 0 }, + { "Microsoft Sans Serif", 12, "Regular Class", 0 }, + { "Segoe UI", 11, "Regular Class", 0 }, + { "Segoe UI", 12, "Regular Class", 'm' }, + { "Segoe UI", 14, "Regular Class", 'm' }, + { "Tahoma", 11, "Regular Class", 0 }, + { "Times New Roman", 16, "Regular Class", 'c' }, + { "Times New Roman", 16, "Regular Class", 'm' }, + { "Times New Roman", 16, "Regular Class", 'o' }, + { "Times New Roman", 16, "Regular Class", 'w' }, + { "Trebuchet MS", 12, "Regular Class", 0 }, + { "Trebuchet MS", 14, "Regular Class", 0 }, + { "Trebuchet MS", 15, "Regular Class", 0 }, + { "Ubuntu", 12, "Regular Class", 'm' }, + { "Verdana", 10, "Regular Class", L'ш' }, + { "Verdana", 11, "Regular Class", L'ш' }, + { "Verdana and Clones", 12, "Regular Class", 'm' }, + { "Verdana and Clones", 12, "Regular Class", 'l' }, + { "Verdana and Clones", 12, "Regular Class", 'i' }, + { "Verdana and Clones", 12, "Regular Class", 'j' }, + { "Verdana and Clones", 13, "Regular Class", 'l' }, + { "Verdana and Clones", 13, "Regular Class", 'i' }, + { "Verdana and Clones", 13, "Regular Class", 'j' }, + { "Verdana and Clones", 14, "Regular Class", 'm' }, + }; + + + /* Scaling slightly in the x-direction prior to hinting results in */ + /* more visually pleasing glyphs in certain cases. */ + /* This sometimes needs to be coordinated with compatible width rules. */ +#define X_SCALING_RULES_SIZE 40 + SPH_ScaleRule X_SCALING_Rules + [X_SCALING_RULES_SIZE] = + { + { "DejaVu Sans", 12, "Regular Class", 'm', .95 }, + { "Verdana and Clones", 12, "Regular Class", 'a', 1.1 }, + { "Arial", 11, "Regular Class", 'm', .975 }, + { "Arial", 12, "Regular Class", 'm', 1.05 }, + { "Arial", 13, "Regular Class", L'л', .95 }, + { "Arial", 14, "Regular Class", 'm', .95 }, + { "Arial", 15, "Regular Class", L'л', .925 }, + { "Bitstream Vera Sans", 10, "Regular Class", 0, 1.1 }, + { "Bitstream Vera Sans", 12, "Regular Class", 0, 1.05}, + { "Bitstream Vera Sans", 16, "Regular Class", 0, 1.05 }, + { "Bitstream Vera Sans", 9, "Regular Class", 0, 1.05}, + { "DejaVu Sans", 12, "Regular Class", 'l', .975 }, + { "DejaVu Sans", 12, "Regular Class", 'i', .975 }, + { "DejaVu Sans", 12, "Regular Class", 'j', .975 }, + { "DejaVu Sans", 13, "Regular Class", 'l', .95 }, + { "DejaVu Sans", 13, "Regular Class", 'i', .95 }, + { "DejaVu Sans", 13, "Regular Class", 'j', .95 }, + { "DejaVu Sans", 10, "Regular Class", 0, 1.1 }, + { "DejaVu Sans", 12, "Regular Class", 0, 1.05 }, + { "Georgia", 10, "", 0, 1.05 }, + { "Georgia", 11, "", 0, 1.1 }, + { "Georgia", 12, "", 0, 1.025 }, + { "Georgia", 13, "", 0, 1.05 }, + { "Georgia", 16, "", 0, 1.05 }, + { "Georgia", 17, "", 0, 1.03 }, + { "Liberation Sans", 12, "Regular Class", 'm', 1.1 }, + { "Lucida Grande", 11, "Regular Class", 'm', 1.1 }, + { "Microsoft Sans Serif", 11, "Regular Class", 'm', .95 }, + { "Microsoft Sans Serif", 12, "Regular Class", 'm', 1.05 }, + { "Segoe UI", 12, "Regular Class", 'H', 1.05 }, + { "Segoe UI", 12, "Regular Class", 'm', 1.05 }, + { "Segoe UI", 14, "Regular Class", 'm', 1.05 }, + { "Tahoma", 11, "Regular Class", 'm', .975 }, + { "Verdana", 10, "Regular Class", 0, 1.1 }, + { "Verdana", 12, "Regular Class", 'm', .975 }, + { "Verdana", 12, "Regular Class", 0, 1.05 }, + { "Verdana", 16, "Regular Class", 0, 1.05 }, + { "Verdana", 9, "Regular Class", 0, 1.05 }, + { "Times New Roman", 16, "Regular Class", 'm', .95 }, + { "Trebuchet MS", 12, "Regular Class", 'm', .95 }, + }; +#else +#define COMPATIBLE_WIDTHS_RULES_SIZE 1 + SPH_TweakRule COMPATIBLE_WIDTHS_Rules + [COMPATIBLE_WIDTHS_RULES_SIZE] = + { + { "-", 0, "", 0 }, + }; + +#define X_SCALING_RULES_SIZE 1 + SPH_ScaleRule X_SCALING_Rules + [X_SCALING_RULES_SIZE] = + { + { "-", 0, "", 0, 1.0 }, + }; +#endif /* FORCE_NATURAL_WIDTHS */ + +#endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + +#endif /* __TTSUBPIX_H__ */ + +/* END */ -- cgit v1.2.3