| /***************************************************************************/ |
| /* */ |
| /* psobjs.c */ |
| /* */ |
| /* Auxiliary functions for PostScript fonts (body). */ |
| /* */ |
| /* Copyright 1996-2015 by */ |
| /* David Turner, Robert Wilhelm, and Werner Lemberg. */ |
| /* */ |
| /* 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 <ft2build.h> |
| #include FT_INTERNAL_POSTSCRIPT_AUX_H |
| #include FT_INTERNAL_DEBUG_H |
| #include FT_INTERNAL_CALC_H |
| |
| #include "psobjs.h" |
| #include "psconv.h" |
| |
| #include "psauxerr.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_psobjs |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** PS_TABLE *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| /*************************************************************************/ |
| /* */ |
| /* <Function> */ |
| /* ps_table_new */ |
| /* */ |
| /* <Description> */ |
| /* Initializes a PS_Table. */ |
| /* */ |
| /* <InOut> */ |
| /* table :: The address of the target table. */ |
| /* */ |
| /* <Input> */ |
| /* count :: The table size = the maximum number of elements. */ |
| /* */ |
| /* memory :: The memory object to use for all subsequent */ |
| /* reallocations. */ |
| /* */ |
| /* <Return> */ |
| /* FreeType error code. 0 means success. */ |
| /* */ |
| FT_LOCAL_DEF( FT_Error ) |
| ps_table_new( PS_Table table, |
| FT_Int count, |
| FT_Memory memory ) |
| { |
| FT_Error error; |
| |
| |
| table->memory = memory; |
| if ( FT_NEW_ARRAY( table->elements, count ) || |
| FT_NEW_ARRAY( table->lengths, count ) ) |
| goto Exit; |
| |
| table->max_elems = count; |
| table->init = 0xDEADBEEFUL; |
| table->num_elems = 0; |
| table->block = NULL; |
| table->capacity = 0; |
| table->cursor = 0; |
| |
| *(PS_Table_FuncsRec*)&table->funcs = ps_table_funcs; |
| |
| Exit: |
| if ( error ) |
| FT_FREE( table->elements ); |
| |
| return error; |
| } |
| |
| |
| static void |
| shift_elements( PS_Table table, |
| FT_Byte* old_base ) |
| { |
| FT_PtrDist delta = table->block - old_base; |
| FT_Byte** offset = table->elements; |
| FT_Byte** limit = offset + table->max_elems; |
| |
| |
| for ( ; offset < limit; offset++ ) |
| { |
| if ( offset[0] ) |
| offset[0] += delta; |
| } |
| } |
| |
| |
| static FT_Error |
| reallocate_t1_table( PS_Table table, |
| FT_Offset new_size ) |
| { |
| FT_Memory memory = table->memory; |
| FT_Byte* old_base = table->block; |
| FT_Error error; |
| |
| |
| /* allocate new base block */ |
| if ( FT_ALLOC( table->block, new_size ) ) |
| { |
| table->block = old_base; |
| return error; |
| } |
| |
| /* copy elements and shift offsets */ |
| if ( old_base ) |
| { |
| FT_MEM_COPY( table->block, old_base, table->capacity ); |
| shift_elements( table, old_base ); |
| FT_FREE( old_base ); |
| } |
| |
| table->capacity = new_size; |
| |
| return FT_Err_Ok; |
| } |
| |
| |
| /*************************************************************************/ |
| /* */ |
| /* <Function> */ |
| /* ps_table_add */ |
| /* */ |
| /* <Description> */ |
| /* Adds an object to a PS_Table, possibly growing its memory block. */ |
| /* */ |
| /* <InOut> */ |
| /* table :: The target table. */ |
| /* */ |
| /* <Input> */ |
| /* idx :: The index of the object in the table. */ |
| /* */ |
| /* object :: The address of the object to copy in memory. */ |
| /* */ |
| /* length :: The length in bytes of the source object. */ |
| /* */ |
| /* <Return> */ |
| /* FreeType error code. 0 means success. An error is returned if a */ |
| /* reallocation fails. */ |
| /* */ |
| FT_LOCAL_DEF( FT_Error ) |
| ps_table_add( PS_Table table, |
| FT_Int idx, |
| void* object, |
| FT_UInt length ) |
| { |
| if ( idx < 0 || idx >= table->max_elems ) |
| { |
| FT_ERROR(( "ps_table_add: invalid index\n" )); |
| return FT_THROW( Invalid_Argument ); |
| } |
| |
| /* grow the base block if needed */ |
| if ( table->cursor + length > table->capacity ) |
| { |
| FT_Error error; |
| FT_Offset new_size = table->capacity; |
| FT_PtrDist in_offset; |
| |
| |
| in_offset = (FT_Byte*)object - table->block; |
| if ( in_offset < 0 || (FT_Offset)in_offset >= table->capacity ) |
| in_offset = -1; |
| |
| while ( new_size < table->cursor + length ) |
| { |
| /* increase size by 25% and round up to the nearest multiple |
| of 1024 */ |
| new_size += ( new_size >> 2 ) + 1; |
| new_size = FT_PAD_CEIL( new_size, 1024 ); |
| } |
| |
| error = reallocate_t1_table( table, new_size ); |
| if ( error ) |
| return error; |
| |
| if ( in_offset >= 0 ) |
| object = table->block + in_offset; |
| } |
| |
| /* add the object to the base block and adjust offset */ |
| table->elements[idx] = table->block + table->cursor; |
| table->lengths [idx] = length; |
| FT_MEM_COPY( table->block + table->cursor, object, length ); |
| |
| table->cursor += length; |
| return FT_Err_Ok; |
| } |
| |
| |
| /*************************************************************************/ |
| /* */ |
| /* <Function> */ |
| /* ps_table_done */ |
| /* */ |
| /* <Description> */ |
| /* Finalizes a PS_TableRec (i.e., reallocate it to its current */ |
| /* cursor). */ |
| /* */ |
| /* <InOut> */ |
| /* table :: The target table. */ |
| /* */ |
| /* <Note> */ |
| /* This function does NOT release the heap's memory block. It is up */ |
| /* to the caller to clean it, or reference it in its own structures. */ |
| /* */ |
| FT_LOCAL_DEF( void ) |
| ps_table_done( PS_Table table ) |
| { |
| FT_Memory memory = table->memory; |
| FT_Error error; |
| FT_Byte* old_base = table->block; |
| |
| |
| /* should never fail, because rec.cursor <= rec.size */ |
| if ( !old_base ) |
| return; |
| |
| if ( FT_ALLOC( table->block, table->cursor ) ) |
| return; |
| FT_MEM_COPY( table->block, old_base, table->cursor ); |
| shift_elements( table, old_base ); |
| |
| table->capacity = table->cursor; |
| FT_FREE( old_base ); |
| |
| FT_UNUSED( error ); |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| ps_table_release( PS_Table table ) |
| { |
| FT_Memory memory = table->memory; |
| |
| |
| if ( (FT_ULong)table->init == 0xDEADBEEFUL ) |
| { |
| FT_FREE( table->block ); |
| FT_FREE( table->elements ); |
| FT_FREE( table->lengths ); |
| table->init = 0; |
| } |
| } |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** T1 PARSER *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| |
| /* first character must be already part of the comment */ |
| |
| static void |
| skip_comment( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur = *acur; |
| |
| |
| while ( cur < limit ) |
| { |
| if ( IS_PS_NEWLINE( *cur ) ) |
| break; |
| cur++; |
| } |
| |
| *acur = cur; |
| } |
| |
| |
| static void |
| skip_spaces( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur = *acur; |
| |
| |
| while ( cur < limit ) |
| { |
| if ( !IS_PS_SPACE( *cur ) ) |
| { |
| if ( *cur == '%' ) |
| /* According to the PLRM, a comment is equal to a space. */ |
| skip_comment( &cur, limit ); |
| else |
| break; |
| } |
| cur++; |
| } |
| |
| *acur = cur; |
| } |
| |
| |
| #define IS_OCTAL_DIGIT( c ) ( '0' <= (c) && (c) <= '7' ) |
| |
| |
| /* first character must be `('; */ |
| /* *acur is positioned at the character after the closing `)' */ |
| |
| static FT_Error |
| skip_literal_string( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur = *acur; |
| FT_Int embed = 0; |
| FT_Error error = FT_ERR( Invalid_File_Format ); |
| unsigned int i; |
| |
| |
| while ( cur < limit ) |
| { |
| FT_Byte c = *cur; |
| |
| |
| ++cur; |
| |
| if ( c == '\\' ) |
| { |
| /* Red Book 3rd ed., section `Literal Text Strings', p. 29: */ |
| /* A backslash can introduce three different types */ |
| /* of escape sequences: */ |
| /* - a special escaped char like \r, \n, etc. */ |
| /* - a one-, two-, or three-digit octal number */ |
| /* - none of the above in which case the backslash is ignored */ |
| |
| if ( cur == limit ) |
| /* error (or to be ignored?) */ |
| break; |
| |
| switch ( *cur ) |
| { |
| /* skip `special' escape */ |
| case 'n': |
| case 'r': |
| case 't': |
| case 'b': |
| case 'f': |
| case '\\': |
| case '(': |
| case ')': |
| ++cur; |
| break; |
| |
| default: |
| /* skip octal escape or ignore backslash */ |
| for ( i = 0; i < 3 && cur < limit; ++i ) |
| { |
| if ( !IS_OCTAL_DIGIT( *cur ) ) |
| break; |
| |
| ++cur; |
| } |
| } |
| } |
| else if ( c == '(' ) |
| embed++; |
| else if ( c == ')' ) |
| { |
| embed--; |
| if ( embed == 0 ) |
| { |
| error = FT_Err_Ok; |
| break; |
| } |
| } |
| } |
| |
| *acur = cur; |
| |
| return error; |
| } |
| |
| |
| /* first character must be `<' */ |
| |
| static FT_Error |
| skip_string( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur = *acur; |
| FT_Error err = FT_Err_Ok; |
| |
| |
| while ( ++cur < limit ) |
| { |
| /* All whitespace characters are ignored. */ |
| skip_spaces( &cur, limit ); |
| if ( cur >= limit ) |
| break; |
| |
| if ( !IS_PS_XDIGIT( *cur ) ) |
| break; |
| } |
| |
| if ( cur < limit && *cur != '>' ) |
| { |
| FT_ERROR(( "skip_string: missing closing delimiter `>'\n" )); |
| err = FT_THROW( Invalid_File_Format ); |
| } |
| else |
| cur++; |
| |
| *acur = cur; |
| return err; |
| } |
| |
| |
| /* first character must be the opening brace that */ |
| /* starts the procedure */ |
| |
| /* NB: [ and ] need not match: */ |
| /* `/foo {[} def' is a valid PostScript fragment, */ |
| /* even within a Type1 font */ |
| |
| static FT_Error |
| skip_procedure( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur; |
| FT_Int embed = 0; |
| FT_Error error = FT_Err_Ok; |
| |
| |
| FT_ASSERT( **acur == '{' ); |
| |
| for ( cur = *acur; cur < limit && error == FT_Err_Ok; ++cur ) |
| { |
| switch ( *cur ) |
| { |
| case '{': |
| ++embed; |
| break; |
| |
| case '}': |
| --embed; |
| if ( embed == 0 ) |
| { |
| ++cur; |
| goto end; |
| } |
| break; |
| |
| case '(': |
| error = skip_literal_string( &cur, limit ); |
| break; |
| |
| case '<': |
| error = skip_string( &cur, limit ); |
| break; |
| |
| case '%': |
| skip_comment( &cur, limit ); |
| break; |
| } |
| } |
| |
| end: |
| if ( embed != 0 ) |
| error = FT_THROW( Invalid_File_Format ); |
| |
| *acur = cur; |
| |
| return error; |
| } |
| |
| |
| /***********************************************************************/ |
| /* */ |
| /* All exported parsing routines handle leading whitespace and stop at */ |
| /* the first character which isn't part of the just handled token. */ |
| /* */ |
| /***********************************************************************/ |
| |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_skip_PS_token( PS_Parser parser ) |
| { |
| /* Note: PostScript allows any non-delimiting, non-whitespace */ |
| /* character in a name (PS Ref Manual, 3rd ed, p31). */ |
| /* PostScript delimiters are (, ), <, >, [, ], {, }, /, and %. */ |
| |
| FT_Byte* cur = parser->cursor; |
| FT_Byte* limit = parser->limit; |
| FT_Error error = FT_Err_Ok; |
| |
| |
| skip_spaces( &cur, limit ); /* this also skips comments */ |
| if ( cur >= limit ) |
| goto Exit; |
| |
| /* self-delimiting, single-character tokens */ |
| if ( *cur == '[' || *cur == ']' ) |
| { |
| cur++; |
| goto Exit; |
| } |
| |
| /* skip balanced expressions (procedures and strings) */ |
| |
| if ( *cur == '{' ) /* {...} */ |
| { |
| error = skip_procedure( &cur, limit ); |
| goto Exit; |
| } |
| |
| if ( *cur == '(' ) /* (...) */ |
| { |
| error = skip_literal_string( &cur, limit ); |
| goto Exit; |
| } |
| |
| if ( *cur == '<' ) /* <...> */ |
| { |
| if ( cur + 1 < limit && *(cur + 1) == '<' ) /* << */ |
| { |
| cur++; |
| cur++; |
| } |
| else |
| error = skip_string( &cur, limit ); |
| |
| goto Exit; |
| } |
| |
| if ( *cur == '>' ) |
| { |
| cur++; |
| if ( cur >= limit || *cur != '>' ) /* >> */ |
| { |
| FT_ERROR(( "ps_parser_skip_PS_token:" |
| " unexpected closing delimiter `>'\n" )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| cur++; |
| goto Exit; |
| } |
| |
| if ( *cur == '/' ) |
| cur++; |
| |
| /* anything else */ |
| while ( cur < limit ) |
| { |
| /* *cur might be invalid (e.g., ')' or '}'), but this */ |
| /* is handled by the test `cur == parser->cursor' below */ |
| if ( IS_PS_DELIM( *cur ) ) |
| break; |
| |
| cur++; |
| } |
| |
| Exit: |
| if ( cur < limit && cur == parser->cursor ) |
| { |
| FT_ERROR(( "ps_parser_skip_PS_token:" |
| " current token is `%c' which is self-delimiting\n" |
| " " |
| " but invalid at this point\n", |
| *cur )); |
| |
| error = FT_THROW( Invalid_File_Format ); |
| } |
| |
| parser->error = error; |
| parser->cursor = cur; |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_skip_spaces( PS_Parser parser ) |
| { |
| skip_spaces( &parser->cursor, parser->limit ); |
| } |
| |
| |
| /* `token' here means either something between balanced delimiters */ |
| /* or the next token; the delimiters are not removed. */ |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_to_token( PS_Parser parser, |
| T1_Token token ) |
| { |
| FT_Byte* cur; |
| FT_Byte* limit; |
| FT_Int embed; |
| |
| |
| token->type = T1_TOKEN_TYPE_NONE; |
| token->start = NULL; |
| token->limit = NULL; |
| |
| /* first of all, skip leading whitespace */ |
| ps_parser_skip_spaces( parser ); |
| |
| cur = parser->cursor; |
| limit = parser->limit; |
| |
| if ( cur >= limit ) |
| return; |
| |
| switch ( *cur ) |
| { |
| /************* check for literal string *****************/ |
| case '(': |
| token->type = T1_TOKEN_TYPE_STRING; |
| token->start = cur; |
| |
| if ( skip_literal_string( &cur, limit ) == FT_Err_Ok ) |
| token->limit = cur; |
| break; |
| |
| /************* check for programs/array *****************/ |
| case '{': |
| token->type = T1_TOKEN_TYPE_ARRAY; |
| token->start = cur; |
| |
| if ( skip_procedure( &cur, limit ) == FT_Err_Ok ) |
| token->limit = cur; |
| break; |
| |
| /************* check for table/array ********************/ |
| /* XXX: in theory we should also look for "<<" */ |
| /* since this is semantically equivalent to "["; */ |
| /* in practice it doesn't matter (?) */ |
| case '[': |
| token->type = T1_TOKEN_TYPE_ARRAY; |
| embed = 1; |
| token->start = cur++; |
| |
| /* we need this to catch `[ ]' */ |
| parser->cursor = cur; |
| ps_parser_skip_spaces( parser ); |
| cur = parser->cursor; |
| |
| while ( cur < limit && !parser->error ) |
| { |
| /* XXX: this is wrong because it does not */ |
| /* skip comments, procedures, and strings */ |
| if ( *cur == '[' ) |
| embed++; |
| else if ( *cur == ']' ) |
| { |
| embed--; |
| if ( embed <= 0 ) |
| { |
| token->limit = ++cur; |
| break; |
| } |
| } |
| |
| parser->cursor = cur; |
| ps_parser_skip_PS_token( parser ); |
| /* we need this to catch `[XXX ]' */ |
| ps_parser_skip_spaces ( parser ); |
| cur = parser->cursor; |
| } |
| break; |
| |
| /* ************ otherwise, it is any token **************/ |
| default: |
| token->start = cur; |
| token->type = ( *cur == '/' ? T1_TOKEN_TYPE_KEY : T1_TOKEN_TYPE_ANY ); |
| ps_parser_skip_PS_token( parser ); |
| cur = parser->cursor; |
| if ( !parser->error ) |
| token->limit = cur; |
| } |
| |
| if ( !token->limit ) |
| { |
| token->start = NULL; |
| token->type = T1_TOKEN_TYPE_NONE; |
| } |
| |
| parser->cursor = cur; |
| } |
| |
| |
| /* NB: `tokens' can be NULL if we only want to count */ |
| /* the number of array elements */ |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_to_token_array( PS_Parser parser, |
| T1_Token tokens, |
| FT_UInt max_tokens, |
| FT_Int* pnum_tokens ) |
| { |
| T1_TokenRec master; |
| |
| |
| *pnum_tokens = -1; |
| |
| /* this also handles leading whitespace */ |
| ps_parser_to_token( parser, &master ); |
| |
| if ( master.type == T1_TOKEN_TYPE_ARRAY ) |
| { |
| FT_Byte* old_cursor = parser->cursor; |
| FT_Byte* old_limit = parser->limit; |
| T1_Token cur = tokens; |
| T1_Token limit = cur + max_tokens; |
| |
| |
| /* don't include outermost delimiters */ |
| parser->cursor = master.start + 1; |
| parser->limit = master.limit - 1; |
| |
| while ( parser->cursor < parser->limit ) |
| { |
| T1_TokenRec token; |
| |
| |
| ps_parser_to_token( parser, &token ); |
| if ( !token.type ) |
| break; |
| |
| if ( tokens != NULL && cur < limit ) |
| *cur = token; |
| |
| cur++; |
| } |
| |
| *pnum_tokens = (FT_Int)( cur - tokens ); |
| |
| parser->cursor = old_cursor; |
| parser->limit = old_limit; |
| } |
| } |
| |
| |
| /* first character must be a delimiter or a part of a number */ |
| /* NB: `coords' can be NULL if we just want to skip the */ |
| /* array; in this case we ignore `max_coords' */ |
| |
| static FT_Int |
| ps_tocoordarray( FT_Byte* *acur, |
| FT_Byte* limit, |
| FT_Int max_coords, |
| FT_Short* coords ) |
| { |
| FT_Byte* cur = *acur; |
| FT_Int count = 0; |
| FT_Byte c, ender; |
| |
| |
| if ( cur >= limit ) |
| goto Exit; |
| |
| /* check for the beginning of an array; otherwise, only one number */ |
| /* will be read */ |
| c = *cur; |
| ender = 0; |
| |
| if ( c == '[' ) |
| ender = ']'; |
| else if ( c == '{' ) |
| ender = '}'; |
| |
| if ( ender ) |
| cur++; |
| |
| /* now, read the coordinates */ |
| while ( cur < limit ) |
| { |
| FT_Short dummy; |
| FT_Byte* old_cur; |
| |
| |
| /* skip whitespace in front of data */ |
| skip_spaces( &cur, limit ); |
| if ( cur >= limit ) |
| goto Exit; |
| |
| if ( *cur == ender ) |
| { |
| cur++; |
| break; |
| } |
| |
| old_cur = cur; |
| |
| if ( coords != NULL && count >= max_coords ) |
| break; |
| |
| /* call PS_Conv_ToFixed() even if coords == NULL */ |
| /* to properly parse number at `cur' */ |
| *( coords != NULL ? &coords[count] : &dummy ) = |
| (FT_Short)( PS_Conv_ToFixed( &cur, limit, 0 ) >> 16 ); |
| |
| if ( old_cur == cur ) |
| { |
| count = -1; |
| goto Exit; |
| } |
| else |
| count++; |
| |
| if ( !ender ) |
| break; |
| } |
| |
| Exit: |
| *acur = cur; |
| return count; |
| } |
| |
| |
| /* first character must be a delimiter or a part of a number */ |
| /* NB: `values' can be NULL if we just want to skip the */ |
| /* array; in this case we ignore `max_values' */ |
| /* */ |
| /* return number of successfully parsed values */ |
| |
| static FT_Int |
| ps_tofixedarray( FT_Byte* *acur, |
| FT_Byte* limit, |
| FT_Int max_values, |
| FT_Fixed* values, |
| FT_Int power_ten ) |
| { |
| FT_Byte* cur = *acur; |
| FT_Int count = 0; |
| FT_Byte c, ender; |
| |
| |
| if ( cur >= limit ) |
| goto Exit; |
| |
| /* Check for the beginning of an array. Otherwise, only one number */ |
| /* will be read. */ |
| c = *cur; |
| ender = 0; |
| |
| if ( c == '[' ) |
| ender = ']'; |
| else if ( c == '{' ) |
| ender = '}'; |
| |
| if ( ender ) |
| cur++; |
| |
| /* now, read the values */ |
| while ( cur < limit ) |
| { |
| FT_Fixed dummy; |
| FT_Byte* old_cur; |
| |
| |
| /* skip whitespace in front of data */ |
| skip_spaces( &cur, limit ); |
| if ( cur >= limit ) |
| goto Exit; |
| |
| if ( *cur == ender ) |
| { |
| cur++; |
| break; |
| } |
| |
| old_cur = cur; |
| |
| if ( values != NULL && count >= max_values ) |
| break; |
| |
| /* call PS_Conv_ToFixed() even if coords == NULL */ |
| /* to properly parse number at `cur' */ |
| *( values != NULL ? &values[count] : &dummy ) = |
| PS_Conv_ToFixed( &cur, limit, power_ten ); |
| |
| if ( old_cur == cur ) |
| { |
| count = -1; |
| goto Exit; |
| } |
| else |
| count++; |
| |
| if ( !ender ) |
| break; |
| } |
| |
| Exit: |
| *acur = cur; |
| return count; |
| } |
| |
| |
| #if 0 |
| |
| static FT_String* |
| ps_tostring( FT_Byte** cursor, |
| FT_Byte* limit, |
| FT_Memory memory ) |
| { |
| FT_Byte* cur = *cursor; |
| FT_UInt len = 0; |
| FT_Int count; |
| FT_String* result; |
| FT_Error error; |
| |
| |
| /* XXX: some stupid fonts have a `Notice' or `Copyright' string */ |
| /* that simply doesn't begin with an opening parenthesis, even */ |
| /* though they have a closing one! E.g. "amuncial.pfb" */ |
| /* */ |
| /* We must deal with these ill-fated cases there. Note that */ |
| /* these fonts didn't work with the old Type 1 driver as the */ |
| /* notice/copyright was not recognized as a valid string token */ |
| /* and made the old token parser commit errors. */ |
| |
| while ( cur < limit && ( *cur == ' ' || *cur == '\t' ) ) |
| cur++; |
| if ( cur + 1 >= limit ) |
| return 0; |
| |
| if ( *cur == '(' ) |
| cur++; /* skip the opening parenthesis, if there is one */ |
| |
| *cursor = cur; |
| count = 0; |
| |
| /* then, count its length */ |
| for ( ; cur < limit; cur++ ) |
| { |
| if ( *cur == '(' ) |
| count++; |
| |
| else if ( *cur == ')' ) |
| { |
| count--; |
| if ( count < 0 ) |
| break; |
| } |
| } |
| |
| len = (FT_UInt)( cur - *cursor ); |
| if ( cur >= limit || FT_ALLOC( result, len + 1 ) ) |
| return 0; |
| |
| /* now copy the string */ |
| FT_MEM_COPY( result, *cursor, len ); |
| result[len] = '\0'; |
| *cursor = cur; |
| return result; |
| } |
| |
| #endif /* 0 */ |
| |
| |
| static int |
| ps_tobool( FT_Byte* *acur, |
| FT_Byte* limit ) |
| { |
| FT_Byte* cur = *acur; |
| FT_Bool result = 0; |
| |
| |
| /* return 1 if we find `true', 0 otherwise */ |
| if ( cur + 3 < limit && |
| cur[0] == 't' && |
| cur[1] == 'r' && |
| cur[2] == 'u' && |
| cur[3] == 'e' ) |
| { |
| result = 1; |
| cur += 5; |
| } |
| else if ( cur + 4 < limit && |
| cur[0] == 'f' && |
| cur[1] == 'a' && |
| cur[2] == 'l' && |
| cur[3] == 's' && |
| cur[4] == 'e' ) |
| { |
| result = 0; |
| cur += 6; |
| } |
| |
| *acur = cur; |
| return result; |
| } |
| |
| |
| /* load a simple field (i.e. non-table) into the current list of objects */ |
| |
| FT_LOCAL_DEF( FT_Error ) |
| ps_parser_load_field( PS_Parser parser, |
| const T1_Field field, |
| void** objects, |
| FT_UInt max_objects, |
| FT_ULong* pflags ) |
| { |
| T1_TokenRec token; |
| FT_Byte* cur; |
| FT_Byte* limit; |
| FT_UInt count; |
| FT_UInt idx; |
| FT_Error error; |
| T1_FieldType type; |
| |
| |
| /* this also skips leading whitespace */ |
| ps_parser_to_token( parser, &token ); |
| if ( !token.type ) |
| goto Fail; |
| |
| count = 1; |
| idx = 0; |
| cur = token.start; |
| limit = token.limit; |
| |
| type = field->type; |
| |
| /* we must detect arrays in /FontBBox */ |
| if ( type == T1_FIELD_TYPE_BBOX ) |
| { |
| T1_TokenRec token2; |
| FT_Byte* old_cur = parser->cursor; |
| FT_Byte* old_limit = parser->limit; |
| |
| |
| /* don't include delimiters */ |
| parser->cursor = token.start + 1; |
| parser->limit = token.limit - 1; |
| |
| ps_parser_to_token( parser, &token2 ); |
| parser->cursor = old_cur; |
| parser->limit = old_limit; |
| |
| if ( token2.type == T1_TOKEN_TYPE_ARRAY ) |
| { |
| type = T1_FIELD_TYPE_MM_BBOX; |
| goto FieldArray; |
| } |
| } |
| else if ( token.type == T1_TOKEN_TYPE_ARRAY ) |
| { |
| count = max_objects; |
| |
| FieldArray: |
| /* if this is an array and we have no blend, an error occurs */ |
| if ( max_objects == 0 ) |
| goto Fail; |
| |
| idx = 1; |
| |
| /* don't include delimiters */ |
| cur++; |
| limit--; |
| } |
| |
| for ( ; count > 0; count--, idx++ ) |
| { |
| FT_Byte* q = (FT_Byte*)objects[idx] + field->offset; |
| FT_Long val; |
| FT_String* string; |
| |
| |
| skip_spaces( &cur, limit ); |
| |
| switch ( type ) |
| { |
| case T1_FIELD_TYPE_BOOL: |
| val = ps_tobool( &cur, limit ); |
| goto Store_Integer; |
| |
| case T1_FIELD_TYPE_FIXED: |
| val = PS_Conv_ToFixed( &cur, limit, 0 ); |
| goto Store_Integer; |
| |
| case T1_FIELD_TYPE_FIXED_1000: |
| val = PS_Conv_ToFixed( &cur, limit, 3 ); |
| goto Store_Integer; |
| |
| case T1_FIELD_TYPE_INTEGER: |
| val = PS_Conv_ToInt( &cur, limit ); |
| /* fall through */ |
| |
| Store_Integer: |
| switch ( field->size ) |
| { |
| case (8 / FT_CHAR_BIT): |
| *(FT_Byte*)q = (FT_Byte)val; |
| break; |
| |
| case (16 / FT_CHAR_BIT): |
| *(FT_UShort*)q = (FT_UShort)val; |
| break; |
| |
| case (32 / FT_CHAR_BIT): |
| *(FT_UInt32*)q = (FT_UInt32)val; |
| break; |
| |
| default: /* for 64-bit systems */ |
| *(FT_Long*)q = val; |
| } |
| break; |
| |
| case T1_FIELD_TYPE_STRING: |
| case T1_FIELD_TYPE_KEY: |
| { |
| FT_Memory memory = parser->memory; |
| FT_UInt len = (FT_UInt)( limit - cur ); |
| |
| |
| if ( cur >= limit ) |
| break; |
| |
| /* we allow both a string or a name */ |
| /* for cases like /FontName (foo) def */ |
| if ( token.type == T1_TOKEN_TYPE_KEY ) |
| { |
| /* don't include leading `/' */ |
| len--; |
| cur++; |
| } |
| else if ( token.type == T1_TOKEN_TYPE_STRING ) |
| { |
| /* don't include delimiting parentheses */ |
| /* XXX we don't handle <<...>> here */ |
| /* XXX should we convert octal escapes? */ |
| /* if so, what encoding should we use? */ |
| cur++; |
| len -= 2; |
| } |
| else |
| { |
| FT_ERROR(( "ps_parser_load_field:" |
| " expected a name or string\n" |
| " " |
| " but found token of type %d instead\n", |
| token.type )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| /* for this to work (FT_String**)q must have been */ |
| /* initialized to NULL */ |
| if ( *(FT_String**)q != NULL ) |
| { |
| FT_TRACE0(( "ps_parser_load_field: overwriting field %s\n", |
| field->ident )); |
| FT_FREE( *(FT_String**)q ); |
| *(FT_String**)q = NULL; |
| } |
| |
| if ( FT_ALLOC( string, len + 1 ) ) |
| goto Exit; |
| |
| FT_MEM_COPY( string, cur, len ); |
| string[len] = 0; |
| |
| *(FT_String**)q = string; |
| } |
| break; |
| |
| case T1_FIELD_TYPE_BBOX: |
| { |
| FT_Fixed temp[4]; |
| FT_BBox* bbox = (FT_BBox*)q; |
| FT_Int result; |
| |
| |
| result = ps_tofixedarray( &cur, limit, 4, temp, 0 ); |
| |
| if ( result < 4 ) |
| { |
| FT_ERROR(( "ps_parser_load_field:" |
| " expected four integers in bounding box\n" )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| bbox->xMin = FT_RoundFix( temp[0] ); |
| bbox->yMin = FT_RoundFix( temp[1] ); |
| bbox->xMax = FT_RoundFix( temp[2] ); |
| bbox->yMax = FT_RoundFix( temp[3] ); |
| } |
| break; |
| |
| case T1_FIELD_TYPE_MM_BBOX: |
| { |
| FT_Memory memory = parser->memory; |
| FT_Fixed* temp; |
| FT_Int result; |
| FT_UInt i; |
| |
| |
| if ( FT_NEW_ARRAY( temp, max_objects * 4 ) ) |
| goto Exit; |
| |
| for ( i = 0; i < 4; i++ ) |
| { |
| result = ps_tofixedarray( &cur, limit, (FT_Int)max_objects, |
| temp + i * max_objects, 0 ); |
| if ( result < 0 || (FT_UInt)result < max_objects ) |
| { |
| FT_ERROR(( "ps_parser_load_field:" |
| " expected %d integers in the %s subarray\n" |
| " " |
| " of /FontBBox in the /Blend dictionary\n", |
| max_objects, |
| i == 0 ? "first" |
| : ( i == 1 ? "second" |
| : ( i == 2 ? "third" |
| : "fourth" ) ) )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| skip_spaces( &cur, limit ); |
| } |
| |
| for ( i = 0; i < max_objects; i++ ) |
| { |
| FT_BBox* bbox = (FT_BBox*)objects[i]; |
| |
| |
| bbox->xMin = FT_RoundFix( temp[i ] ); |
| bbox->yMin = FT_RoundFix( temp[i + max_objects] ); |
| bbox->xMax = FT_RoundFix( temp[i + 2 * max_objects] ); |
| bbox->yMax = FT_RoundFix( temp[i + 3 * max_objects] ); |
| } |
| |
| FT_FREE( temp ); |
| } |
| break; |
| |
| default: |
| /* an error occurred */ |
| goto Fail; |
| } |
| } |
| |
| #if 0 /* obsolete -- keep for reference */ |
| if ( pflags ) |
| *pflags |= 1L << field->flag_bit; |
| #else |
| FT_UNUSED( pflags ); |
| #endif |
| |
| error = FT_Err_Ok; |
| |
| Exit: |
| return error; |
| |
| Fail: |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| |
| #define T1_MAX_TABLE_ELEMENTS 32 |
| |
| |
| FT_LOCAL_DEF( FT_Error ) |
| ps_parser_load_field_table( PS_Parser parser, |
| const T1_Field field, |
| void** objects, |
| FT_UInt max_objects, |
| FT_ULong* pflags ) |
| { |
| T1_TokenRec elements[T1_MAX_TABLE_ELEMENTS]; |
| T1_Token token; |
| FT_Int num_elements; |
| FT_Error error = FT_Err_Ok; |
| FT_Byte* old_cursor; |
| FT_Byte* old_limit; |
| T1_FieldRec fieldrec = *(T1_Field)field; |
| |
| |
| fieldrec.type = T1_FIELD_TYPE_INTEGER; |
| if ( field->type == T1_FIELD_TYPE_FIXED_ARRAY || |
| field->type == T1_FIELD_TYPE_BBOX ) |
| fieldrec.type = T1_FIELD_TYPE_FIXED; |
| |
| ps_parser_to_token_array( parser, elements, |
| T1_MAX_TABLE_ELEMENTS, &num_elements ); |
| if ( num_elements < 0 ) |
| { |
| error = FT_ERR( Ignore ); |
| goto Exit; |
| } |
| if ( (FT_UInt)num_elements > field->array_max ) |
| num_elements = (FT_Int)field->array_max; |
| |
| old_cursor = parser->cursor; |
| old_limit = parser->limit; |
| |
| /* we store the elements count if necessary; */ |
| /* we further assume that `count_offset' can't be zero */ |
| if ( field->type != T1_FIELD_TYPE_BBOX && field->count_offset != 0 ) |
| *(FT_Byte*)( (FT_Byte*)objects[0] + field->count_offset ) = |
| (FT_Byte)num_elements; |
| |
| /* we now load each element, adjusting the field.offset on each one */ |
| token = elements; |
| for ( ; num_elements > 0; num_elements--, token++ ) |
| { |
| parser->cursor = token->start; |
| parser->limit = token->limit; |
| |
| error = ps_parser_load_field( parser, |
| &fieldrec, |
| objects, |
| max_objects, |
| 0 ); |
| if ( error ) |
| break; |
| |
| fieldrec.offset += fieldrec.size; |
| } |
| |
| #if 0 /* obsolete -- keep for reference */ |
| if ( pflags ) |
| *pflags |= 1L << field->flag_bit; |
| #else |
| FT_UNUSED( pflags ); |
| #endif |
| |
| parser->cursor = old_cursor; |
| parser->limit = old_limit; |
| |
| Exit: |
| return error; |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Long ) |
| ps_parser_to_int( PS_Parser parser ) |
| { |
| ps_parser_skip_spaces( parser ); |
| return PS_Conv_ToInt( &parser->cursor, parser->limit ); |
| } |
| |
| |
| /* first character must be `<' if `delimiters' is non-zero */ |
| |
| FT_LOCAL_DEF( FT_Error ) |
| ps_parser_to_bytes( PS_Parser parser, |
| FT_Byte* bytes, |
| FT_Offset max_bytes, |
| FT_ULong* pnum_bytes, |
| FT_Bool delimiters ) |
| { |
| FT_Error error = FT_Err_Ok; |
| FT_Byte* cur; |
| |
| |
| ps_parser_skip_spaces( parser ); |
| cur = parser->cursor; |
| |
| if ( cur >= parser->limit ) |
| goto Exit; |
| |
| if ( delimiters ) |
| { |
| if ( *cur != '<' ) |
| { |
| FT_ERROR(( "ps_parser_to_bytes: Missing starting delimiter `<'\n" )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| cur++; |
| } |
| |
| *pnum_bytes = PS_Conv_ASCIIHexDecode( &cur, |
| parser->limit, |
| bytes, |
| max_bytes ); |
| |
| if ( delimiters ) |
| { |
| if ( cur < parser->limit && *cur != '>' ) |
| { |
| FT_ERROR(( "ps_parser_to_bytes: Missing closing delimiter `>'\n" )); |
| error = FT_THROW( Invalid_File_Format ); |
| goto Exit; |
| } |
| |
| cur++; |
| } |
| |
| parser->cursor = cur; |
| |
| Exit: |
| return error; |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Fixed ) |
| ps_parser_to_fixed( PS_Parser parser, |
| FT_Int power_ten ) |
| { |
| ps_parser_skip_spaces( parser ); |
| return PS_Conv_ToFixed( &parser->cursor, parser->limit, power_ten ); |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Int ) |
| ps_parser_to_coord_array( PS_Parser parser, |
| FT_Int max_coords, |
| FT_Short* coords ) |
| { |
| ps_parser_skip_spaces( parser ); |
| return ps_tocoordarray( &parser->cursor, parser->limit, |
| max_coords, coords ); |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Int ) |
| ps_parser_to_fixed_array( PS_Parser parser, |
| FT_Int max_values, |
| FT_Fixed* values, |
| FT_Int power_ten ) |
| { |
| ps_parser_skip_spaces( parser ); |
| return ps_tofixedarray( &parser->cursor, parser->limit, |
| max_values, values, power_ten ); |
| } |
| |
| |
| #if 0 |
| |
| FT_LOCAL_DEF( FT_String* ) |
| T1_ToString( PS_Parser parser ) |
| { |
| return ps_tostring( &parser->cursor, parser->limit, parser->memory ); |
| } |
| |
| |
| FT_LOCAL_DEF( FT_Bool ) |
| T1_ToBool( PS_Parser parser ) |
| { |
| return ps_tobool( &parser->cursor, parser->limit ); |
| } |
| |
| #endif /* 0 */ |
| |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_init( PS_Parser parser, |
| FT_Byte* base, |
| FT_Byte* limit, |
| FT_Memory memory ) |
| { |
| parser->error = FT_Err_Ok; |
| parser->base = base; |
| parser->limit = limit; |
| parser->cursor = base; |
| parser->memory = memory; |
| parser->funcs = ps_parser_funcs; |
| } |
| |
| |
| FT_LOCAL_DEF( void ) |
| ps_parser_done( PS_Parser parser ) |
| { |
| FT_UNUSED( parser ); |
| } |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** T1 BUILDER *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| /*************************************************************************/ |
| /* */ |
| /* <Function> */ |
| /* t1_builder_init */ |
| /* */ |
| /* <Description> */ |
| /* Initializes a given glyph builder. */ |
| /* */ |
| /* <InOut> */ |
| /* builder :: A pointer to the glyph builder to initialize. */ |
| /* */ |
| /* <Input> */ |
| /* face :: The current face object. */ |
| /* */ |
| /* size :: The current size object. */ |
| /* */ |
| /* glyph :: The current glyph object. */ |
| /* */ |
| /* hinting :: Whether hinting should be applied. */ |
| /* */ |
| FT_LOCAL_DEF( void ) |
| t1_builder_init( T1_Builder builder, |
| FT_Face face, |
| FT_Size size, |
| FT_GlyphSlot glyph, |
| FT_Bool hinting ) |
| { |
| builder->parse_state = T1_Parse_Start; |
| builder->load_points = 1; |
| |
| builder->face = face; |
| builder->glyph = glyph; |
| builder->memory = face->memory; |
| |
| if ( glyph ) |
| { |
| FT_GlyphLoader loader = glyph->internal->loader; |
| |
| |
| builder->loader = loader; |
| builder->base = &loader->base.outline; |
| builder->current = &loader->current.outline; |
| FT_GlyphLoader_Rewind( loader ); |
| |
| builder->hints_globals = size->internal; |
| builder->hints_funcs = NULL; |
| |
| if ( hinting ) |
| builder->hints_funcs = glyph->internal->glyph_hints; |
| } |
| |
| builder->pos_x = 0; |
| builder->pos_y = 0; |
| |
| builder->left_bearing.x = 0; |
| builder->left_bearing.y = 0; |
| builder->advance.x = 0; |
| builder->advance.y = 0; |
| |
| builder->funcs = t1_builder_funcs; |
| } |
| |
| |
| /*************************************************************************/ |
| /* */ |
| /* <Function> */ |
| /* t1_builder_done */ |
| /* */ |
| /* <Description> */ |
| /* Finalizes a given glyph builder. Its contents can still be used */ |
| /* after the call, but the function saves important information */ |
| /* within the corresponding glyph slot. */ |
| /* */ |
| /* <Input> */ |
| /* builder :: A pointer to the glyph builder to finalize. */ |
| /* */ |
| FT_LOCAL_DEF( void ) |
| t1_builder_done( T1_Builder builder ) |
| { |
| FT_GlyphSlot glyph = builder->glyph; |
| |
| |
| if ( glyph ) |
| glyph->outline = *builder->base; |
| } |
| |
| |
| /* check that there is enough space for `count' more points */ |
| FT_LOCAL_DEF( FT_Error ) |
| t1_builder_check_points( T1_Builder builder, |
| FT_Int count ) |
| { |
| return FT_GLYPHLOADER_CHECK_POINTS( builder->loader, count, 0 ); |
| } |
| |
| |
| /* add a new point, do not check space */ |
| FT_LOCAL_DEF( void ) |
| t1_builder_add_point( T1_Builder builder, |
| FT_Pos x, |
| FT_Pos y, |
| FT_Byte flag ) |
| { |
| FT_Outline* outline = builder->current; |
| |
| |
| if ( builder->load_points ) |
| { |
| FT_Vector* point = outline->points + outline->n_points; |
| FT_Byte* control = (FT_Byte*)outline->tags + outline->n_points; |
| |
| |
| point->x = FIXED_TO_INT( x ); |
| point->y = FIXED_TO_INT( y ); |
| *control = (FT_Byte)( flag ? FT_CURVE_TAG_ON : FT_CURVE_TAG_CUBIC ); |
| } |
| outline->n_points++; |
| } |
| |
| |
| /* check space for a new on-curve point, then add it */ |
| FT_LOCAL_DEF( FT_Error ) |
| t1_builder_add_point1( T1_Builder builder, |
| FT_Pos x, |
| FT_Pos y ) |
| { |
| FT_Error error; |
| |
| |
| error = t1_builder_check_points( builder, 1 ); |
| if ( !error ) |
| t1_builder_add_point( builder, x, y, 1 ); |
| |
| return error; |
| } |
| |
| |
| /* check space for a new contour, then add it */ |
| FT_LOCAL_DEF( FT_Error ) |
| t1_builder_add_contour( T1_Builder builder ) |
| { |
| FT_Outline* outline = builder->current; |
| FT_Error error; |
| |
| |
| /* this might happen in invalid fonts */ |
| if ( !outline ) |
| { |
| FT_ERROR(( "t1_builder_add_contour: no outline to add points to\n" )); |
| return FT_THROW( Invalid_File_Format ); |
| } |
| |
| if ( !builder->load_points ) |
| { |
| outline->n_contours++; |
| return FT_Err_Ok; |
| } |
| |
| error = FT_GLYPHLOADER_CHECK_POINTS( builder->loader, 0, 1 ); |
| if ( !error ) |
| { |
| if ( outline->n_contours > 0 ) |
| outline->contours[outline->n_contours - 1] = |
| (short)( outline->n_points - 1 ); |
| |
| outline->n_contours++; |
| } |
| |
| return error; |
| } |
| |
| |
| /* if a path was begun, add its first on-curve point */ |
| FT_LOCAL_DEF( FT_Error ) |
| t1_builder_start_point( T1_Builder builder, |
| FT_Pos x, |
| FT_Pos y ) |
| { |
| FT_Error error = FT_ERR( Invalid_File_Format ); |
| |
| |
| /* test whether we are building a new contour */ |
| |
| if ( builder->parse_state == T1_Parse_Have_Path ) |
| error = FT_Err_Ok; |
| else |
| { |
| builder->parse_state = T1_Parse_Have_Path; |
| error = t1_builder_add_contour( builder ); |
| if ( !error ) |
| error = t1_builder_add_point1( builder, x, y ); |
| } |
| |
| return error; |
| } |
| |
| |
| /* close the current contour */ |
| FT_LOCAL_DEF( void ) |
| t1_builder_close_contour( T1_Builder builder ) |
| { |
| FT_Outline* outline = builder->current; |
| FT_Int first; |
| |
| |
| if ( !outline ) |
| return; |
| |
| first = outline->n_contours <= 1 |
| ? 0 : outline->contours[outline->n_contours - 2] + 1; |
| |
| /* We must not include the last point in the path if it */ |
| /* is located on the first point. */ |
| if ( outline->n_points > 1 ) |
| { |
| FT_Vector* p1 = outline->points + first; |
| FT_Vector* p2 = outline->points + outline->n_points - 1; |
| FT_Byte* control = (FT_Byte*)outline->tags + outline->n_points - 1; |
| |
| |
| /* `delete' last point only if it coincides with the first */ |
| /* point and it is not a control point (which can happen). */ |
| if ( p1->x == p2->x && p1->y == p2->y ) |
| if ( *control == FT_CURVE_TAG_ON ) |
| outline->n_points--; |
| } |
| |
| if ( outline->n_contours > 0 ) |
| { |
| /* Don't add contours only consisting of one point, i.e., */ |
| /* check whether the first and the last point is the same. */ |
| if ( first == outline->n_points - 1 ) |
| { |
| outline->n_contours--; |
| outline->n_points--; |
| } |
| else |
| outline->contours[outline->n_contours - 1] = |
| (short)( outline->n_points - 1 ); |
| } |
| } |
| |
| |
| /*************************************************************************/ |
| /*************************************************************************/ |
| /***** *****/ |
| /***** OTHER *****/ |
| /***** *****/ |
| /*************************************************************************/ |
| /*************************************************************************/ |
| |
| FT_LOCAL_DEF( void ) |
| t1_decrypt( FT_Byte* buffer, |
| FT_Offset length, |
| FT_UShort seed ) |
| { |
| PS_Conv_EexecDecode( &buffer, |
| buffer + length, |
| buffer, |
| length, |
| &seed ); |
| } |
| |
| |
| /* END */ |