shithub: freetype+ttf2subf

Download patch

ref: 34010f7c473330907b39bd7d55e985f407d7f87c
parent: 4fd9cc73e672de3223a44e9034f788de6641efd0
author: Werner Lemberg <[email protected]>
date: Tue Mar 14 17:50:22 EDT 2017

[sfnt] Implement PS names for font instances [3/3].

Everything is guarded with TT_CONFIG_OPTION_GX_VAR_SUPPORT.

* include/freetype/internal/tttypes.h (TT_FaceRec): New fields
`var_postscript_prefix' and `var_postscript_prefix_len'.

* src/sfnt/sfdriver.c: Include FT_TRUETYPE_IDS_H.
(sfnt_is_alphanumeric): New wrapperfunction for `ft_isalnum'.
(get_win_string, get_apple_string): Remove `const' from return
value.
(MAX_VALUE_DESCRIPTOR_LEN, MAX_PS_NAME_LEN): New macros.
(hexdigits): New array.
(sfnt_get_var_ps_name): New function, implementing Adobe TechNote
5902 to construct a PS name for a variation font instance.
(sfnt_get_ps_name): Call `sfnt_get_var_ps_name' for font instances.

* src/sfnt/sfobjs.c (sfnt_done_face): Updated.

* src/truetype/ttgxvar.c (tt_set_mm_blend): Reset
`face->postscript_name' to trigger recalculation for new instance
parameters.

git/fs: mount .git/fs: mount/attach disallowed
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,30 @@
 2017-03-14  Werner Lemberg  <[email protected]>
 
+	[sfnt] Implement PS names for font instances [3/3].
+
+	Everything is guarded with TT_CONFIG_OPTION_GX_VAR_SUPPORT.
+
+	* include/freetype/internal/tttypes.h (TT_FaceRec): New fields
+	`var_postscript_prefix' and `var_postscript_prefix_len'.
+
+	* src/sfnt/sfdriver.c: Include FT_TRUETYPE_IDS_H.
+	(sfnt_is_alphanumeric): New wrapperfunction for `ft_isalnum'.
+	(get_win_string, get_apple_string): Remove `const' from return
+	value.
+	(MAX_VALUE_DESCRIPTOR_LEN, MAX_PS_NAME_LEN): New macros.
+	(hexdigits): New array.
+	(sfnt_get_var_ps_name): New function, implementing Adobe TechNote
+	5902 to construct a PS name for a variation font instance.
+	(sfnt_get_ps_name): Call `sfnt_get_var_ps_name' for font instances.
+
+	* src/sfnt/sfobjs.c (sfnt_done_face): Updated.
+
+	* src/truetype/ttgxvar.c (tt_set_mm_blend): Reset
+	`face->postscript_name' to trigger recalculation for new instance
+	parameters.
+
+2017-03-14  Werner Lemberg  <[email protected]>
+
 	[sfnt] Implement PS names for font instances [2/3].
 
 	* src/sfnt/sfdriver.c (fix2float) [TT_CONFIG_OPTION_GX_VAR_SUPPORT]:
@@ -80,8 +105,8 @@
 	  https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=738
 
 	* src/sfnt/sfobjs.c (sfnt_init_face): While setting number of
-	instances to zero for `CFF' fonts table, ensure that there is no `glyf'
-	present also (which gets priority).
+	instances to zero for `CFF' fonts table, ensure that there is no
+	`glyf' table present also (which gets priority).
 
 2017-03-06  Werner Lemberg  <[email protected]>
 
--- a/include/freetype/freetype.h
+++ b/include/freetype/freetype.h
@@ -891,7 +891,7 @@
   /*                           variation fonts only, holding the named     */
   /*                           instance index for the current face index   */
   /*                           (starting with value~1; value~0 indicates   */
-  /*                           font access without variation data).  For   */
+  /*                           font access without a named instance).  For */
   /*                           non-variation fonts, bits 16-30 are         */
   /*                           ignored.  If we have the third named        */
   /*                           instance of face~4, say, `face_index' is    */
@@ -3402,6 +3402,13 @@
   /* <Note>                                                                */
   /*    The returned pointer is owned by the face and is destroyed with    */
   /*    it.                                                                */
+  /*                                                                       */
+  /*    For variation fonts, this string changes if you select a different */
+  /*    instance, and you have to call `FT_Get_PostScript_Name' again to   */
+  /*    retrieve it.  FreeType follows Adobe TechNote #5902, `Generating   */
+  /*    PostScript Names for Fonts Using OpenType Font Variations'.        */
+  /*                                                                       */
+  /*      http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/font/pdfs/5902.AdobePSNameGeneration.html */
   /*                                                                       */
   FT_EXPORT( const char* )
   FT_Get_Postscript_Name( FT_Face  face );
--- a/include/freetype/internal/tttypes.h
+++ b/include/freetype/internal/tttypes.h
@@ -1309,6 +1309,15 @@
   /*                            For example, TT_FACE_FLAG_VAR_FVAR is only */
   /*                            set if we have at least one design axis.   */
   /*                                                                       */
+  /*    var_postscript_prefix ::                                           */
+  /*                            The PostScript name prefix needed for      */
+  /*                            constructing a variation font instance's   */
+  /*                            PS name .                                  */
+  /*                                                                       */
+  /*    var_postscript_prefix_len ::                                       */
+  /*                            The length of the `var_postscript_prefix'  */
+  /*                            string.                                    */
+  /*                                                                       */
   /*    horz_metrics_size    :: The size of the `hmtx' table.              */
   /*                                                                       */
   /*    vert_metrics_size    :: The size of the `vmtx' table.              */
@@ -1502,6 +1511,10 @@
 
     FT_Bool               is_default_instance;   /* since 2.7.1 */
     FT_UInt32             variation_support;     /* since 2.7.1 */
+
+    const char*           var_postscript_prefix;     /* since 2.7.2 */
+    FT_Int                var_postscript_prefix_len; /* since 2.7.2 */
+
 #endif
 
     /* since version 2.2 */
--- a/src/sfnt/sfdriver.c
+++ b/src/sfnt/sfdriver.c
@@ -20,6 +20,7 @@
 #include FT_INTERNAL_DEBUG_H
 #include FT_INTERNAL_SFNT_H
 #include FT_INTERNAL_OBJECTS_H
+#include FT_TRUETYPE_IDS_H
 
 #include "sfdriver.h"
 #include "ttload.h"
@@ -252,6 +253,18 @@
 
 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
 
+  /* Only ASCII letters and digits are taken for a variation font */
+  /* instance's PostScript name.                                  */
+  /*                                                              */
+  /* `ft_isalnum' is a macro, but we need a function here, thus   */
+  /* this definition.                                             */
+  static int
+  sfnt_is_alphanumeric( int  c )
+  {
+    return ft_isalnum( c );
+  }
+
+
   /* the implementation of MurmurHash3 is taken and adapted from          */
   /* https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp */
 
@@ -451,7 +464,7 @@
                          (n)->encodingID == 0 && \
                          (n)->languageID == 0 )
 
-  static const char*
+  static char*
   get_win_string( FT_Memory       memory,
                   FT_Stream       stream,
                   TT_Name         entry,
@@ -460,10 +473,10 @@
   {
     FT_Error  error = FT_Err_Ok;
 
-    const char*  result;
-    FT_String*   r;
-    FT_Char*     p;
-    FT_UInt      len;
+    char*       result;
+    FT_String*  r;
+    FT_Char*    p;
+    FT_UInt     len;
 
     FT_UNUSED( error );
 
@@ -512,7 +525,7 @@
   }
 
 
-  static const char*
+  static char*
   get_apple_string( FT_Memory       memory,
                     FT_Stream       stream,
                     TT_Name         entry,
@@ -521,10 +534,10 @@
   {
     FT_Error  error = FT_Err_Ok;
 
-    const char*  result;
-    FT_String*   r;
-    FT_Char*     p;
-    FT_UInt      len;
+    char*       result;
+    FT_String*  r;
+    FT_Char*    p;
+    FT_UInt     len;
 
     FT_UNUSED( error );
 
@@ -604,6 +617,27 @@
 #ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
 
   /*
+      The maximum length of an axis value descriptor.
+
+      We need 65536 different values for the decimal fraction; this fits
+      nicely into five decimal places.  Consequently, it consists of
+
+        . the minus sign if the number is negative,
+        . up to five characters for the digits before the decimal point,
+        . the decimal point if there is a fractional part, and
+        . up to five characters for the digits after the decimal point.
+
+      We also need one byte for the leading `_' character and up to four
+      bytes for the axis tag.
+   */
+#define MAX_VALUE_DESCRIPTOR_LEN  ( 1 + 5 + 1 + 5 + 1 + 4 )
+
+
+  /* the maximum length of PostScript font names */
+#define MAX_PS_NAME_LEN  127
+
+
+  /*
    *  Find the shortest decimal representation of a 16.16 fixed point
    *  number.  The function fills `buf' with the result, returning a pointer
    *  to the position after the representation's last byte.
@@ -718,39 +752,302 @@
     return p + 1;
   }
 
-#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
 
+  static const char  hexdigits[16] =
+  {
+    '0', '1', '2', '3', '4', '5', '6', '7',
+    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+  };
 
+
   static const char*
-  sfnt_get_ps_name( TT_Face  face )
+  sfnt_get_var_ps_name( TT_Face  face )
   {
-    FT_Int       found, win, apple;
-    const char*  result = NULL;
+    FT_Error   error;
+    FT_Memory  memory = face->root.memory;
 
+    FT_Service_MultiMasters  mm = (FT_Service_MultiMasters)face->mm;
 
-    if ( face->postscript_name )
-      return face->postscript_name;
+    FT_UInt     num_coords;
+    FT_Fixed*   coords;
+    FT_MM_Var*  mm_var;
 
-    /* scan the name table to see whether we have a Postscript name here, */
-    /* either in Macintosh or Windows platform encodings                  */
-    found = sfnt_get_name_id( face, 6, &win, &apple );
+    FT_Int   found, win, apple;
+    FT_UInt  i, j;
 
-    if ( found )
+    char*  result = NULL;
+    char*  p;
+
+
+    if ( !face->var_postscript_prefix )
     {
+      FT_UInt  len;
+
+
+      /* check whether we have a Variations PostScript Name Prefix */
+      found = sfnt_get_name_id( face,
+                                TT_NAME_ID_VARIATIONS_PREFIX,
+                                &win,
+                                &apple );
+      if ( !found )
+      {
+        /* otherwise use the typographic family name */
+        found = sfnt_get_name_id( face,
+                                  TT_NAME_ID_TYPOGRAPHIC_FAMILY,
+                                  &win,
+                                  &apple );
+      }
+
+      if ( !found )
+      {
+        /* as a last resort we try the family name; note that this is */
+        /* not in the Adobe TechNote, but GX fonts (which predate the */
+        /* TechNote) benefit from this behaviour                      */
+        found = sfnt_get_name_id( face,
+                                  TT_NAME_ID_FONT_FAMILY,
+                                  &win,
+                                  &apple );
+      }
+
+      if ( !found )
+      {
+        FT_TRACE0(( "sfnt_get_var_ps_name:"
+                    " Can't construct PS name prefix for font instances\n" ));
+        return NULL;
+      }
+
       /* prefer Windows entries over Apple */
       if ( win != -1 )
         result = get_win_string( face->root.memory,
                                  face->name_table.stream,
                                  face->name_table.names + win,
-                                 sfnt_is_postscript,
-                                 1 );
+                                 sfnt_is_alphanumeric,
+                                 0 );
       else
         result = get_apple_string( face->root.memory,
                                    face->name_table.stream,
                                    face->name_table.names + apple,
-                                   sfnt_is_postscript,
-                                   1 );
+                                   sfnt_is_alphanumeric,
+                                   0 );
+
+      len = ft_strlen( result );
+
+      /* sanitize if necessary; we reserve space for 36 bytes (a 128bit  */
+      /* checksum as a hex number, preceded by `-' and followed by three */
+      /* ASCII dots, to be used if the constructed PS name would be too  */
+      /* long); this is also sufficient for a single instance            */
+      if ( len > MAX_PS_NAME_LEN - ( 1 + 32 + 3 ) )
+      {
+        len         = MAX_PS_NAME_LEN - ( 1 + 32 + 3 );
+        result[len] = '\0';
+
+        FT_TRACE0(( "sfnt_get_var_ps_name:"
+                    " Shortening variation PS name prefix\n"
+                    "                     "
+                    " to %d characters\n", len ));
+      }
+
+      face->var_postscript_prefix     = result;
+      face->var_postscript_prefix_len = len;
     }
+
+    mm->get_var_blend( FT_FACE( face ),
+                       &num_coords,
+                       &coords,
+                       NULL,
+                       &mm_var );
+
+    if ( FT_IS_NAMED_INSTANCE( FT_FACE( face ) ) )
+    {
+      SFNT_Service  sfnt = (SFNT_Service)face->sfnt;
+
+      FT_Long  instance = ( ( face->root.face_index & 0x7FFF0000L ) >> 16 ) - 1;
+      FT_UInt  psid     = mm_var->namedstyle[instance].psid;
+
+      char*  ps_name = NULL;
+
+
+      /* try first to load the name string with index `postScriptNameID' */
+      if ( psid == 6                      ||
+           ( psid > 255 && psid < 32768 ) )
+        (void)sfnt->get_name( face, (FT_UShort)psid, &ps_name );
+
+      if ( ps_name )
+      {
+        result = ps_name;
+        goto check_length;
+      }
+      else
+      {
+        /* otherwise construct a name using `subfamilyNameID' */
+        FT_UInt  strid = mm_var->namedstyle[instance].strid;
+
+        char*  subfamily_name;
+        char*  s;
+
+
+        (void)sfnt->get_name( face, (FT_UShort)strid, &subfamily_name );
+
+        if ( !subfamily_name )
+        {
+          FT_TRACE1(( "sfnt_get_var_ps_name:"
+                      " can't construct named instance PS name;\n"
+                      "                     "
+                      " trying to construct normal instance PS name\n" ));
+          goto construct_instance_name;
+        }
+
+        /* after the prefix we have character `-' followed by the   */
+        /* subfamily name (using only characters a-z, A-Z, and 0-9) */
+        if ( FT_ALLOC( result, face->var_postscript_prefix_len +
+                               1 + ft_strlen( subfamily_name ) + 1 ) )
+          return NULL;
+
+        ft_strcpy( result, face->var_postscript_prefix );
+
+        p = result + face->var_postscript_prefix_len;
+        *p++ = '-';
+
+        s = subfamily_name;
+        while ( *s )
+        {
+          if ( ft_isalnum( *s ) )
+            *p++ = *s;
+          s++;
+        }
+        *p++ = '\0';
+
+        FT_FREE( subfamily_name );
+      }
+    }
+    else
+    {
+      FT_Var_Axis*  axis;
+
+
+    construct_instance_name:
+      axis = mm_var->axis;
+
+      if ( FT_ALLOC( result,
+                     face->var_postscript_prefix_len +
+                       num_coords * MAX_VALUE_DESCRIPTOR_LEN + 1 ) )
+        return NULL;
+
+      p = result;
+
+      ft_strcpy( p, face->var_postscript_prefix );
+      p += face->var_postscript_prefix_len;
+
+      for ( i = 0; i < num_coords; i++, coords++, axis++ )
+      {
+        FT_ULong  t;
+
+
+        /* omit axis value descriptor if it is identical */
+        /* to the default axis value                     */
+        if ( *coords == axis->def )
+          continue;
+
+        *p++ = '_';
+        p    = fixed2float( *coords, p );
+
+        t = axis->tag >> 24 & 0xFF;
+        if ( t != ' ' && ft_isalnum( t ) )
+          *p++ = t;
+        t = axis->tag >> 16 & 0xFF;
+        if ( t != ' ' && ft_isalnum( t ) )
+          *p++ = t;
+        t = axis->tag >> 8 & 0xFF;
+        if ( t != ' ' && ft_isalnum( t ) )
+          *p++ = t;
+        t = axis->tag & 0xFF;
+        if ( t != ' ' && ft_isalnum( t ) )
+          *p++ = t;
+      }
+    }
+
+  check_length:
+    if ( p - result > MAX_PS_NAME_LEN )
+    {
+      /* the PS name is too long; replace the part after the prefix with */
+      /* a checksum; we use MurmurHash 3 with a hash length of 128 bit   */
+
+      FT_UInt32  seed = 123456789;
+
+      FT_UInt32   hash[4];
+      FT_UInt32*  h;
+
+
+      murmur_hash_3_128( result, p - result, seed, hash );
+
+      p = result + face->var_postscript_prefix_len;
+      *p++ = '-';
+
+      /* we convert the hash value to hex digits from back to front */
+      p += 32 + 3;
+      h  = hash + 3;
+
+      *p-- = '\0';
+      *p-- = '.';
+      *p-- = '.';
+      *p-- = '.';
+
+      for ( i = 0; i < 4; i++, h-- )
+      {
+        FT_UInt32  v = *h;
+
+
+        for ( j = 0; j < 8; j++ )
+        {
+          *p--   = hexdigits[v & 0xF];
+          v    >>= 4;
+        }
+      }
+    }
+
+    return result;
+  }
+
+#endif /* TT_CONFIG_OPTION_GX_VAR_SUPPORT */
+
+
+  static const char*
+  sfnt_get_ps_name( TT_Face  face )
+  {
+    FT_Int       found, win, apple;
+    const char*  result = NULL;
+
+
+    if ( face->postscript_name )
+      return face->postscript_name;
+
+#ifdef TT_CONFIG_OPTION_GX_VAR_SUPPORT
+    if ( face->blend )
+    {
+      face->postscript_name = sfnt_get_var_ps_name( face );
+      return face->postscript_name;
+    }
+#endif
+
+    /* scan the name table to see whether we have a Postscript name here, */
+    /* either in Macintosh or Windows platform encodings                  */
+    found = sfnt_get_name_id( face, TT_NAME_ID_PS_NAME, &win, &apple );
+    if ( !found )
+      return NULL;
+
+    /* prefer Windows entries over Apple */
+    if ( win != -1 )
+      result = get_win_string( face->root.memory,
+                               face->name_table.stream,
+                               face->name_table.names + win,
+                               sfnt_is_postscript,
+                               1 );
+    else
+      result = get_apple_string( face->root.memory,
+                                 face->name_table.stream,
+                                 face->name_table.names + apple,
+                                 sfnt_is_postscript,
+                                 1 );
 
     face->postscript_name = result;
 
--- a/src/sfnt/sfobjs.c
+++ b/src/sfnt/sfobjs.c
@@ -1755,6 +1755,7 @@
     face->root.num_fixed_sizes = 0;
 
     FT_FREE( face->postscript_name );
+    FT_FREE( face->var_postscript_prefix );
 
     face->sfnt = NULL;
   }
--- a/src/truetype/ttgxvar.c
+++ b/src/truetype/ttgxvar.c
@@ -2445,6 +2445,10 @@
 
     face->is_default_instance = is_default_instance;
 
+    /* enforce recomputation of the PostScript name; */
+    FT_FREE( face->postscript_name );
+    face->postscript_name = NULL;
+
   Exit:
     return error;
   }