Hey list,
so Derek's mail made me look at the TrueType engine again.

The attached patch does the following:
- v38 is the default version
- implement GETINFO selector bits 6, 10, 11 and 12 for subpixel_hinting (v38 -> always on), subpixel_positioning (v38 -> always on), symmetrical smoothing (always on), grayscale_cleartype (on unless FT_LOAD_TARGET_LCD(_V)) - Minimal backwards compatibility mode: stop all x-Movement in Direct_Move(), Direct_Move_X() and Move_Zp2_Point() iff freedom vector is parallel to x-axis. This adds 3 lines and does 90% of the work to make fonts intended for ClearType rendering render well. Most fonts out there that are intended for ClearType rendering actually rely on the hacks of Microsoft's backwards compatibility mode to render well. This hack does the job for most ClearType-able fonts, even for Microsoft's flagships such as Calibri, Cambria and Segoe UI. Pre-ClearType fonts like Arial and Times fare much worse. Also, additional cvt cutin/minimum distance stuff Infinality did (did not systematically check if this needs to be added to more places). - Fonts can turn off backwards compatibility via Microsoft's INSTCTRL spell. On my Windows 10 Update 1511 installation, I found Constantia, Corbel, Sitka, Malgun Gothic, Microsoft JhengHei (Bold and UI Bold), Microsoft YaHei (Bold and UI Bold), SimSun, NSimSun and Yu Gothic that do it.
- Changes to the (horizontal?) advance width are shorted out if on v38.

Apply on top of unchanged source, leave TT_CONFIG_OPTION_SUBPIXEL_HINTING off.

The patch is meant as a request for comments. I wanted to see if I could find a minimal portion of Infinality's work that would do a reasonable job for most webfonts I come across. It is also a sort-of preparation for a stem-darkening-able light mode for the TT engine. I personally am not fond of the complexity it brought to the code base and there is no one to do anything about it. If there is a way to sort out pre-ClearType fonts and refer them to the autohinter instead, how about going with a patch like this? The configurability would be gone but I personally think that much of it is misguided flexibility and unneeded baggage for pre-ClearType fonts. I'd much rather see improvements to the autohinter and a refactoring of the CFF grid-fitter into its' own module[1].

Known problems:
- Incompatible with #define TT_CONFIG_OPTION_SUBPIXEL_HINTING
- "Classically" hinted fonts like Arial, Times and DejaVu render ugly and with some glitches. Granted, I did nothing about delta hints and such yet and haven't looked closely at other Infinality changes. Is there any known way to detect pre-ClearType fonts and separate them from fonts that render nicely in ClearType but depend on MS' hacks? - Switching from v38 to v35 in ftview seems to hang something, haven't looked into it yet
- Some light fonts render less well, some ClearType-able fonts have glitches
- Rendering results by their nature slightly sharper than what the autohinter/CFF engine put out. Might not harmonize well. - Not suited for tricky fonts unless they go native ClearType? I have none to test.

[1]: I had a look at it and it seems to be intimately tied to the CFF engine. Reworking it into its' own module seems a non-trivial task.
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..e29ecde 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,9 @@
 
     minimum_distance = exc->GS.minimum_distance;
 
+    if ( exc->backwards_compatibility && exc->GS.freeVector.y == 0x0 )
+      minimum_distance = 0;
+
 #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING
     if ( SUBPIXEL_HINTING                                   &&
          exc->ignore_x_mode                                 &&
@@ -6172,6 +6183,9 @@
     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;
+
 #ifdef TT_CONFIG_OPTION_SUBPIXEL_HINTING
     if ( SUBPIXEL_HINTING                                   &&
          exc->ignore_x_mode                                 &&
@@ -7286,9 +7300,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 +7330,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 +7349,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 */
 
_______________________________________________
Freetype-devel mailing list
[email protected]
https://lists.nongnu.org/mailman/listinfo/freetype-devel

Reply via email to