/***************************************************************************/ /* */ /* t42parse.c */ /* */ /* Type 42 font parser (body). */ /* */ /* Copyright 2002 by Roberto Alameda. */ /* */ /* This file is part of the FreeType project, and may only be used, */ /* modified, and distributed under the terms of the FreeType project */ /* license, LICENSE.TXT. By continuing to use, modify, or distribute */ /* this file you indicate that you have read the license and */ /* understand and accept it fully. */ /* */ /***************************************************************************/ #include "t42parse.h" #include "t42error.h" #include FT_INTERNAL_DEBUG_H #include FT_INTERNAL_STREAM_H #include FT_LIST_H #include FT_INTERNAL_POSTSCRIPT_AUX_H /*************************************************************************/ /* */ /* The macro FT_COMPONENT is used in trace mode. It is an implicit */ /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log */ /* messages during execution. */ /* */ #undef FT_COMPONENT #define FT_COMPONENT trace_t42 static void t42_parse_font_name( T42_Face face, T42_Loader loader ); static void t42_parse_font_bbox( T42_Face face, T42_Loader loader ); static void t42_parse_font_matrix( T42_Face face, T42_Loader loader ); static void t42_parse_encoding( T42_Face face, T42_Loader loader ); static void t42_parse_charstrings( T42_Face face, T42_Loader loader ); static void t42_parse_sfnts( T42_Face face, T42_Loader loader ); static const T1_FieldRec t42_keywords[] = { #undef FT_STRUCTURE #define FT_STRUCTURE T1_FontInfo #undef T1CODE #define T1CODE T1_FIELD_LOCATION_FONT_INFO T1_FIELD_STRING ( "version", version ) T1_FIELD_STRING ( "Notice", notice ) T1_FIELD_STRING ( "FullName", full_name ) T1_FIELD_STRING ( "FamilyName", family_name ) T1_FIELD_STRING ( "Weight", weight ) T1_FIELD_NUM ( "ItalicAngle", italic_angle ) T1_FIELD_TYPE_BOOL( "isFixedPitch", is_fixed_pitch ) T1_FIELD_NUM ( "UnderlinePosition", underline_position ) T1_FIELD_NUM ( "UnderlineThickness", underline_thickness ) #undef FT_STRUCTURE #define FT_STRUCTURE T1_FontRec #undef T1CODE #define T1CODE T1_FIELD_LOCATION_FONT_DICT T1_FIELD_NUM( "PaintType", paint_type ) T1_FIELD_NUM( "FontType", font_type ) T1_FIELD_NUM( "StrokeWidth", stroke_width ) T1_FIELD_CALLBACK( "FontName", t42_parse_font_name ) T1_FIELD_CALLBACK( "FontBBox", t42_parse_font_bbox ) T1_FIELD_CALLBACK( "FontMatrix", t42_parse_font_matrix ) T1_FIELD_CALLBACK( "Encoding", t42_parse_encoding ) T1_FIELD_CALLBACK( "CharStrings", t42_parse_charstrings ) T1_FIELD_CALLBACK( "sfnts", t42_parse_sfnts ) { 0, T1_FIELD_LOCATION_CID_INFO, T1_FIELD_TYPE_NONE, 0, 0, 0, 0, 0 } }; #define T1_Add_Table( p, i, o, l ) (p)->funcs.add( (p), i, o, l ) #define T1_Done_Table( p ) \ do \ { \ if ( (p)->funcs.done ) \ (p)->funcs.done( p ); \ } while ( 0 ) #define T1_Release_Table( p ) \ do \ { \ if ( (p)->funcs.release ) \ (p)->funcs.release( p ); \ } while ( 0 ) #define T1_Skip_Spaces( p ) (p)->root.funcs.skip_spaces( &(p)->root ) #define T1_Skip_Alpha( p ) (p)->root.funcs.skip_alpha ( &(p)->root ) #define T1_ToInt( p ) (p)->root.funcs.to_int( &(p)->root ) #define T1_ToFixed( p, t ) (p)->root.funcs.to_fixed( &(p)->root, t ) #define T1_ToCoordArray( p, m, c ) \ (p)->root.funcs.to_coord_array( &(p)->root, m, c ) #define T1_ToFixedArray( p, m, f, t ) \ (p)->root.funcs.to_fixed_array( &(p)->root, m, f, t ) #define T1_ToToken( p, t ) \ (p)->root.funcs.to_token( &(p)->root, t ) #define T1_ToTokenArray( p, t, m, c ) \ (p)->root.funcs.to_token_array( &(p)->root, t, m, c ) #define T1_Load_Field( p, f, o, m, pf ) \ (p)->root.funcs.load_field( &(p)->root, f, o, m, pf ) #define T1_Load_Field_Table( p, f, o, m, pf ) \ (p)->root.funcs.load_field_table( &(p)->root, f, o, m, pf ) /********************* Parsing Functions ******************/ FT_LOCAL_DEF( FT_Error ) t42_parser_init( T42_Parser parser, FT_Stream stream, FT_Memory memory, PSAux_Service psaux ) { FT_Error error = T42_Err_Ok; FT_Long size; psaux->ps_parser_funcs->init( &parser->root, 0, 0, memory ); parser->stream = stream; parser->base_len = 0; parser->base_dict = 0; parser->in_memory = 0; /*******************************************************************/ /* */ /* Here a short summary of what is going on: */ /* */ /* When creating a new Type 42 parser, we try to locate and load */ /* the base dictionary, loading the whole font into memory. */ /* */ /* When `loading' the base dictionary, we only setup pointers in */ /* the case of a memory-based stream. Otherwise, we allocate */ /* and load the base dictionary in it. */ /* */ /* parser->in_memory is set if we have a memory stream. */ /* */ if ( FT_STREAM_SEEK( 0L ) ) goto Exit; size = stream->size; /* now, try to load `size' bytes of the `base' dictionary we */ /* found previously */ /* if it is a memory-based resource, set up pointers */ if ( !stream->read ) { parser->base_dict = (FT_Byte*)stream->base + stream->pos; parser->base_len = size; parser->in_memory = 1; /* check that the `size' field is valid */ if ( FT_STREAM_SKIP( size ) ) goto Exit; } else { /* read segment in memory */ if ( FT_ALLOC( parser->base_dict, size ) || FT_STREAM_READ( parser->base_dict, size ) ) goto Exit; parser->base_len = size; } /* Now check font format; we must see `%!PS-TrueTypeFont' */ if (size <= 17 || ( ft_strncmp( (const char*)parser->base_dict, "%!PS-TrueTypeFont", 17) ) ) error = T42_Err_Unknown_File_Format; else { parser->root.base = parser->base_dict; parser->root.cursor = parser->base_dict; parser->root.limit = parser->root.cursor + parser->base_len; } Exit: if ( error && !parser->in_memory ) FT_FREE( parser->base_dict ); return error; } FT_LOCAL_DEF( void ) t42_parser_done( T42_Parser parser ) { FT_Memory memory = parser->root.memory; /* free the base dictionary only when we have a disk stream */ if ( !parser->in_memory ) FT_FREE( parser->base_dict ); parser->root.funcs.done( &parser->root ); } static int t42_is_alpha( FT_Byte c ) { /* Note: we must accept "+" as a valid character, as it is used in */ /* embedded type1 fonts in PDF documents. */ /* */ return ( ft_isalnum( c ) || c == '.' || c == '_' || c == '-' || c == '+' ); } static int t42_is_space( FT_Byte c ) { return ( c == ' ' || c == '\t' || c == '\r' || c == '\n' ); } static void t42_parse_font_name( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; FT_Error error; FT_Memory memory = parser->root.memory; FT_Int len; FT_Byte* cur; FT_Byte* cur2; FT_Byte* limit; T1_Skip_Spaces( parser ); cur = parser->root.cursor; limit = parser->root.limit; if ( cur >= limit - 1 || ( *cur != '/' && *cur != '(') ) return; cur++; cur2 = cur; while ( cur2 < limit && t42_is_alpha( *cur2 ) ) cur2++; len = (FT_Int)( cur2 - cur ); if ( len > 0 ) { if ( FT_ALLOC( face->type1.font_name, len + 1 ) ) { parser->root.error = error; return; } FT_MEM_COPY( face->type1.font_name, cur, len ); face->type1.font_name[len] = '\0'; } parser->root.cursor = cur2; } static void t42_parse_font_bbox( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; FT_BBox* bbox = &face->type1.font_bbox; bbox->xMin = T1_ToInt( parser ); bbox->yMin = T1_ToInt( parser ); bbox->xMax = T1_ToInt( parser ); bbox->yMax = T1_ToInt( parser ); } static void t42_parse_font_matrix( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; FT_Matrix* matrix = &face->type1.font_matrix; FT_Vector* offset = &face->type1.font_offset; FT_Face root = (FT_Face)&face->root; FT_Fixed temp[6]; FT_Fixed temp_scale; (void)T1_ToFixedArray( parser, 6, temp, 3 ); temp_scale = ABS( temp[3] ); /* Set Units per EM based on FontMatrix values. We set the value to */ /* 1000 / temp_scale, because temp_scale was already multiplied by */ /* 1000 (in t1_tofixed, from psobjs.c). */ root->units_per_EM = (FT_UShort)( FT_DivFix( 1000 * 0x10000L, temp_scale ) >> 16 ); /* we need to scale the values by 1.0/temp_scale */ if ( temp_scale != 0x10000L ) { temp[0] = FT_DivFix( temp[0], temp_scale ); temp[1] = FT_DivFix( temp[1], temp_scale ); temp[2] = FT_DivFix( temp[2], temp_scale ); temp[4] = FT_DivFix( temp[4], temp_scale ); temp[5] = FT_DivFix( temp[5], temp_scale ); temp[3] = 0x10000L; } matrix->xx = temp[0]; matrix->yx = temp[1]; matrix->xy = temp[2]; matrix->yy = temp[3]; /* note that the offsets must be expressed in integer font units */ offset->x = temp[4] >> 16; offset->y = temp[5] >> 16; } static void t42_parse_encoding( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; FT_Byte* cur = parser->root.cursor; FT_Byte* limit = parser->root.limit; PSAux_Service psaux = (PSAux_Service)face->psaux; /* skip whitespace */ while ( t42_is_space( *cur ) ) { cur++; if ( cur >= limit ) { FT_ERROR(( "t42_parse_encoding: out of bounds!\n" )); parser->root.error = T42_Err_Invalid_File_Format; return; } } /* if we have a number, then the encoding is an array, */ /* and we must load it now */ if ( (FT_Byte)( *cur - '0' ) < 10 ) { T1_Encoding encode = &face->type1.encoding; FT_Int count, n; PS_Table char_table = &loader->encoding_table; FT_Memory memory = parser->root.memory; FT_Error error; /* read the number of entries in the encoding, should be 256 */ count = T1_ToInt( parser ); if ( parser->root.error ) return; /* we use a T1_Table to store our charnames */ loader->num_chars = encode->num_chars = count; if ( FT_NEW_ARRAY( encode->char_index, count ) || FT_NEW_ARRAY( encode->char_name, count ) || FT_SET_ERROR( psaux->ps_table_funcs->init( char_table, count, memory ) ) ) { parser->root.error = error; return; } /* We need to `zero' out encoding_table.elements */ for ( n = 0; n < count; n++ ) { char* notdef = (char *)".notdef"; T1_Add_Table( char_table, n, notdef, 8 ); } /* Now, we will need to read a record of the form */ /* ... charcode /charname ... for each entry in our table */ /* */ /* We simply look for a number followed by an immediate */ /* name. Note that this ignores correctly the sequence */ /* that is often seen in type1 fonts: */ /* */ /* 0 1 255 { 1 index exch /.notdef put } for dup */ /* */ /* used to clean the encoding array before anything else. */ /* */ /* We stop when we encounter a `def'. */ cur = parser->root.cursor; limit = parser->root.limit; n = 0; for ( ; cur < limit; ) { FT_Byte c; c = *cur; /* we stop when we encounter a `def' */ if ( c == 'd' && cur + 3 < limit ) { if ( cur[1] == 'e' && cur[2] == 'f' && t42_is_space( cur[-1] ) && t42_is_space( cur[3] ) ) { FT_TRACE6(( "encoding end\n" )); break; } } /* otherwise, we must find a number before anything else */ if ( (FT_Byte)( c - '0' ) < 10 ) { FT_Int charcode; parser->root.cursor = cur; charcode = T1_ToInt( parser ); cur = parser->root.cursor; /* skip whitespace */ while ( cur < limit && t42_is_space( *cur ) ) cur++; if ( cur < limit && *cur == '/' ) { /* bingo, we have an immediate name -- it must be a */ /* character name */ FT_Byte* cur2 = cur + 1; FT_Int len; while ( cur2 < limit && t42_is_alpha( *cur2 ) ) cur2++; len = (FT_Int)( cur2 - cur - 1 ); parser->root.error = T1_Add_Table( char_table, charcode, cur + 1, len + 1 ); char_table->elements[charcode][len] = '\0'; if ( parser->root.error ) return; cur = cur2; } } else cur++; } face->type1.encoding_type = T1_ENCODING_TYPE_ARRAY; parser->root.cursor = cur; } /* Otherwise, we should have either `StandardEncoding', */ /* `ExpertEncoding', or `ISOLatin1Encoding' */ else { if ( cur + 17 < limit && ft_strncmp( (const char*)cur, "StandardEncoding", 16 ) == 0 ) face->type1.encoding_type = T1_ENCODING_TYPE_STANDARD; else if ( cur + 15 < limit && ft_strncmp( (const char*)cur, "ExpertEncoding", 14 ) == 0 ) face->type1.encoding_type = T1_ENCODING_TYPE_EXPERT; else if ( cur + 18 < limit && ft_strncmp( (const char*)cur, "ISOLatin1Encoding", 17 ) == 0 ) face->type1.encoding_type = T1_ENCODING_TYPE_ISOLATIN1; else { FT_ERROR(( "t42_parse_encoding: invalid token!\n" )); parser->root.error = T42_Err_Invalid_File_Format; } } } static FT_UInt t42_hexval( FT_Byte v ) { FT_UInt d; d = (FT_UInt)( v - 'A' ); if ( d < 6 ) { d += 10; goto Exit; } d = (FT_UInt)( v - 'a' ); if ( d < 6 ) { d += 10; goto Exit; } d = (FT_UInt)( v - '0' ); if ( d < 10 ) goto Exit; d = 0; Exit: return d; } static void t42_parse_sfnts( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; FT_Memory memory = parser->root.memory; FT_Byte* cur = parser->root.cursor; FT_Byte* limit = parser->root.limit; FT_Error error; FT_Int num_tables = 0, status; FT_ULong count, ttf_size = 0, string_size = 0; FT_Bool in_string = 0; FT_Byte v = 0; /* The format is `/sfnts [ <...> <...> ... ] def' */ while ( t42_is_space( *cur ) ) cur++; if (*cur++ == '[') { status = 0; count = 0; } else { FT_ERROR(( "t42_parse_sfnts: can't find begin of sfnts vector!\n" )); error = T42_Err_Invalid_File_Format; goto Fail; } while ( cur < limit - 2 ) { while ( t42_is_space( *cur ) ) cur++; switch ( *cur ) { case ']': parser->root.cursor = cur++; return; case '<': in_string = 1; string_size = 0; cur++; continue; case '>': if ( !in_string ) { FT_ERROR(( "t42_parse_sfnts: found unpaired `>'!\n" )); error = T42_Err_Invalid_File_Format; goto Fail; } /* A string can have, as a last byte, */ /* a zero byte for padding. If so, ignore it */ if ( ( v == 0 ) && ( string_size % 2 == 1 ) ) count--; in_string = 0; cur++; continue; case '%': if ( !in_string ) { /* Comment found; skip till end of line */ while ( *cur != '\n' ) cur++; continue; } else { FT_ERROR(( "t42_parse_sfnts: found `%' in string!\n" )); error = T42_Err_Invalid_File_Format; goto Fail; } default: if ( !ft_xdigit( *cur ) || !ft_xdigit( *(cur + 1) ) ) { FT_ERROR(( "t42_parse_sfnts: found non-hex characters in string" )); error = T42_Err_Invalid_File_Format; goto Fail; } v = (FT_Byte)( 16 * t42_hexval( *cur++ ) + t42_hexval( *cur++ ) ); string_size++; } switch ( status ) { case 0: /* The '[' was read, so load offset table, 12 bytes */ if ( count < 12 ) { face->ttf_data[count++] = v; continue; } else { num_tables = 16 * face->ttf_data[4] + face->ttf_data[5]; status = 1; ttf_size = 12 + 16 * num_tables; if ( FT_REALLOC( face->ttf_data, 12, ttf_size ) ) goto Fail; } /* No break, fall-through */ case 1: /* The offset table is read; read now the table directory */ if ( count < ttf_size ) { face->ttf_data[count++] = v; continue; } else { int i; FT_ULong len; for ( i = 0; i < num_tables; i++ ) { FT_Byte* p = face->ttf_data + 12 + 16*i + 12; len = FT_PEEK_ULONG( p ); /* Pad to a 4-byte boundary length */ ttf_size += ( len + 3 ) & ~3; } status = 2; face->ttf_size = ttf_size; if ( FT_REALLOC( face->ttf_data, 12 + 16 * num_tables, ttf_size + 1 ) ) goto Fail; } /* No break, fall-through */ case 2: /* We are reading normal tables; just swallow them */ face->ttf_data[count++] = v; } } /* If control reaches this point, the format was not valid */ error = T42_Err_Invalid_File_Format; Fail: parser->root.error = error; } static void t42_parse_charstrings( T42_Face face, T42_Loader loader ) { T42_Parser parser = &loader->parser; PS_Table code_table = &loader->charstrings; PS_Table name_table = &loader->glyph_names; FT_Memory memory = parser->root.memory; FT_Error error; PSAux_Service psaux = (PSAux_Service)face->psaux; FT_Byte* cur; FT_Byte* limit = parser->root.limit; FT_Int n; loader->num_glyphs = T1_ToInt( parser ); if ( parser->root.error ) return; /* initialize tables */ error = psaux->ps_table_funcs->init( code_table, loader->num_glyphs, memory ); if ( error ) goto Fail; error = psaux->ps_table_funcs->init( name_table, loader->num_glyphs, memory ); if ( error ) goto Fail; n = 0; for (;;) { /* the format is simple: */ /* `/glyphname' + index + def */ /* */ /* note that we stop when we find an `end' */ /* */ T1_Skip_Spaces( parser ); cur = parser->root.cursor; if ( cur >= limit ) break; /* we stop when we find an `end' keyword */ if ( *cur == 'e' && cur + 3 < limit && cur[1] == 'n' && cur[2] == 'd' ) break; if ( *cur != '/' ) T1_Skip_Alpha( parser ); else { FT_Byte* cur2 = cur + 1; FT_Int len; while ( cur2 < limit && t42_is_alpha( *cur2 ) ) cur2++; len = (FT_Int)( cur2 - cur - 1 ); error = T1_Add_Table( name_table, n, cur + 1, len + 1 ); if ( error ) goto Fail; /* add a trailing zero to the name table */ name_table->elements[n][len] = '\0'; parser->root.cursor = cur2; T1_Skip_Spaces( parser ); cur2 = cur = parser->root.cursor; if ( cur >= limit ) break; while ( cur2 < limit && t42_is_alpha( *cur2 ) ) cur2++; len = (FT_Int)( cur2 - cur ); error = T1_Add_Table( code_table, n, cur, len + 1 ); if ( error ) goto Fail; code_table->elements[n][len] = '\0'; n++; if ( n >= loader->num_glyphs ) break; } } /* Index 0 must be a .notdef element */ if ( ft_strcmp( (char *)name_table->elements[0], ".notdef" ) ) { FT_ERROR(( "t42_parse_charstrings: Index 0 is not `.notdef'!\n" )); error = T42_Err_Invalid_File_Format; goto Fail; } loader->num_glyphs = n; return; Fail: parser->root.error = error; } static FT_Error t42_load_keyword( T42_Face face, T42_Loader loader, T1_Field field ) { FT_Error error; void* dummy_object; void** objects; FT_UInt max_objects = 0; /* if the keyword has a dedicated callback, call it */ if ( field->type == T1_FIELD_TYPE_CALLBACK ) { field->reader( (FT_Face)face, loader ); error = loader->parser.root.error; goto Exit; } /* now, the keyword is either a simple field, or a table of fields; */ /* we are now going to take care of it */ switch ( field->location ) { case T1_FIELD_LOCATION_FONT_INFO: dummy_object = &face->type1.font_info; objects = &dummy_object; break; default: dummy_object = &face->type1; objects = &dummy_object; } if ( field->type == T1_FIELD_TYPE_INTEGER_ARRAY || field->type == T1_FIELD_TYPE_FIXED_ARRAY ) error = T1_Load_Field_Table( &loader->parser, field, objects, max_objects, 0 ); else error = T1_Load_Field( &loader->parser, field, objects, max_objects, 0 ); Exit: return error; } FT_LOCAL_DEF( FT_Error ) t42_parse_dict( T42_Face face, T42_Loader loader, FT_Byte* base, FT_Long size ) { T42_Parser parser = &loader->parser; FT_Byte* cur = base; FT_Byte* limit = cur + size; FT_UInt n_keywords = sizeof ( t42_keywords ) / sizeof ( t42_keywords[0] ); parser->root.cursor = base; parser->root.limit = base + size; parser->root.error = 0; for ( ; cur < limit; cur++ ) { /* look for `FontDirectory', which causes problems on some fonts */ if ( *cur == 'F' && cur + 25 < limit && ft_strncmp( (char*)cur, "FontDirectory", 13 ) == 0 ) { FT_Byte* cur2; /* skip the `FontDirectory' keyword */ cur += 13; cur2 = cur; /* lookup the `known' keyword */ while ( cur < limit && *cur != 'k' && ft_strncmp( (char*)cur, "known", 5 ) ) cur++; if ( cur < limit ) { T1_TokenRec token; /* skip the `known' keyword and the token following it */ cur += 5; loader->parser.root.cursor = cur; T1_ToToken( &loader->parser, &token ); /* if the last token was an array, skip it! */ if ( token.type == T1_TOKEN_TYPE_ARRAY ) cur2 = parser->root.cursor; } cur = cur2; } /* look for immediates */ else if ( *cur == '/' && cur + 2 < limit ) { FT_Byte* cur2; FT_UInt i, len; cur++; cur2 = cur; while ( cur2 < limit && t42_is_alpha( *cur2 ) ) cur2++; len = (FT_UInt)( cur2 - cur ); if ( len > 0 && len < 22 ) /* XXX What shall it this 22? */ { /* now, compare the immediate name to the keyword table */ /* Loop through all known keywords */ for ( i = 0; i < n_keywords; i++ ) { T1_Field keyword = (T1_Field)&t42_keywords[i]; FT_Byte *name = (FT_Byte*)keyword->ident; if ( !name ) continue; if ( ( len == ft_strlen( (const char *)name ) ) && ( ft_memcmp( cur, name, len ) == 0 ) ) { /* we found it -- run the parsing callback! */ parser->root.cursor = cur2; T1_Skip_Spaces( parser ); parser->root.error = t42_load_keyword(face, loader, keyword ); if ( parser->root.error ) return parser->root.error; cur = parser->root.cursor; break; } } } } } return parser->root.error; } FT_LOCAL_DEF( void ) t42_loader_init( T42_Loader loader, T42_Face face ) { FT_UNUSED( face ); FT_MEM_SET( loader, 0, sizeof ( *loader ) ); loader->num_glyphs = 0; loader->num_chars = 0; /* initialize the tables -- simply set their `init' field to 0 */ loader->encoding_table.init = 0; loader->charstrings.init = 0; loader->glyph_names.init = 0; } FT_LOCAL_DEF( void ) t42_loader_done( T42_Loader loader ) { T42_Parser parser = &loader->parser; /* finalize tables */ T1_Release_Table( &loader->encoding_table ); T1_Release_Table( &loader->charstrings ); T1_Release_Table( &loader->glyph_names ); /* finalize parser */ t42_parser_done( parser ); } /* END */