diff --git a/src/truetype/ttdriver.c b/src/truetype/ttdriver.c index bbebabd..a856fed 100644 --- a/src/truetype/ttdriver.c +++ b/src/truetype/ttdriver.c @@ -72,11 +72,10 @@ FT_UInt* interpreter_version = (FT_UInt*)value; -#ifndef TT_CONFIG_OPTION_SUBPIXEL_HINTING - if ( *interpreter_version != TT_INTERPRETER_VERSION_35 ) + if ( *interpreter_version != TT_INTERPRETER_VERSION_35 && + *interpreter_version != TT_INTERPRETER_VERSION_38 ) error = FT_ERR( Unimplemented_Feature ); else -#endif driver->interpreter_version = *interpreter_version; return error; diff --git a/src/truetype/ttgload.c b/src/truetype/ttgload.c index 4ab6603..7689595 100644 --- a/src/truetype/ttgload.c +++ b/src/truetype/ttgload.c @@ -815,11 +815,26 @@ #endif - /* save glyph phantom points */ - loader->pp1 = zone->cur[zone->n_points - 4]; - loader->pp2 = zone->cur[zone->n_points - 3]; - loader->pp3 = zone->cur[zone->n_points - 2]; - loader->pp4 = zone->cur[zone->n_points - 1]; + /* Save possibly modified glyph phantom points unless in v38 backwards + * compatibility mode, where no movement on the X-axis means no reason to + * change bearings or advance widths. */ + if ( !loader->exec->backwards_compatibility ) + { + loader->pp1 = zone->cur[zone->n_points - 4]; + loader->pp2 = zone->cur[zone->n_points - 3]; + loader->pp3 = zone->cur[zone->n_points - 2]; + loader->pp4 = zone->cur[zone->n_points - 1]; + } else { + FT_UShort n; + + /* Clean up everything that got through the backwards compatibility mode + * by resetting all x-coordinates to their previous positions. Only do + * this for glyphs that are instructed, otherwise we get garbled output. + */ + if ( n_ins > 0 ) + for ( n = 0; n < zone->n_points - 4; n++ ) + zone->cur[n].x = zone->org[n].x; + } #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) @@ -1969,10 +1984,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 or backwards compatibility mode of the + * v38 interpreter is active. Non-"native ClearType" fonts should be + * stopped from changing advance widths or spacing may suffer, "native + * ClearType" fonts are trusted to know what they're doing. */ + if ( !( loader->exec && loader->exec->backwards_compatibility ) && + !face->postscript.isFixedPitch && + IS_HINTED( loader->load_flags ) && !( loader->load_flags & FT_LOAD_COMPUTE_METRICS ) ) { FT_Byte* widthp; @@ -2186,6 +2205,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 +2213,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 +2224,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 +2262,18 @@ if ( !exec ) return FT_THROW( Could_Not_Find_Context ); + if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) + { + subpixel_hinting = TRUE; + grayscale_cleartype = ! FT_BOOL( load_flags & FT_LOAD_TARGET_LCD || + load_flags & FT_LOAD_TARGET_LCD_V ); + exec->vertical_lcd = FT_BOOL( load_flags & FT_LOAD_TARGET_LCD_V ); + } else { + subpixel_hinting = FALSE; + grayscale_cleartype = FALSE; + exec->vertical_lcd = FALSE; + } + #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) @@ -2299,8 +2334,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 +2373,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.h b/src/truetype/ttinterp.h index e5a02b9..218aa46 100644 --- a/src/truetype/ttinterp.h +++ b/src/truetype/ttinterp.h @@ -247,7 +247,68 @@ 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 (e.g. subpixel + * rendering and subpixel hinting). 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 in these 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()). The best results are achieved for fonts that were from + * the outset designed with ClearType in mind, meaning they leave the X-axis + * mostly alone. The harder the designer tried to produce very specific + * black-and-white pixel patterns ("superhinting") for + * pre-ClearType-display, the worse the results. Most web fonts seen in the + * wild are made for ClearType display. + * + * 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; /* Using v38 implies this. */ + FT_Bool vertical_lcd; /* long side of LCD subpixel */ + /* rectangles is horizontal */ + + FT_Bool backwards_compatibility; /* Defaults to true with v38 + interpreter. If this is + false, it implies the + interpreter is in v35 or in + native ClearType mode. */ + FT_Bool iup_called; /* Useful for detecting and denying post-iup trickery. */ + + /* ClearType hinting and grayscale rendering, as used by Universal Windows + * Platform apps (Windows 8 and above). Like the standard colorful + * ClearType mode, it utilizes a vastly increased virtual resolution on the + * X-axis. Different from bi-level hinting and grayscale rendering, the + * old mode from Win9x days that roughly adheres to the physical pixel grid + * on both axes. */ + 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 cd4e294..4b43bb4 100644 --- a/src/truetype/ttobjs.c +++ b/src/truetype/ttobjs.c @@ -1286,12 +1286,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 */