diff --git a/src/truetype/ttgload.c b/src/truetype/ttgload.c index 4ab6603..5a68052 100644 --- a/src/truetype/ttgload.c +++ b/src/truetype/ttgload.c @@ -1945,6 +1945,7 @@ #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING TT_Driver driver = (TT_Driver)FT_FACE_DRIVER( face ); #endif + TT_Driver driver = (TT_Driver)FT_FACE_DRIVER( face ); FT_BBox bbox; FT_Fixed y_scale; @@ -1969,10 +1970,14 @@ glyph->metrics.horiBearingY = bbox.yMax; glyph->metrics.horiAdvance = loader->pp2.x - loader->pp1.x; - /* adjust advance width to the value contained in the hdmx table */ - /* unless FT_LOAD_COMPUTE_METRICS is set */ - if ( !face->postscript.isFixedPitch && - IS_HINTED( loader->load_flags ) && + /* Adjust advance width to the value contained in the hdmx table + * unless FT_LOAD_COMPUTE_METRICS is set. Interpreter v38 uses subpixel + * hinting and indicates subpixel positioning and therefore ignores any + * changes to the horizontal advance width. XXX: does this clash with any + * non-bytecode-advance-width-changing-feature? */ + if ( driver->interpreter_version != TT_INTERPRETER_VERSION_38 && + !face->postscript.isFixedPitch && + IS_HINTED( loader->load_flags ) && !( loader->load_flags & FT_LOAD_COMPUTE_METRICS ) ) { FT_Byte* widthp; @@ -2186,6 +2191,7 @@ TT_Face face; FT_Stream stream; + TT_Driver driver; #ifdef TT_USE_BYTECODE_INTERPRETER FT_Bool pedantic = FT_BOOL( load_flags & FT_LOAD_PEDANTIC ); #endif @@ -2193,6 +2199,7 @@ face = (TT_Face)glyph->face; stream = face->root.stream; + driver = (TT_Driver)FT_FACE_DRIVER( face ); FT_MEM_ZERO( loader, sizeof ( TT_LoaderRec ) ); @@ -2203,6 +2210,8 @@ { TT_ExecContext exec; FT_Bool grayscale; + FT_Bool subpixel_hinting; + FT_Bool grayscale_cleartype; #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING TT_Driver driver = (TT_Driver)FT_FACE_DRIVER( face ); @@ -2239,6 +2248,18 @@ if ( !exec ) return FT_THROW( Could_Not_Find_Context ); + if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) + { + subpixel_hinting = TRUE; + exec->backwards_compatibility = ! (exec->GS.instruct_control & 4) ; + grayscale_cleartype = ! FT_BOOL( load_flags & FT_LOAD_TARGET_LCD || + load_flags & FT_LOAD_TARGET_LCD_V ); + } else { + subpixel_hinting = FALSE; + exec->backwards_compatibility = FALSE; + grayscale_cleartype = FALSE; + } + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) @@ -2299,8 +2320,8 @@ #endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ { - grayscale = FT_BOOL( FT_LOAD_TARGET_MODE( load_flags ) != - FT_RENDER_MODE_MONO ); + grayscale = FT_BOOL( ! subpixel_hinting && + FT_LOAD_TARGET_MODE( load_flags ) != FT_RENDER_MODE_MONO ); } error = TT_Load_Context( exec, face, size ); @@ -2338,6 +2359,28 @@ #endif /* 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_TRACE4(( "tt_loader_init: subpixel hinting change," + " re-executing `prep' table\n" )); + + exec->subpixel_hinting = subpixel_hinting; + reexecute = TRUE; + } + + /* a change from colored to grayscale subpixel rendering (and vice + * versa) requires a re-execution of the CVT program */ + if ( grayscale_cleartype != exec->grayscale_cleartype ) + { + FT_TRACE4(( "tt_loader_init: subpixel hinting change," + " re-executing `prep' table\n" )); + + exec->grayscale_cleartype = grayscale_cleartype; + reexecute = TRUE; + } + /* a change from mono to grayscale rendering (and vice versa) */ /* requires a re-execution of the CVT program */ if ( grayscale != exec->grayscale ) diff --git a/src/truetype/ttinterp.c b/src/truetype/ttinterp.c index 1e34151..e539cda 100644 --- a/src/truetype/ttinterp.c +++ b/src/truetype/ttinterp.c @@ -1698,6 +1698,7 @@ ( !exc->ignore_x_mode || ( exc->sph_tweak_flags & SPH_TWEAK_ALLOW_X_DMOVE ) ) ) #endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + if ( exc->backwards_compatibility && exc->GS.freeVector.y != 0x0 ) zone->cur[point].x += FT_MulDiv( distance, v, exc->F_dot_P ); zone->tags[point] |= FT_CURVE_TAG_TOUCH_X; @@ -1778,6 +1779,7 @@ if ( !SUBPIXEL_HINTING || !exc->ignore_x_mode ) #endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ + if ( exc->backwards_compatibility && exc->GS.freeVector.y != 0x0 ) zone->cur[point].x += distance; zone->tags[point] |= FT_CURVE_TAG_TOUCH_X; @@ -5176,6 +5178,8 @@ if ( K == 3 ) exc->ignore_x_mode = FT_BOOL( L == 4 ); #endif + if ( K == 3 ) + exc->backwards_compatibility = ! FT_BOOL( L == 4 ); } @@ -5440,7 +5444,8 @@ if ( exc->GS.freeVector.x != 0 ) { - exc->zp2.cur[point].x += dx; + if ( exc->backwards_compatibility && exc->GS.freeVector.y != 0x0 ) + exc->zp2.cur[point].x += dx; if ( touch ) exc->zp2.tags[point] |= FT_CURVE_TAG_TOUCH_X; } @@ -5911,6 +5916,9 @@ cvtEntry = (FT_ULong)args[1]; point = (FT_UShort)args[0]; + if ( exc->backwards_compatibility && exc->GS.freeVector.y == 0x0 ) + control_value_cutin = 0; + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( SUBPIXEL_HINTING && exc->ignore_x_mode && @@ -6019,6 +6027,14 @@ minimum_distance = exc->GS.minimum_distance; + if ( exc->backwards_compatibility && exc->GS.freeVector.y == 0x0 ) + minimum_distance = 0; + + if ( exc->backwards_compatibility && exc->GS.freeVector.x == 0x0 ) + { + minimum_distance = minimum_distance / 2; + } + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( SUBPIXEL_HINTING && exc->ignore_x_mode && @@ -6172,6 +6188,15 @@ point = (FT_UShort)args[0]; cvtEntry = (FT_ULong)( args[1] + 1 ); + if ( exc->backwards_compatibility && exc->GS.freeVector.y == 0x0 ) + control_value_cutin = minimum_distance = 0; + + if ( exc->backwards_compatibility && exc->GS.freeVector.x == 0x0 ) + { + control_value_cutin = control_value_cutin / 2; + minimum_distance = minimum_distance / 2; + } + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( SUBPIXEL_HINTING && exc->ignore_x_mode && @@ -7286,9 +7311,11 @@ FT_Long* args ) { FT_Long K; + TT_Driver driver; K = 0; + driver = (TT_Driver)FT_FACE_DRIVER( exc->face ); #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING /********************************/ @@ -7314,7 +7341,7 @@ else #endif /* TT_CONFIG_OPTION_SUBPIXEL_HINTING */ if ( ( args[0] & 1 ) != 0 ) - K = TT_INTERPRETER_VERSION_35; + K = driver->interpreter_version; /********************************/ /* GLYPH ROTATED */ @@ -7333,13 +7360,57 @@ K |= 1 << 8; /********************************/ - /* HINTING FOR GRAYSCALE */ + /* BI-LEVEL HINTING AND */ + /* GRAYSCALE RENDERING */ /* Selector Bit: 5 */ /* Return Bit(s): 12 */ /* */ if ( ( args[0] & 32 ) != 0 && exc->grayscale ) K |= 1 << 12; + + if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) + { + /********************************/ + /* HINTING FOR SUBPIXEL */ + /* Selector Bit: 6 */ + /* Return Bit(s): 13 */ + /* */ + /* v38 will do subpixel hinting by default. */ + if ( ( args[0] & 64 ) != 0 ) + K |= 1 << 13; + + /********************************/ + /* SUBPIXEL POSITIONED? */ + /* Selector Bit: 10 */ + /* Return Bit(s): 17 */ + /* */ + /* XXX: FreeType supports it, dependant on what client does? */ + if ( ( args[0] & 1024 ) != 0 ) + K |= 1 << 17; + + /********************************/ + /* SYMMETRICAL SMOOTHING */ + /* Selector Bit: 11 */ + /* Return Bit(s): 18 */ + /* */ + /* The only smoothing method FreeType supports unless someone set + * FT_LOAD_TARGET_MONO. */ + if ( ( args[0] & 2048 ) != 0 ) + K |= 1 << 18; + + /********************************/ + /* CLEARTYPE HINTING AND */ + /* GRAYSCALE RENDERING */ + /* Selector Bit: 12 */ + /* Return Bit(s): 19 */ + /* */ + /* Grayscale rendering is what FreeType does anyway unless someone set + * FT_LOAD_TARGET_MONO or FT_LOAD_TARGET_LCD(_V) */ + if ( ( args[0] & 4096 ) != 0 && exc->grayscale_cleartype ) + K |= 1 << 19; + } + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( SUBPIXEL_HINTING && diff --git a/src/truetype/ttinterp.h b/src/truetype/ttinterp.h index e5a02b9..3e2503f 100644 --- a/src/truetype/ttinterp.h +++ b/src/truetype/ttinterp.h @@ -247,7 +247,52 @@ FT_BEGIN_HEADER TT_Set_CVT_Func func_write_cvt; /* write a cvt entry (in pixels) */ TT_Set_CVT_Func func_move_cvt; /* incr a cvt entry (in pixels) */ - FT_Bool grayscale; /* are we hinting for grayscale? */ + FT_Bool grayscale; /* Bi-level hinting and grayscale + rendering */ + + /* Modern TrueType fonts are usually rendered through Microsoft's + * collection of rendering techniques called ClearType. When ClearType was + * introduced, most fonts were not ready. Microsoft decided to implement a + * backwards compatibility mode that employed several simple to complicated + * assumptions and tricks that modified the interpretation of the bytecode + * contained by the fonts to make them look ClearType-y somehow. Most + * (web)fonts that were released since then have come to rely on these + * hacks to render correctly, even some of Microsoft's flagship ClearType + * fonts (Calibri, Cambria, Segoe UI). Microsoft describes a way to turn + * off backwards compatibility and interpret instructions as before + * ("native ClearType")[1]. The font designer then regains full control and + * is responsible for making the font work correctly with ClearType without + * any hand-holding by the interpreter or rasterizer[2]. + * + * Of the hacks implemented in FreeType, ignoring any point movement on the + * X-axis if the freedom vector is parallel to the X-axis has the smallest + * code footprint and single biggest effect (cf. Direct_Move() and + * Direct_Move_X(), also Move_Zp2_Point()). The best results are achieved + * for fonts that were from the outset designed with ClearType in mind, + * "classically" hinted fonts like Arial and Times fare far worse. + * + * The v38 interpreter assumes backwards compatibility by default. Fonts + * can turn it off and go "native ClearType" by using the following + * bytecode sequence at the beginning of the CVT program[1]: + * + * #PUSH 4,3 + * INSTCTRL[] + * + * (cf. Ins_INSTCTRL()). + * + * [1]: Proposed by Microsoft's Greg Hitchcock in + * https://www.microsoft.com/typography/cleartype/truetypecleartype.aspx#Toc227035738 + * [2]: The list of "native ClearType" fonts is small at the time of this + * writing, I found the following on a Windows 10 Update 1511 installation: + * Constantia, Corbel, Sitka, Malgun Gothic, Microsoft JhengHei (Bold and + * UI Bold), Microsoft YaHei (Bold and UI Bold), SimSun, NSimSun and Yu Gothic. + * */ + FT_Bool subpixel_hinting; + FT_Bool backwards_compatibility; + + /* ClearType hinting and grayscale rendering. Different from bi-level + * hinting and grayscale rendering! */ + FT_Bool grayscale_cleartype; #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING TT_Round_Func func_round_sphn; /* subpixel rounding function */ diff --git a/src/truetype/ttobjs.c b/src/truetype/ttobjs.c index 419ce35..1fe11f7 100644 --- a/src/truetype/ttobjs.c +++ b/src/truetype/ttobjs.c @@ -1308,12 +1308,7 @@ #ifdef TT_USE_BYTECODE_INTERPRETER TT_Driver driver = (TT_Driver)ttdriver; - -#ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING driver->interpreter_version = TT_INTERPRETER_VERSION_38; -#else - driver->interpreter_version = TT_INTERPRETER_VERSION_35; -#endif #else /* !TT_USE_BYTECODE_INTERPRETER */