From 34010f7c473330907b39bd7d55e985f407d7f87c Mon Sep 17 00:00:00 2001 From: Werner Lemberg Date: Tue, 14 Mar 2017 21:50:22 +0100 Subject: [PATCH] [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. --- ChangeLog | 29 ++- include/freetype/freetype.h | 9 +- include/freetype/internal/tttypes.h | 13 ++ src/sfnt/sfdriver.c | 345 ++++++++++++++++++++++++++-- src/sfnt/sfobjs.c | 1 + src/truetype/ttgxvar.c | 4 + 6 files changed, 374 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index cdbec0db1..b68e263d1 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,28 @@ +2017-03-14 Werner Lemberg + + [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 [sfnt] Implement PS names for font instances [2/3]. @@ -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 diff --git a/include/freetype/freetype.h b/include/freetype/freetype.h index bf119e3e8..7b65e2174 100644 --- a/include/freetype/freetype.h +++ b/include/freetype/freetype.h @@ -891,7 +891,7 @@ FT_BEGIN_HEADER /* 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 */ @@ -3403,6 +3403,13 @@ FT_BEGIN_HEADER /* 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 ); diff --git a/include/freetype/internal/tttypes.h b/include/freetype/internal/tttypes.h index abe4dcd64..a1f6774ab 100644 --- a/include/freetype/internal/tttypes.h +++ b/include/freetype/internal/tttypes.h @@ -1309,6 +1309,15 @@ FT_BEGIN_HEADER /* 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_BEGIN_HEADER 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 */ diff --git a/src/sfnt/sfdriver.c b/src/sfnt/sfdriver.c index f0ae98168..0f95c76b2 100644 --- 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 ); @@ -603,6 +616,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 @@ -718,6 +752,262 @@ return p + 1; } + + 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_var_ps_name( TT_Face face ) + { + FT_Error error; + FT_Memory memory = face->root.memory; + + FT_Service_MultiMasters mm = (FT_Service_MultiMasters)face->mm; + + FT_UInt num_coords; + FT_Fixed* coords; + FT_MM_Var* mm_var; + + FT_Int found, win, apple; + FT_UInt i, j; + + 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_alphanumeric, + 0 ); + else + result = get_apple_string( face->root.memory, + face->name_table.stream, + face->name_table.names + apple, + 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 */ @@ -731,26 +1021,33 @@ 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, 6, &win, &apple ); + found = sfnt_get_name_id( face, TT_NAME_ID_PS_NAME, &win, &apple ); + if ( !found ) + return NULL; - if ( found ) - { - /* prefer Windows entries over Apple */ - if ( win != -1 ) - result = get_win_string( face->root.memory, + /* 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 + win, + face->name_table.names + apple, 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; diff --git a/src/sfnt/sfobjs.c b/src/sfnt/sfobjs.c index 54912c500..36400749a 100644 --- 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; } diff --git a/src/truetype/ttgxvar.c b/src/truetype/ttgxvar.c index 32779058e..7f0b3f34d 100644 --- 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; }