tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

t42objs.c (19239B)


      1 /****************************************************************************
      2 *
      3 * t42objs.c
      4 *
      5 *   Type 42 objects manager (body).
      6 *
      7 * Copyright (C) 2002-2025 by
      8 * Roberto Alameda.
      9 *
     10 * This file is part of the FreeType project, and may only be used,
     11 * modified, and distributed under the terms of the FreeType project
     12 * license, LICENSE.TXT.  By continuing to use, modify, or distribute
     13 * this file you indicate that you have read the license and
     14 * understand and accept it fully.
     15 *
     16 */
     17 
     18 
     19 #include "t42objs.h"
     20 #include "t42parse.h"
     21 #include "t42error.h"
     22 #include <freetype/internal/ftdebug.h>
     23 #include <freetype/ftlist.h>
     24 #include <freetype/ttnameid.h>
     25 
     26 
     27 #undef  FT_COMPONENT
     28 #define FT_COMPONENT  t42
     29 
     30 
     31  static FT_Error
     32  T42_Open_Face( T42_Face  face )
     33  {
     34    T42_LoaderRec  loader;
     35    T42_Parser     parser;
     36    T1_Font        type1 = &face->type1;
     37    FT_Memory      memory = face->root.memory;
     38    FT_Error       error;
     39 
     40    PSAux_Service  psaux  = (PSAux_Service)face->psaux;
     41 
     42 
     43    t42_loader_init( &loader, face );
     44 
     45    parser = &loader.parser;
     46 
     47    face->ttf_data = NULL;
     48    face->ttf_size = 0;
     49 
     50    error = t42_parser_init( parser,
     51                             face->root.stream,
     52                             memory,
     53                             psaux);
     54    if ( error )
     55      goto Exit;
     56 
     57    error = t42_parse_dict( face, &loader,
     58                            parser->base_dict, parser->base_len );
     59    if ( error )
     60      goto Exit;
     61 
     62    if ( type1->font_type != 42 )
     63    {
     64      FT_ERROR(( "T42_Open_Face: cannot handle FontType %d\n",
     65                 type1->font_type ));
     66      error = FT_THROW( Unknown_File_Format );
     67      goto Exit;
     68    }
     69 
     70    /* now, propagate the charstrings and glyphnames tables */
     71    /* to the Type1 data                                    */
     72    type1->num_glyphs = loader.num_glyphs;
     73 
     74    if ( !loader.charstrings.init )
     75    {
     76      FT_ERROR(( "T42_Open_Face: no charstrings array in face\n" ));
     77      error = FT_THROW( Invalid_File_Format );
     78    }
     79 
     80    loader.charstrings.init  = 0;
     81    type1->charstrings_block = loader.charstrings.block;
     82    type1->charstrings       = loader.charstrings.elements;
     83    type1->charstrings_len   = loader.charstrings.lengths;
     84 
     85    /* we copy the glyph names `block' and `elements' fields; */
     86    /* the `lengths' field must be released later             */
     87    type1->glyph_names_block    = loader.glyph_names.block;
     88    type1->glyph_names          = (FT_String**)loader.glyph_names.elements;
     89    loader.glyph_names.block    = NULL;
     90    loader.glyph_names.elements = NULL;
     91 
     92    /* we must now build type1.encoding when we have a custom array */
     93    if ( type1->encoding_type == T1_ENCODING_TYPE_ARRAY )
     94    {
     95      FT_Int  charcode, idx, min_char, max_char;
     96 
     97 
     98      /* OK, we do the following: for each element in the encoding   */
     99      /* table, look up the index of the glyph having the same name  */
    100      /* as defined in the CharStrings array.                        */
    101      /* The index is then stored in type1.encoding.char_index, and  */
    102      /* the name in type1.encoding.char_name                        */
    103 
    104      min_char = 0;
    105      max_char = 0;
    106 
    107      charcode = 0;
    108      for ( ; charcode < loader.encoding_table.max_elems; charcode++ )
    109      {
    110        const FT_String*  char_name =
    111              (const FT_String*)loader.encoding_table.elements[charcode];
    112 
    113 
    114        type1->encoding.char_index[charcode] = 0;
    115        type1->encoding.char_name [charcode] = ".notdef";
    116 
    117        if ( char_name )
    118          for ( idx = 0; idx < type1->num_glyphs; idx++ )
    119          {
    120            const FT_String*  glyph_name = type1->glyph_names[idx];
    121 
    122 
    123            if ( ft_strcmp( char_name, glyph_name ) == 0 )
    124            {
    125              type1->encoding.char_index[charcode] = (FT_UShort)idx;
    126              type1->encoding.char_name [charcode] = glyph_name;
    127 
    128              /* Change min/max encoded char only if glyph name is */
    129              /* not /.notdef                                      */
    130              if ( ft_strcmp( ".notdef", glyph_name ) != 0 )
    131              {
    132                if ( charcode < min_char )
    133                  min_char = charcode;
    134                if ( charcode >= max_char )
    135                  max_char = charcode + 1;
    136              }
    137              break;
    138            }
    139          }
    140      }
    141 
    142      type1->encoding.code_first = min_char;
    143      type1->encoding.code_last  = max_char;
    144      type1->encoding.num_chars  = loader.num_chars;
    145    }
    146 
    147  Exit:
    148    t42_loader_done( &loader );
    149    if ( error )
    150    {
    151      FT_FREE( face->ttf_data );
    152      face->ttf_size = 0;
    153    }
    154    return error;
    155  }
    156 
    157 
    158  /***************** Driver Functions *************/
    159 
    160 
    161  FT_LOCAL_DEF( FT_Error )
    162  T42_Face_Init( FT_Stream      stream,
    163                 FT_Face        t42face,       /* T42_Face */
    164                 FT_Int         face_index,
    165                 FT_Int         num_params,
    166                 FT_Parameter*  params )
    167  {
    168    T42_Face            face  = (T42_Face)t42face;
    169    FT_Error            error;
    170    FT_Service_PsCMaps  psnames;
    171    PSAux_Service       psaux;
    172    FT_Face             root  = (FT_Face)&face->root;
    173    T1_Font             type1 = &face->type1;
    174    PS_FontInfo         info  = &type1->font_info;
    175 
    176    FT_UNUSED( num_params );
    177    FT_UNUSED( params );
    178    FT_UNUSED( stream );
    179 
    180 
    181    face->ttf_face       = NULL;
    182    face->root.num_faces = 1;
    183 
    184    FT_FACE_FIND_GLOBAL_SERVICE( face, psnames, POSTSCRIPT_CMAPS );
    185    face->psnames = psnames;
    186 
    187    face->psaux = FT_Get_Module_Interface( FT_FACE_LIBRARY( face ),
    188                                           "psaux" );
    189    psaux = (PSAux_Service)face->psaux;
    190    if ( !psaux )
    191    {
    192      FT_ERROR(( "T42_Face_Init: cannot access `psaux' module\n" ));
    193      error = FT_THROW( Missing_Module );
    194      goto Exit;
    195    }
    196 
    197    FT_TRACE2(( "Type 42 driver\n" ));
    198 
    199    /* open the tokenizer, this will also check the font format */
    200    error = T42_Open_Face( face );
    201    if ( error )
    202      goto Exit;
    203 
    204    /* if we just wanted to check the format, leave successfully now */
    205    if ( face_index < 0 )
    206      goto Exit;
    207 
    208    /* check the face index */
    209    if ( ( face_index & 0xFFFF ) > 0 )
    210    {
    211      FT_ERROR(( "T42_Face_Init: invalid face index\n" ));
    212      error = FT_THROW( Invalid_Argument );
    213      goto Exit;
    214    }
    215 
    216    /* Now load the font program into the face object */
    217 
    218    /* Init the face object fields */
    219    /* Now set up root face fields */
    220 
    221    root->num_glyphs   = type1->num_glyphs;
    222    root->num_charmaps = 0;
    223    root->face_index   = 0;
    224 
    225    root->face_flags |= FT_FACE_FLAG_SCALABLE    |
    226                        FT_FACE_FLAG_HORIZONTAL  |
    227                        FT_FACE_FLAG_GLYPH_NAMES;
    228 
    229    if ( info->is_fixed_pitch )
    230      root->face_flags |= FT_FACE_FLAG_FIXED_WIDTH;
    231 
    232 #ifdef TT_CONFIG_OPTION_BYTECODE_INTERPRETER
    233    root->face_flags |= FT_FACE_FLAG_HINTER;
    234 #endif
    235 
    236    /* XXX: TODO -- add kerning with .afm support */
    237 
    238    /* get style name -- be careful, some broken fonts only */
    239    /* have a `/FontName' dictionary entry!                 */
    240    root->family_name = info->family_name;
    241    /* assume "Regular" style if we don't know better */
    242    root->style_name = (char *)"Regular";
    243    if ( root->family_name )
    244    {
    245      char*  full   = info->full_name;
    246      char*  family = root->family_name;
    247 
    248 
    249      if ( full )
    250      {
    251        while ( *full )
    252        {
    253          if ( *full == *family )
    254          {
    255            family++;
    256            full++;
    257          }
    258          else
    259          {
    260            if ( *full == ' ' || *full == '-' )
    261              full++;
    262            else if ( *family == ' ' || *family == '-' )
    263              family++;
    264            else
    265            {
    266              if ( !*family )
    267                root->style_name = full;
    268              break;
    269            }
    270          }
    271        }
    272      }
    273    }
    274    else
    275    {
    276      /* do we have a `/FontName'? */
    277      if ( type1->font_name )
    278        root->family_name = type1->font_name;
    279    }
    280 
    281    /* no embedded bitmap support */
    282    root->num_fixed_sizes = 0;
    283    root->available_sizes = NULL;
    284 
    285    /* Load the TTF font embedded in the T42 font */
    286    {
    287      FT_Open_Args  args;
    288 
    289 
    290      args.flags       = FT_OPEN_MEMORY | FT_OPEN_DRIVER;
    291      args.driver      = FT_Get_Module( FT_FACE_LIBRARY( face ),
    292                                        "truetype" );
    293      args.memory_base = face->ttf_data;
    294      args.memory_size = face->ttf_size;
    295 
    296      if ( num_params )
    297      {
    298        args.flags     |= FT_OPEN_PARAMS;
    299        args.num_params = num_params;
    300        args.params     = params;
    301      }
    302 
    303      error = FT_Open_Face( FT_FACE_LIBRARY( face ),
    304                            &args, 0, &face->ttf_face );
    305    }
    306 
    307    if ( error )
    308      goto Exit;
    309 
    310    FT_Done_Size( face->ttf_face->size );
    311 
    312    /* Ignore info in FontInfo dictionary and use the info from the  */
    313    /* loaded TTF font.  The PostScript interpreter also ignores it. */
    314    root->bbox         = face->ttf_face->bbox;
    315    root->units_per_EM = face->ttf_face->units_per_EM;
    316 
    317    root->ascender  = face->ttf_face->ascender;
    318    root->descender = face->ttf_face->descender;
    319    root->height    = face->ttf_face->height;
    320 
    321    root->max_advance_width  = face->ttf_face->max_advance_width;
    322    root->max_advance_height = face->ttf_face->max_advance_height;
    323 
    324    root->underline_position  = (FT_Short)info->underline_position;
    325    root->underline_thickness = (FT_Short)info->underline_thickness;
    326 
    327    /* compute style flags */
    328    root->style_flags = 0;
    329    if ( info->italic_angle )
    330      root->style_flags |= FT_STYLE_FLAG_ITALIC;
    331 
    332    if ( face->ttf_face->style_flags & FT_STYLE_FLAG_BOLD )
    333      root->style_flags |= FT_STYLE_FLAG_BOLD;
    334 
    335    if ( face->ttf_face->face_flags & FT_FACE_FLAG_VERTICAL )
    336      root->face_flags |= FT_FACE_FLAG_VERTICAL;
    337 
    338    {
    339      if ( psnames )
    340      {
    341        FT_CharMapRec    charmap;
    342        T1_CMap_Classes  cmap_classes = psaux->t1_cmap_classes;
    343        FT_CMap_Class    clazz;
    344 
    345 
    346        charmap.face = root;
    347 
    348        /* first of all, try to synthesize a Unicode charmap */
    349        charmap.platform_id = TT_PLATFORM_MICROSOFT;
    350        charmap.encoding_id = TT_MS_ID_UNICODE_CS;
    351        charmap.encoding    = FT_ENCODING_UNICODE;
    352 
    353        error = FT_CMap_New( cmap_classes->unicode, NULL, &charmap, NULL );
    354        if ( error                                      &&
    355             FT_ERR_NEQ( error, No_Unicode_Glyph_Name ) &&
    356             FT_ERR_NEQ( error, Unimplemented_Feature ) )
    357          goto Exit;
    358        error = FT_Err_Ok;
    359 
    360        /* now, generate an Adobe Standard encoding when appropriate */
    361        charmap.platform_id = TT_PLATFORM_ADOBE;
    362        clazz               = NULL;
    363 
    364        switch ( type1->encoding_type )
    365        {
    366        case T1_ENCODING_TYPE_STANDARD:
    367          charmap.encoding    = FT_ENCODING_ADOBE_STANDARD;
    368          charmap.encoding_id = TT_ADOBE_ID_STANDARD;
    369          clazz               = cmap_classes->standard;
    370          break;
    371 
    372        case T1_ENCODING_TYPE_EXPERT:
    373          charmap.encoding    = FT_ENCODING_ADOBE_EXPERT;
    374          charmap.encoding_id = TT_ADOBE_ID_EXPERT;
    375          clazz               = cmap_classes->expert;
    376          break;
    377 
    378        case T1_ENCODING_TYPE_ARRAY:
    379          charmap.encoding    = FT_ENCODING_ADOBE_CUSTOM;
    380          charmap.encoding_id = TT_ADOBE_ID_CUSTOM;
    381          clazz               = cmap_classes->custom;
    382          break;
    383 
    384        case T1_ENCODING_TYPE_ISOLATIN1:
    385          charmap.encoding    = FT_ENCODING_ADOBE_LATIN_1;
    386          charmap.encoding_id = TT_ADOBE_ID_LATIN_1;
    387          clazz               = cmap_classes->unicode;
    388          break;
    389 
    390        default:
    391          ;
    392        }
    393 
    394        if ( clazz )
    395          error = FT_CMap_New( clazz, NULL, &charmap, NULL );
    396      }
    397    }
    398  Exit:
    399    return error;
    400  }
    401 
    402 
    403  FT_LOCAL_DEF( void )
    404  T42_Face_Done( FT_Face  t42face )
    405  {
    406    T42_Face     face = (T42_Face)t42face;
    407    T1_Font      type1;
    408    PS_FontInfo  info;
    409    FT_Memory    memory;
    410 
    411 
    412    if ( !face )
    413      return;
    414 
    415    type1  = &face->type1;
    416    info   = &type1->font_info;
    417    memory = face->root.memory;
    418 
    419    /* delete internal ttf face prior to freeing face->ttf_data */
    420    if ( face->ttf_face )
    421      FT_Done_Face( face->ttf_face );
    422 
    423    /* release font info strings */
    424    FT_FREE( info->version );
    425    FT_FREE( info->notice );
    426    FT_FREE( info->full_name );
    427    FT_FREE( info->family_name );
    428    FT_FREE( info->weight );
    429 
    430    /* release top dictionary */
    431    FT_FREE( type1->charstrings_len );
    432    FT_FREE( type1->charstrings );
    433    FT_FREE( type1->glyph_names );
    434 
    435    FT_FREE( type1->charstrings_block );
    436    FT_FREE( type1->glyph_names_block );
    437 
    438    FT_FREE( type1->encoding.char_index );
    439    FT_FREE( type1->encoding.char_name );
    440    FT_FREE( type1->font_name );
    441 
    442    FT_FREE( face->ttf_data );
    443 
    444 #if 0
    445    /* release afm data if present */
    446    if ( face->afm_data )
    447      T1_Done_AFM( memory, (T1_AFM*)face->afm_data );
    448 #endif
    449 
    450    /* release unicode map, if any */
    451    FT_FREE( face->unicode_map.maps );
    452    face->unicode_map.num_maps = 0;
    453 
    454    face->root.family_name = NULL;
    455    face->root.style_name  = NULL;
    456  }
    457 
    458 
    459  /**************************************************************************
    460   *
    461   * @Function:
    462   *   T42_Driver_Init
    463   *
    464   * @Description:
    465   *   Initializes a given Type 42 driver object.
    466   *
    467   * @Input:
    468   *   driver ::
    469   *     A handle to the target driver object.
    470   *
    471   * @Return:
    472   *   FreeType error code.  0 means success.
    473   */
    474  FT_LOCAL_DEF( FT_Error )
    475  T42_Driver_Init( FT_Module  module )        /* T42_Driver */
    476  {
    477    T42_Driver  driver = (T42_Driver)module;
    478    FT_Module   ttmodule;
    479 
    480 
    481    ttmodule = FT_Get_Module( module->library, "truetype" );
    482    if ( !ttmodule )
    483    {
    484      FT_ERROR(( "T42_Driver_Init: cannot access `truetype' module\n" ));
    485      return FT_THROW( Missing_Module );
    486    }
    487 
    488    driver->ttclazz = (FT_Driver_Class)ttmodule->clazz;
    489 
    490    return FT_Err_Ok;
    491  }
    492 
    493 
    494  FT_LOCAL_DEF( void )
    495  T42_Driver_Done( FT_Module  module )
    496  {
    497    FT_UNUSED( module );
    498  }
    499 
    500 
    501  FT_LOCAL_DEF( FT_Error )
    502  T42_Size_Init( FT_Size  size )         /* T42_Size */
    503  {
    504    T42_Size  t42size = (T42_Size)size;
    505    FT_Face   face    = size->face;
    506    T42_Face  t42face = (T42_Face)face;
    507    FT_Size   ttsize;
    508    FT_Error  error;
    509 
    510 
    511    error = FT_New_Size( t42face->ttf_face, &ttsize );
    512    if ( !error )
    513    {
    514      t42size->ttsize = ttsize;
    515      FT_Activate_Size( ttsize );
    516    }
    517 
    518    return error;
    519  }
    520 
    521 
    522  FT_LOCAL_DEF( FT_Error )
    523  T42_Size_Request( FT_Size          t42size,      /* T42_Size */
    524                    FT_Size_Request  req )
    525  {
    526    T42_Size  size = (T42_Size)t42size;
    527    T42_Face  face = (T42_Face)t42size->face;
    528    FT_Error  error;
    529 
    530 
    531    FT_Activate_Size( size->ttsize );
    532 
    533    error = FT_Request_Size( face->ttf_face, req );
    534    if ( !error )
    535      t42size->metrics = face->ttf_face->size->metrics;
    536 
    537    return error;
    538  }
    539 
    540 
    541  FT_LOCAL_DEF( FT_Error )
    542  T42_Size_Select( FT_Size   t42size,         /* T42_Size */
    543                   FT_ULong  strike_index )
    544  {
    545    T42_Size  size = (T42_Size)t42size;
    546    T42_Face  face = (T42_Face)t42size->face;
    547    FT_Error  error;
    548 
    549 
    550    FT_Activate_Size( size->ttsize );
    551 
    552    error = FT_Select_Size( face->ttf_face, (FT_Int)strike_index );
    553    if ( !error )
    554      t42size->metrics = face->ttf_face->size->metrics;
    555 
    556    return error;
    557 
    558  }
    559 
    560 
    561  FT_LOCAL_DEF( void )
    562  T42_Size_Done( FT_Size  t42size )             /* T42_Size */
    563  {
    564    T42_Size     size    = (T42_Size)t42size;
    565    FT_Face      face    = t42size->face;
    566    T42_Face     t42face = (T42_Face)face;
    567    FT_ListNode  node;
    568 
    569 
    570    node = FT_List_Find( &t42face->ttf_face->sizes_list, size->ttsize );
    571    if ( node )
    572    {
    573      FT_Done_Size( size->ttsize );
    574      size->ttsize = NULL;
    575    }
    576  }
    577 
    578 
    579  FT_LOCAL_DEF( FT_Error )
    580  T42_GlyphSlot_Init( FT_GlyphSlot  t42slot )        /* T42_GlyphSlot */
    581  {
    582    T42_GlyphSlot  slot    = (T42_GlyphSlot)t42slot;
    583    FT_Face        face    = t42slot->face;
    584    T42_Face       t42face = (T42_Face)face;
    585    FT_GlyphSlot   ttslot;
    586    FT_Memory      memory  = face->memory;
    587    FT_Error       error   = FT_Err_Ok;
    588 
    589 
    590    if ( !face->glyph )
    591    {
    592      /* First glyph slot for this face */
    593      slot->ttslot = t42face->ttf_face->glyph;
    594    }
    595    else
    596    {
    597      error = FT_New_GlyphSlot( t42face->ttf_face, &ttslot );
    598      if ( !error )
    599        slot->ttslot = ttslot;
    600    }
    601 
    602    /* share the loader so that the autohinter can see it */
    603    FT_GlyphLoader_Done( slot->ttslot->internal->loader );
    604    FT_FREE( slot->ttslot->internal );
    605    slot->ttslot->internal = t42slot->internal;
    606 
    607    return error;
    608  }
    609 
    610 
    611  FT_LOCAL_DEF( void )
    612  T42_GlyphSlot_Done( FT_GlyphSlot  t42slot )       /* T42_GlyphSlot */
    613  {
    614    T42_GlyphSlot  slot = (T42_GlyphSlot)t42slot;
    615 
    616 
    617    /* do not destroy the inherited internal structure just yet */
    618    slot->ttslot->internal = NULL;
    619    FT_Done_GlyphSlot( slot->ttslot );
    620  }
    621 
    622 
    623  static void
    624  t42_glyphslot_clear( FT_GlyphSlot  slot )
    625  {
    626    /* free bitmap if needed */
    627    ft_glyphslot_free_bitmap( slot );
    628 
    629    /* clear all public fields in the glyph slot */
    630    FT_ZERO( &slot->metrics );
    631    FT_ZERO( &slot->outline );
    632    FT_ZERO( &slot->bitmap );
    633 
    634    slot->bitmap_left   = 0;
    635    slot->bitmap_top    = 0;
    636    slot->num_subglyphs = 0;
    637    slot->subglyphs     = NULL;
    638    slot->control_data  = NULL;
    639    slot->control_len   = 0;
    640    slot->other         = NULL;
    641    slot->format        = FT_GLYPH_FORMAT_NONE;
    642 
    643    slot->linearHoriAdvance = 0;
    644    slot->linearVertAdvance = 0;
    645  }
    646 
    647 
    648  FT_LOCAL_DEF( FT_Error )
    649  T42_GlyphSlot_Load( FT_GlyphSlot  glyph,
    650                      FT_Size       size,
    651                      FT_UInt       glyph_index,
    652                      FT_Int32      load_flags )
    653  {
    654    FT_Error         error;
    655    T42_GlyphSlot    t42slot = (T42_GlyphSlot)glyph;
    656    T42_Size         t42size = (T42_Size)size;
    657    T42_Face         t42face = (T42_Face)size->face;
    658    FT_Driver_Class  ttclazz = ((T42_Driver)glyph->face->driver)->ttclazz;
    659 
    660 
    661    FT_TRACE1(( "T42_GlyphSlot_Load: glyph index %u\n", glyph_index ));
    662 
    663    /* map T42 glyph index to embedded TTF's glyph index */
    664    glyph_index = (FT_UInt)ft_strtol(
    665                    (const char *)t42face->type1.charstrings[glyph_index],
    666                    NULL, 10 );
    667 
    668    t42_glyphslot_clear( t42slot->ttslot );
    669    error = ttclazz->load_glyph( t42slot->ttslot,
    670                                 t42size->ttsize,
    671                                 glyph_index,
    672                                 load_flags | FT_LOAD_NO_BITMAP );
    673 
    674    if ( !error )
    675    {
    676      glyph->metrics = t42slot->ttslot->metrics;
    677 
    678      glyph->linearHoriAdvance = t42slot->ttslot->linearHoriAdvance;
    679      glyph->linearVertAdvance = t42slot->ttslot->linearVertAdvance;
    680 
    681      glyph->format  = t42slot->ttslot->format;
    682      glyph->outline = t42slot->ttslot->outline;
    683 
    684      glyph->bitmap      = t42slot->ttslot->bitmap;
    685      glyph->bitmap_left = t42slot->ttslot->bitmap_left;
    686      glyph->bitmap_top  = t42slot->ttslot->bitmap_top;
    687 
    688      glyph->num_subglyphs = t42slot->ttslot->num_subglyphs;
    689      glyph->subglyphs     = t42slot->ttslot->subglyphs;
    690 
    691      glyph->control_data  = t42slot->ttslot->control_data;
    692      glyph->control_len   = t42slot->ttslot->control_len;
    693    }
    694 
    695    return error;
    696  }
    697 
    698 
    699 /* END */