>From 4e5d17190c6727e7db5cf9a8fe0b092ca3e967f1 Mon Sep 17 00:00:00 2001 From: Nikolaus Waxweiler Date: Sun, 13 Mar 2016 15:53:21 +0100 Subject: [PATCH 1/2] Implement v38 bytecode interpreter and make it the default [1/2] 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). FreeType employs a small list of font-agnostic hacks to bludgeon non-native-ClearType fonts (except tricky ones[1]) into submission. FreeType will not try to toggle hacks for specific fonts for performance and complexity reasons. The focus is on modern (web)fonts rather than legacy fonts that were made for black-and-white rendering. Major hacks: - Any point movement on the X-axis will be ignored (cf. Direct_Move() and Direct_Move_X()). This has the smallest code footprint and single biggest effect. - Points will not be moved post-IUP, except the x-component of diagonal moves post-IUP (cf. Direct_Move(), Move_Zp2_Point()). - SHPIX and DELTAP will not execute unless moving a composite on the Y-axis or moving a previously Y-touched point. SHPIX will additionally deny movement on the X-axis (c.f. Ins_SHPIX() and Ins_DELTAP()). - The hdmx table and modifications to phantom points are ignored. Bearings and advance widths remain unchanged (except rounding them outside the interpreter!). C.f. compute_glyph_metrics() and TT_Hint_Glyph(). Minor hacks: - All rounding instructions are turned into Round_None if the freedom vector is parallel to the X-axis (c.f. Round_*()). - SHP will deny movement on the X-axis (c.f. Ins_SHP()) - FLIPRGON, FLIPRGOFF and FLIPPT will not execute post-IUP. - MSIRP, MIAP, MDRP and MIRP will see a control value cut-in and minimum distance of 0 if the freedom vector is parallel to the X-axis (c.f. Ins_MSIRP(), Ins_MIAP(), Ins_MDRP() and Ins_MIRP()). Post-IUP is the state after both IUP[x] and IUP[y] have been executed. 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 and don't mess with the "final" outline to produce more pleasing pixel patterns. The harder the designer tried to produce very specific patterns ("superhinting") for pre-ClearType-display, the worse the results. Microsoft defines a way to turn off backwards compatibility and interpret instructions as before ("native ClearType")[2][3]. 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[4]. The v38 interpreter assumes backwards compatibility by default, which can be turned off the same way by executing the following in the control program: #PUSH 4,3 INSTCTRL[] (cf. Ins_INSTCTRL()). [1]: Tricky fonts as FreeType defines them rely on the bytecode interpreter to display correctly. Hacks can interfere with them, so they get treated like native ClearType fonts (v38 with backwards compatibility turned off). C.f. TT_RunIns(). [2]: Proposed by Microsoft's Greg Hitchcock in https://www.microsoft.com/typography/cleartype/truetypecleartype.aspx#Toc227035738 [3]: Beat Stamm describes it in more detail: http://www.beatstamm.com/typography/RTRCh4.htm#Sec12 [4]: 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. --- src/truetype/ttdriver.c | 5 +-- src/truetype/ttgload.c | 69 +++++++++++++++++++++++++++------ src/truetype/ttinterp.h | 100 +++++++++++++++++++++++++++++++++++++++++++++++- src/truetype/ttobjs.c | 5 --- 4 files changed, 159 insertions(+), 20 deletions(-) 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..5635303 100644 --- a/src/truetype/ttgload.c +++ b/src/truetype/ttgload.c @@ -815,11 +815,16 @@ #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]; + } #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING if ( driver->interpreter_version == TT_INTERPRETER_VERSION_38 ) @@ -1969,10 +1974,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 +2195,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 +2203,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 +2214,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 +2252,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 +2324,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 +2363,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: grayscale 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..930a82e 100644 --- a/src/truetype/ttinterp.h +++ b/src/truetype/ttinterp.h @@ -247,7 +247,105 @@ 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). + * + * FreeType employs a small list of font-agnostic hacks to bludgeon + * non-native-ClearType fonts (except tricky ones[1]) into submission. + * FreeType will not try to toggle hacks for specific fonts for performance + * and complexity reasons. The focus is on modern (web)fonts rather than + * legacy fonts that were made for black-and-white rendering. + * + * Major hacks: + * - Any point movement on the X-axis will be ignored (cf. Direct_Move() + * and Direct_Move_X()). This has the smallest code footprint and single + * biggest effect. + * - Points will not be moved post-IUP, except the x-component of diagonal + * moves post-IUP (cf. Direct_Move(), Move_Zp2_Point()). + * - SHPIX and DELTAP will not execute unless moving a composite on the + * Y-axis or moving a previously Y-touched point. SHPIX will additionally + * deny movement on the X-axis (c.f. Ins_SHPIX() and Ins_DELTAP()). + * - The hdmx table and modifications to phantom points are ignored. + * Bearings and advance widths remain unchanged (except rounding them + * outside the interpreter!). C.f. compute_glyph_metrics() and + * TT_Hint_Glyph(). + * + * Minor hacks: + * - All rounding instructions are turned into Round_None if the freedom + * vector is parallel to the X-axis (c.f. Round_*()). + * - SHP will deny movement on the X-axis (c.f. Ins_SHP()) + * - FLIPRGON, FLIPRGOFF and FLIPPT will not execute post-IUP. + * - MSIRP, MIAP, MDRP and MIRP will see a control value cut-in and minimum + * distance of 0 if the freedom vector is parallel to the X-axis (c.f. + * Ins_MSIRP(), Ins_MIAP(), Ins_MDRP() and Ins_MIRP()). + * + * Post-IUP is the state after both IUP[x] and IUP[y] have been executed. + * + * 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 and don't mess with the "final" outline to produce more pleasing + * pixel patterns. The harder the designer tried to produce very specific + * patterns ("superhinting") for pre-ClearType-display, the worse the + * results. + * + * Microsoft defines a way to turn off backwards compatibility and + * interpret instructions as before ("native ClearType")[2][3]. 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[4]. The v38 interpreter assumes backwards + * compatibility by default, which can be turned off the same way by + * executing the following in the control program: + * + * #PUSH 4,3 + * INSTCTRL[] + * + * (cf. Ins_INSTCTRL()). + * + * [1]: Tricky fonts as FreeType defines them rely on the bytecode + * interpreter to display correctly. Hacks can interfere with them, so + * they get treated like native ClearType fonts (v38 with + * backwards compatibility turned off). C.f. TT_RunIns(). + * [2]: Proposed by Microsoft's Greg Hitchcock in + * https://www.microsoft.com/typography/cleartype/truetypecleartype.aspx#Toc227035738 + * [3]: Beat Stamm describes it in more detail: + * http://www.beatstamm.com/typography/RTRCh4.htm#Sec12 + * [4]: 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 */ + + /* Defaults to true with v38 interpreter. If this is false, it implies the + * interpreter is in v35 or in native ClearType mode. */ + FT_Bool backwards_compatibility; + + /* Useful for detecting and denying post-iup trickery that is usually used + * to fix pixel patterns ("superhinting"). */ + FT_Bool iupx_called; + FT_Bool iupy_called; + + /* 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 a05f216..953931f 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 */ -- 2.5.0