shithub: freetype+ttf2subf

ref: c06eb3b73a07f18e24634bd96d3ef791bc43a3dc
dir: /src/type1z/t1load.c/

View raw version
/*******************************************************************
 *
 *  t1load.h                                                    2.0
 *
 *    Type1 Loader.                          
 *
 *  Copyright 1996-2000 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.
 *
 *
 *  This is the new and improved Type 1 data loader for FreeType 2.
 *  The old loader has several problems: it is slow, complex, difficult
 *  to maintain, and contains incredible hacks to make it accept some
 *  ill-formed Type 1 fonts without hiccup-ing. Moreover, about 5%
 *  of the Type 1 fonts on my machine still aren't loaded correctly
 *  by it.
 *
 *  This version is much simpler, much faster and also easier to
 *  read and maintain by a great order of magnitude. The idea behind
 *  it is to _not_ try to read the Type 1 token stream with a state
 *  machine (i.e. a Postscript-like interpreter) but rather to perform
 *  simple pattern-matching.
 *
 *  Indeed, nearly all data definitions follow a simple pattern
 *  like :
 *
 *      ..... /Field <data> ....
 *
 *  where <data> can be a number, a boolean, a string, or an 
 *  array of numbers. There are a few exceptions, namely the
 *  encoding, font name, charstrings and subrs and they are
 *  handled with a special pattern-matching routine.
 *
 *  All other common cases are handled very simply. The matching
 *  rules are defined in the file "t1tokens.h" through the use
 *  of several macros calls PARSE_XXXX
 *
 *  This file is included twice here, the first time to generate
 *  parsing callback functions, the second to generate a table
 *  of keywords (with pointers to the associated callback).
 *
 *  The function "parse_dict" simply scans *linearly* a given
 *  dictionary (either the top-level or private one) and calls
 *  the appropriate callback when it encounters an immediate
 *  keyword.
 *
 *  This is by far the fastest way one can find to parse and read
 *  all data :-)
 *
 *  This led to tremendous code size reduction. Note that later,
 *  the glyph loader will also be _greatly_ simplified, and the
 *  automatic hinter will replace the clumsy "t1hinter"..
 *
 ******************************************************************/

#include <freetype/internal/ftdebug.h>
#include <freetype/config/ftconfig.h>

#include <freetype/internal/t1types.h>
#include <t1errors.h>
#include <t1load.h>
#include <stdio.h>

#undef  FT_COMPONENT
#define FT_COMPONENT  trace_t1load

  typedef  void  (*T1_Parse_Func)( T1_Face   face, T1_Loader*  loader );

  typedef  struct T1_KeyWord_  
  {
    const char*    name;
    T1_Parse_Func  parsing;
  
  } T1_KeyWord;

  /* some handy macros used to easily define parsing callback functions */
  /* each callback is in charge of loading a value and storing it in a  */
  /* given field of the Type 1 face..                                   */
 
#define PARSE_(x)  static void FT_XCAT(parse_,x) ( T1_Face  face, T1_Loader* loader )

#define FIELD   FACE.x

#define PARSE_STRING(s,x)   PARSE_(x)                               \
        {                                                           \
          FACE.x = T1_ToString(&loader->parser);                  \
          FT_TRACE2(( "type1.parse_%s: \"%s\"\n", #x, FACE.x ));   \
        }

#define PARSE_NUM(s,x,t)  PARSE_(x)                                 \
        {                                                           \
          FACE.x = (t)T1_ToInt(&loader->parser);                  \
          FT_TRACE2(( "type1.parse_%s: \"%d\"\n", #x, FACE.x ));   \
        }
        
#define PARSE_INT(s,x)   PARSE_(x)                                  \
        {                                                           \
          FACE.x = T1_ToInt(&loader->parser);                     \
          FT_TRACE2(( "type1.parse_%s: \"%d\"\n", #x, FACE.x ));   \
        }

#define PARSE_BOOL(s,x)   PARSE_(x)                                 \
        {                                                           \
          FACE.x = T1_ToBool(&loader->parser);                    \
          FT_TRACE2(( "type1.parse_%s : \"%s\"\n",                \
                       #x, FACE.x ? "true" : "false" ));              \
        }
 
#define PARSE_FIXED(s,x)   PARSE_(x)                                        \
        {                                                                   \
          FACE.x = T1_ToFixed(&loader->parser,3);                         \
          FT_TRACE2(( "type1.parse_%s: \"%f\"\n", #x, FACE.x/65536.0 ));   \
        }
 
#define PARSE_COORDS(s,c,m,x)   PARSE_(x)                                         \
        {                                                                         \
          FACE.c = T1_ToCoordArray(&loader->parser, m, (T1_Short*)FACE.x );   \
          FT_TRACE2(( "type1.parse_%s\n", #x ));                                   \
        }
           
#define PARSE_FIXEDS(s,c,m,x)   PARSE_(x)                                           \
        {                                                                           \
          FACE.c = T1_ToFixedArray(&loader->parser, m, (T1_Fixed*)FACE.x, 3 );  \
          FT_TRACE2(( "type1.parse_%s\n", #x ));                                     \
        }


#define PARSE_COORDS2(s,m,x)   PARSE_(x)                                     \
        {                                                                    \
          (void)T1_ToCoordArray( &loader->parser, m, (T1_Short*)&FACE.x );  \
          FT_TRACE2(( "type1.parse_%s\n", #x ));                              \
        }

#define PARSE_FIXEDS2(s,m,x)   PARSE_(x)                                           \
        {                                                                           \
          (void)T1_ToFixedArray(&loader->parser, m, (T1_Fixed*)&FACE.x, 3 );  \
          FT_TRACE2(( "type1.parse_%s\n", #x ));                                     \
        }


/* define all parsing callbacks */
#include <t1tokens.h>


  static
  int  is_space( char c )
  {
    return ( c == ' ' || c == '\t' || c == '\r' || c == '\n' );
  }

  static
  int  is_alpha( char c )
  {
    return ( (c >= 'A' && c <= 'Z') ||
             (c >= 'a' && c <= 'z') ||
             (c >= '0' && c <= '9') ||
             (c == '.')             ||
             (c == '_') );
  }

  static
  void  skip_whitespace( T1_Parser* parser )
  {
    T1_Byte*  cur = parser->cursor;
    
    while ( cur < parser->limit && is_space(*cur) )
      cur++;
      
    parser->cursor = cur;
  }
  
  static
  void skip_blackspace( T1_Parser* parser )
  {
    T1_Byte*  cur = parser->cursor;
    
    while ( cur < parser->limit && !is_space(*cur) )
      cur++;
      
    parser->cursor = cur;
  }
  
  static
  int   read_binary_data( T1_Parser*  parser, T1_Int *size, T1_Byte* *base )
  {
    T1_Byte*  cur;
    T1_Byte*  limit = parser->limit;
    
    /* the binary data has the following format */
    /*                                          */
    /* "size" [white*] RD white ....... ND      */
    /*                                          */
    
    skip_whitespace(parser);
    cur = parser->cursor;
    
    if ( cur < limit && (T1_Byte)(*cur-'0') < 10 )
    {
      *size = T1_ToInt(parser);
      
      skip_whitespace(parser);
      skip_blackspace(parser);  /* "RD" or "-|" or something else */
      
      /* there is only one whitespace char after the */
      /* "RD" or "-|" token                          */
      *base = parser->cursor + 1;
      
      parser->cursor += *size+1;
      return 1;
    }

    FT_ERROR(( "type1.read_binary_data: invalid size field\n" ));
    parser->error = FT_Err_Invalid_File_Format;
    return 0;
  }


  /* we will now define the routines used to handle */
  /* the /Encoding, /Subrs and /CharStrings         */
  /* dictionaries..                                 */

  static
  void  parse_font_name( T1_Face  face, T1_Loader*  loader )
  {
    T1_Parser*  parser = &loader->parser;
    FT_Error    error;
    FT_Memory   memory = parser->memory;
    T1_Int      len;
    T1_Byte*    cur;
    T1_Byte*    cur2;
    T1_Byte*    limit;
    
    skip_whitespace(parser);
    cur   = parser->cursor;
    limit = parser->limit;
    if ( cur >= limit-1 || *cur != '/' ) return;
    
    cur++;
    cur2 = cur;
    while (cur2 < limit && is_alpha(*cur2)) cur2++;
    len = cur2-cur;
    if (len > 0)
    {
      if ( ALLOC( face->type1.font_name, len+1 ) )
      {
        parser->error = error;
        return;
      }
      
      MEM_Copy( face->type1.font_name, cur, len );
      face->type1.font_name[len] = '\0';
    }
    parser->cursor = cur2;
  }

  static
  void  parse_font_bbox( T1_Face  face, T1_Loader*  loader )
  {
    T1_Parser*  parser = &loader->parser;
    T1_Short    temp[4];
    T1_BBox*    bbox = &face->type1.font_bbox;
    
    (void)T1_ToCoordArray( parser, 4, temp );
    bbox->xMin = temp[0];
    bbox->yMin = temp[1];
    bbox->xMax = temp[2];
    bbox->yMax = temp[3];
  }

  static
  void  parse_encoding( T1_Face  face, T1_Loader*  loader )
  {
    T1_Parser*  parser = &loader->parser;
    T1_Byte*    cur   = parser->cursor;
    T1_Byte*    limit = parser->limit;
    
    /* skip whitespace */
    while (is_space(*cur))
    {
      cur++;
      if (cur >= limit)
      {
        FT_ERROR(( "type1.parse_encoding: out of bounds !!\n" ));
        parser->error = FT_Err_Invalid_File_Format;
        return;
      }
    }
    
    /* if we have a number, then the encoding is an array, */
    /* and we must load it now                             */
    if ((T1_Byte)(*cur - '0') < 10)
    {
      T1_Encoding*  encode     = &face->type1.encoding;
      T1_Int        count, n;
      T1_Table*     char_table = &loader->encoding_table;
      FT_Memory     memory     = parser->memory;
      FT_Error      error;
      
      /* read the number of entries in the encoding, should be 256 */
      count = T1_ToInt( parser );
      if (parser->error) return;

      /* we use a T1_Table to store our charnames */
      encode->num_chars = count;
      if ( ALLOC_ARRAY( encode->char_index, count, T1_Short   ) ||
           ALLOC_ARRAY( encode->char_name,  count, T1_String* ) ||
           (error = T1_New_Table( char_table, count, memory )) != 0     )
      {
        parser->error = error;
        return;
      }
      
      /* 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->cursor;
      limit = parser->limit;
      n     = 0;

      for ( ; cur < limit; )
      {
        T1_Byte  c;
        
        c = *cur;
        
        /* we stop when we encounter a 'def' */
        if ( c == 'd' && cur+3 < limit )
        {
          if ( cur[1] == 'e' &&
               cur[2] == 'f' &&
               is_space(cur[-1]) &&
               is_space(cur[3]) )
          {
              FT_TRACE6(( "encoding end\n" ));
              break;
          }
        }
        
        /* otherwise, we must find a number before anything else */
        if ( (T1_Byte)(c-'0') < 10 )
        {
          T1_Int  charcode;
          
          parser->cursor = cur;
          charcode = T1_ToInt(parser);
          cur = parser->cursor;
          
          /* skip whitespace */
          while (cur < limit && 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;
            T1_Int    len;
            
            while (cur2 < limit && is_alpha(*cur2)) cur2++;
            len = cur2-cur-1;
            parser->error = T1_Add_Table( char_table, charcode, cur+1, len+1 );
            char_table->elements[charcode][len] = '\0';
            if (parser->error) return;
            
            cur = cur2;
          }
        }
        else
          cur++;
      }
      
      face->type1.encoding_type = t1_encoding_array;
      parser->cursor            = cur;
    }
    /* Otherwise, we should have either "StandardEncoding" or */
    /* "ExpertEncoding"                                       */
    else
    {
      if ( cur+17 < limit &&
           strncmp( (const char*)cur, "StandardEncoding", 16 ) == 0 )
        face->type1.encoding_type = t1_encoding_standard;
        
      else if ( cur+15 < limit &&
                strncmp( (const char*)cur, "ExpertEncoding", 14 ) == 0 )
        face->type1.encoding_type = t1_encoding_expert;

      else
      {
        FT_ERROR(( "type1.parse_encoding: invalid token !!\n" ));
        parser->error = FT_Err_Invalid_File_Format;
      }      
    }
  }


  static
  void  parse_subrs( T1_Face  face, T1_Loader*  loader )
  {
    T1_Parser*  parser = &loader->parser;
    T1_Table*   table  = &loader->subrs;
    FT_Memory   memory = parser->memory;
    FT_Error    error;
    T1_Int      n;
    
    loader->num_subrs = T1_ToInt( parser );    
    if (parser->error) return;
    
    /* initialise subrs array */
    error = T1_New_Table( table, loader->num_subrs, memory );
    if (error) goto Fail;

    /* the format is simple :                                */
    /*                                                       */
    /*   "index" + binary data                               */
    /*                                                       */
    
    for ( n = 0; n < loader->num_subrs; n++ )
    {
      T1_Int    index, size;
      T1_Byte*  base;
      
      index = T1_ToInt(parser);
      if (!read_binary_data(parser,&size,&base)) return;
      
      T1_Decrypt( base, size, 4330 );      
      size -= face->type1.private_dict.lenIV;
      base += face->type1.private_dict.lenIV;

      error = T1_Add_Table( table, index, base, size );
      if (error) goto Fail;
    }
    return;

  Fail:
    parser->error = error;
  }




  static
  void  parse_charstrings( T1_Face  face, T1_Loader*  loader )
  {
    T1_Parser*  parser     = &loader->parser;
    T1_Table*   code_table = &loader->charstrings;
    T1_Table*   name_table = &loader->glyph_names;
    FT_Memory   memory     = parser->memory;
    FT_Error    error;

    T1_Byte*    cur;
    T1_Byte*    limit = parser->limit;
    T1_Int      n;
    
    loader->num_glyphs = T1_ToInt( parser );    
    if (parser->error) return;
    
    /* initialise tables */
    error = T1_New_Table( code_table, loader->num_glyphs, memory ) ||
            T1_New_Table( name_table, loader->num_glyphs, memory );
    if (error) goto Fail;
    
    n = 0;
    for ( ;; )
    {
      T1_Int    size;
      T1_Byte*  base;
      
      /* the format is simple :                   */
      /*   "/glyphname" + binary data             */
      /*                                          */
      /* note that we stop when we find a "def"   */
      /*                                          */
      skip_whitespace(parser);
      cur = parser->cursor;
      if (cur >= limit) break;

      /* we stop when we find a "def" or "end" keyword */
      if (*cur    == 'd' && 
           cur+3 < limit &&
           cur[1] == 'e' &&
           cur[2] == 'f' )
        break;

      if (*cur == 'e'   &&
          cur+3 < limit &&
          cur[1] == 'n' &&
          cur[2] == 'd' )
        break;

      if (*cur != '/')
        skip_blackspace(parser);
      else
      {
        T1_Byte*  cur2 = cur+1;
        T1_Int    len;
        
        while (cur2 < limit && is_alpha(*cur2)) cur2++;
        len = 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->cursor = cur2; 
        if (!read_binary_data(parser,&size,&base)) return;

        T1_Decrypt( base, size, 4330 );      
        size -= face->type1.private_dict.lenIV;
        base += face->type1.private_dict.lenIV;

        error = T1_Add_Table( code_table, n, base, size );
        if (error) goto Fail;
        
        n++;
        if (n >= loader->num_glyphs)
          break;
      }
    }
    loader->num_glyphs = n;
    return;
    
  Fail:
    parser->error = error;
  }


#undef PARSE_STRING
#undef PARSE_INT
#undef PARSE_NUM
#undef PARSE_BOOL
#undef PARSE_FIXED
#undef PARSE_COORDS
#undef PARSE_FIXEDS
#undef PARSE_COORDS2
#undef PARSE_FIXEDS2

#undef PARSE_
#define PARSE_(s,x)   { s, parse_##x },

#define PARSE_STRING(s,x)      PARSE_(s,x)
#define PARSE_INT(s,x)         PARSE_(s,x)
#define PARSE_NUM(s,x,t)       PARSE_(s,x)
#define PARSE_BOOL(s,x)        PARSE_(s,x)
#define PARSE_FIXED(s,x)       PARSE_(s,x)
#define PARSE_COORDS(s,c,m,x)  PARSE_(s,x)
#define PARSE_FIXEDS(s,c,m,x)  PARSE_(s,x)
#define PARSE_COORDS2(s,m,x)   PARSE_(s,x)
#define PARSE_FIXEDS2(s,m,x)   PARSE_(s,x)

  static
  const T1_KeyWord  t1_keywords[] =
  {
#include <t1tokens.h>
    /* now add the special functions... */
    { "FontName",    parse_font_name   },
    { "FontBBox",    parse_font_bbox   },
    { "Encoding",    parse_encoding    },
    { "Subrs",       parse_subrs       },
    { "CharStrings", parse_charstrings },
    { 0, 0 }
  };


  static
  T1_Error  parse_dict( T1_Face     face,
                        T1_Loader*  loader,
                        T1_Byte*    base,
                        T1_Long     size )
  {
    T1_Parser*  parser = &loader->parser;
    
    parser->cursor = base;
    parser->limit  = base + size;
    parser->error  = 0;
    
    {
      T1_Byte*  cur     = base;
      T1_Byte*  limit   = cur + size;
      
      for ( ;cur < limit; cur++ )
      {
        /* look for immediates */
        if (*cur == '/' && cur+2 < limit)
        {
          T1_Byte* cur2;
          T1_Int   len;
          
          cur  ++;
          cur2 = cur;
          while (cur2 < limit && is_alpha(*cur2)) cur2++;
          len  = cur2-cur;
          
          if (len > 0 && len < 20)
          {
            /* now, compare the immediate name to the keyword table */
            T1_KeyWord*  keyword = (T1_KeyWord*)t1_keywords;
            
            for (;;)
            {
              T1_Byte*  name;
              
              name = (T1_Byte*)keyword->name;
              if (!name) break;

              if ( cur[0] == name[0] &&
                   len == (T1_Int)strlen((const char*)name) )
              {
                T1_Int  n;
                for ( n = 1; n < len; n++ )
                  if (cur[n] != name[n])
                    break;
                    
                if (n >= len)
                {
                  /* we found it - run the parsing callback !! */
                  parser->cursor = cur2;
                  skip_whitespace( parser );
                  keyword->parsing( face, loader );
                  if (parser->error)
                    return parser->error;

                  cur = parser->cursor;
                  break;
                }
              }
              keyword++;
            }
          }
        }
      }
    }
    return parser->error;
  }

  static
  void t1_init_loader( T1_Loader* loader, T1_Face  face )
  {
    UNUSED(face);
    
    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;
    loader->subrs.init          = 0;
  }
  
  static
  void t1_done_loader( T1_Loader* loader )
  {
    T1_Parser*  parser = &loader->parser;
    
    /* finalize tables */
    T1_Release_Table( &loader->encoding_table );
    T1_Release_Table( &loader->charstrings );
    T1_Release_Table( &loader->glyph_names );
    T1_Release_Table( &loader->subrs );
    
    /* finalize parser */
    T1_Done_Parser( parser );
  }

  LOCAL_FUNC
  T1_Error  T1_Open_Face( T1_Face  face )
  {
    T1_Loader  loader;
    T1_Parser* parser;
    T1_Font*   type1 = &face->type1;
    FT_Error   error;

    t1_init_loader( &loader, face );

    /* default lenIV */
    type1->private_dict.lenIV = 4;
    
    parser = &loader.parser;
    error = T1_New_Parser( parser, face->root.stream, face->root.memory );
    if (error) goto Exit;

    error = parse_dict( face, &loader, parser->base_dict, parser->base_len );
    if (error) goto Exit;
    
    error = T1_Get_Private_Dict( parser );
    if (error) goto Exit;
    
    error = parse_dict( face, &loader, parser->private_dict, parser->private_len );
    if (error) goto Exit;    

    /* now, propagate the subrs, charstrings and glyphnames tables */
    /* to the Type1 data                                           */
    type1->num_glyphs = loader.num_glyphs;
    
    if ( !loader.subrs.init )
    {
      FT_ERROR(( "T1.Open_Face: no subrs array in face !!\n" ));
      error = FT_Err_Invalid_File_Format;
    }
    
    if ( !loader.charstrings.init )
    {
      FT_ERROR(( "T1.Open_Face: no charstrings array in face !!\n" ));
      error = FT_Err_Invalid_File_Format;
    }
    
    loader.subrs.init  = 0;
    type1->num_subrs   = loader.num_subrs;
    type1->subrs_block = loader.subrs.block;
    type1->subrs       = loader.subrs.elements;
    type1->subrs_len   = loader.subrs.lengths;
    
    loader.charstrings.init  = 0;
    type1->charstrings_block = loader.charstrings.block;
    type1->charstrings       = loader.charstrings.elements;
    type1->charstrings_len   = loader.charstrings.lengths;
    
    /* we copy the glyph names "block" and "elements" fields */
    /* but the "lengths" field must be released later..      */
    type1->glyph_names_block    = loader.glyph_names.block;
    type1->glyph_names          = (T1_String**)loader.glyph_names.elements;
    loader.glyph_names.block    = 0;
    loader.glyph_names.elements = 0;

    /* we must now build type1.encoding when we have a custom */
    /* array..                                                */
    if ( type1->encoding_type == t1_encoding_array )
    {
      T1_Int    charcode, index, min_char, max_char;
      T1_Byte*  char_name;
      T1_Byte*  glyph_name;

      /* OK, we do the following : for each element in the encoding */
      /* table, lookup the index of the glyph having the same name  */
      /* the index is then stored in type1.encoding.char_index, and */
      /* a the name to type1.encoding.char_name                     */
      
      min_char = +32000;
      max_char = -32000;
      
      charcode = 0;
      for ( ; charcode < loader.encoding_table.num_elems; charcode++ )
      {
        type1->encoding.char_index[charcode] = 0;
        type1->encoding.char_name [charcode] = ".notdef";
        
        char_name = loader.encoding_table.elements[charcode];
        if (char_name)
          for ( index = 0; index < type1->num_glyphs; index++ )
          {
            glyph_name = (T1_Byte*)type1->glyph_names[index];
            if ( strcmp( (const char*)char_name,
                         (const char*)glyph_name ) == 0 )
            {
              type1->encoding.char_index[charcode] = index;
              type1->encoding.char_name [charcode] = (char*)glyph_name;
              
              if (charcode < min_char) min_char = charcode;
              if (charcode > max_char) max_char = charcode;
              break;
            }
          }
      }
      type1->encoding.code_first = min_char;
      type1->encoding.code_last  = max_char;
      type1->encoding.num_chars  = loader.num_chars;
   }
    
  Exit:
    t1_done_loader( &loader );
    return error;
  }