• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /***************************************************************************/
2 /*                                                                         */
3 /*  cffdrivr.c                                                             */
4 /*                                                                         */
5 /*    OpenType font driver implementation (body).                          */
6 /*                                                                         */
7 /*  Copyright 1996-2012 by                                                 */
8 /*  David Turner, Robert Wilhelm, and Werner Lemberg.                      */
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 <ft2build.h>
20 #include FT_FREETYPE_H
21 #include FT_INTERNAL_DEBUG_H
22 #include FT_INTERNAL_STREAM_H
23 #include FT_INTERNAL_SFNT_H
24 #include FT_SERVICE_CID_H
25 #include FT_SERVICE_POSTSCRIPT_INFO_H
26 #include FT_SERVICE_POSTSCRIPT_NAME_H
27 #include FT_SERVICE_TT_CMAP_H
28 
29 #include "cffdrivr.h"
30 #include "cffgload.h"
31 #include "cffload.h"
32 #include "cffcmap.h"
33 #include "cffparse.h"
34 
35 #include "cfferrs.h"
36 #include "cffpic.h"
37 
38 #include FT_SERVICE_XFREE86_NAME_H
39 #include FT_SERVICE_GLYPH_DICT_H
40 
41 
42   /*************************************************************************/
43   /*                                                                       */
44   /* The macro FT_COMPONENT is used in trace mode.  It is an implicit      */
45   /* parameter of the FT_TRACE() and FT_ERROR() macros, used to print/log  */
46   /* messages during execution.                                            */
47   /*                                                                       */
48 #undef  FT_COMPONENT
49 #define FT_COMPONENT  trace_cffdriver
50 
51 
52   /*************************************************************************/
53   /*************************************************************************/
54   /*************************************************************************/
55   /****                                                                 ****/
56   /****                                                                 ****/
57   /****                          F A C E S                              ****/
58   /****                                                                 ****/
59   /****                                                                 ****/
60   /*************************************************************************/
61   /*************************************************************************/
62   /*************************************************************************/
63 
64 
65 #undef  PAIR_TAG
66 #define PAIR_TAG( left, right )  ( ( (FT_ULong)left << 16 ) | \
67                                      (FT_ULong)right        )
68 
69 
70   /*************************************************************************/
71   /*                                                                       */
72   /* <Function>                                                            */
73   /*    cff_get_kerning                                                    */
74   /*                                                                       */
75   /* <Description>                                                         */
76   /*    A driver method used to return the kerning vector between two      */
77   /*    glyphs of the same face.                                           */
78   /*                                                                       */
79   /* <Input>                                                               */
80   /*    face        :: A handle to the source face object.                 */
81   /*                                                                       */
82   /*    left_glyph  :: The index of the left glyph in the kern pair.       */
83   /*                                                                       */
84   /*    right_glyph :: The index of the right glyph in the kern pair.      */
85   /*                                                                       */
86   /* <Output>                                                              */
87   /*    kerning     :: The kerning vector.  This is in font units for      */
88   /*                   scalable formats, and in pixels for fixed-sizes     */
89   /*                   formats.                                            */
90   /*                                                                       */
91   /* <Return>                                                              */
92   /*    FreeType error code.  0 means success.                             */
93   /*                                                                       */
94   /* <Note>                                                                */
95   /*    Only horizontal layouts (left-to-right & right-to-left) are        */
96   /*    supported by this function.  Other layouts, or more sophisticated  */
97   /*    kernings, are out of scope of this method (the basic driver        */
98   /*    interface is meant to be simple).                                  */
99   /*                                                                       */
100   /*    They can be implemented by format-specific interfaces.             */
101   /*                                                                       */
102   FT_CALLBACK_DEF( FT_Error )
cff_get_kerning(FT_Face ttface,FT_UInt left_glyph,FT_UInt right_glyph,FT_Vector * kerning)103   cff_get_kerning( FT_Face     ttface,          /* TT_Face */
104                    FT_UInt     left_glyph,
105                    FT_UInt     right_glyph,
106                    FT_Vector*  kerning )
107   {
108     TT_Face       face = (TT_Face)ttface;
109     SFNT_Service  sfnt = (SFNT_Service)face->sfnt;
110 
111 
112     kerning->x = 0;
113     kerning->y = 0;
114 
115     if ( sfnt )
116       kerning->x = sfnt->get_kerning( face, left_glyph, right_glyph );
117 
118     return CFF_Err_Ok;
119   }
120 
121 
122 #undef PAIR_TAG
123 
124 
125   /*************************************************************************/
126   /*                                                                       */
127   /* <Function>                                                            */
128   /*    cff_glyph_load                                                     */
129   /*                                                                       */
130   /* <Description>                                                         */
131   /*    A driver method used to load a glyph within a given glyph slot.    */
132   /*                                                                       */
133   /* <Input>                                                               */
134   /*    slot        :: A handle to the target slot object where the glyph  */
135   /*                   will be loaded.                                     */
136   /*                                                                       */
137   /*    size        :: A handle to the source face size at which the glyph */
138   /*                   must be scaled, loaded, etc.                        */
139   /*                                                                       */
140   /*    glyph_index :: The index of the glyph in the font file.            */
141   /*                                                                       */
142   /*    load_flags  :: A flag indicating what to load for this glyph.  The */
143   /*                   FT_LOAD_??? constants can be used to control the    */
144   /*                   glyph loading process (e.g., whether the outline    */
145   /*                   should be scaled, whether to load bitmaps or not,   */
146   /*                   whether to hint the outline, etc).                  */
147   /*                                                                       */
148   /* <Return>                                                              */
149   /*    FreeType error code.  0 means success.                             */
150   /*                                                                       */
151   FT_CALLBACK_DEF( FT_Error )
cff_glyph_load(FT_GlyphSlot cffslot,FT_Size cffsize,FT_UInt glyph_index,FT_Int32 load_flags)152   cff_glyph_load( FT_GlyphSlot  cffslot,      /* CFF_GlyphSlot */
153                   FT_Size       cffsize,      /* CFF_Size      */
154                   FT_UInt       glyph_index,
155                   FT_Int32      load_flags )
156   {
157     FT_Error       error;
158     CFF_GlyphSlot  slot = (CFF_GlyphSlot)cffslot;
159     CFF_Size       size = (CFF_Size)cffsize;
160 
161 
162     if ( !slot )
163       return CFF_Err_Invalid_Slot_Handle;
164 
165     /* check whether we want a scaled outline or bitmap */
166     if ( !size )
167       load_flags |= FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING;
168 
169     /* reset the size object if necessary */
170     if ( load_flags & FT_LOAD_NO_SCALE )
171       size = NULL;
172 
173     if ( size )
174     {
175       /* these two objects must have the same parent */
176       if ( cffsize->face != cffslot->face )
177         return CFF_Err_Invalid_Face_Handle;
178     }
179 
180     /* now load the glyph outline if necessary */
181     error = cff_slot_load( slot, size, glyph_index, load_flags );
182 
183     /* force drop-out mode to 2 - irrelevant now */
184     /* slot->outline.dropout_mode = 2; */
185 
186     return error;
187   }
188 
189 
190   FT_CALLBACK_DEF( FT_Error )
cff_get_advances(FT_Face face,FT_UInt start,FT_UInt count,FT_Int32 flags,FT_Fixed * advances)191   cff_get_advances( FT_Face    face,
192                     FT_UInt    start,
193                     FT_UInt    count,
194                     FT_Int32   flags,
195                     FT_Fixed*  advances )
196   {
197     FT_UInt       nn;
198     FT_Error      error = CFF_Err_Ok;
199     FT_GlyphSlot  slot  = face->glyph;
200 
201 
202     flags |= (FT_UInt32)FT_LOAD_ADVANCE_ONLY;
203 
204     for ( nn = 0; nn < count; nn++ )
205     {
206       error = cff_glyph_load( slot, face->size, start + nn, flags );
207       if ( error )
208         break;
209 
210       advances[nn] = ( flags & FT_LOAD_VERTICAL_LAYOUT )
211                      ? slot->linearVertAdvance
212                      : slot->linearHoriAdvance;
213     }
214 
215     return error;
216   }
217 
218 
219   /*
220    *  GLYPH DICT SERVICE
221    *
222    */
223 
224   static FT_Error
cff_get_glyph_name(CFF_Face face,FT_UInt glyph_index,FT_Pointer buffer,FT_UInt buffer_max)225   cff_get_glyph_name( CFF_Face    face,
226                       FT_UInt     glyph_index,
227                       FT_Pointer  buffer,
228                       FT_UInt     buffer_max )
229   {
230     CFF_Font    font   = (CFF_Font)face->extra.data;
231     FT_String*  gname;
232     FT_UShort   sid;
233     FT_Error    error;
234 
235 
236     if ( !font->psnames )
237     {
238       FT_ERROR(( "cff_get_glyph_name:"
239                  " cannot get glyph name from CFF & CEF fonts\n"
240                  "                   "
241                  " without the `PSNames' module\n" ));
242       error = CFF_Err_Missing_Module;
243       goto Exit;
244     }
245 
246     /* first, locate the sid in the charset table */
247     sid = font->charset.sids[glyph_index];
248 
249     /* now, lookup the name itself */
250     gname = cff_index_get_sid_string( font, sid );
251 
252     if ( gname )
253       FT_STRCPYN( buffer, gname, buffer_max );
254 
255     error = CFF_Err_Ok;
256 
257   Exit:
258     return error;
259   }
260 
261 
262   static FT_UInt
cff_get_name_index(CFF_Face face,FT_String * glyph_name)263   cff_get_name_index( CFF_Face    face,
264                       FT_String*  glyph_name )
265   {
266     CFF_Font            cff;
267     CFF_Charset         charset;
268     FT_Service_PsCMaps  psnames;
269     FT_String*          name;
270     FT_UShort           sid;
271     FT_UInt             i;
272 
273 
274     cff     = (CFF_FontRec *)face->extra.data;
275     charset = &cff->charset;
276 
277     FT_FACE_FIND_GLOBAL_SERVICE( face, psnames, POSTSCRIPT_CMAPS );
278     if ( !psnames )
279       return 0;
280 
281     for ( i = 0; i < cff->num_glyphs; i++ )
282     {
283       sid = charset->sids[i];
284 
285       if ( sid > 390 )
286         name = cff_index_get_string( cff, sid - 391 );
287       else
288         name = (FT_String *)psnames->adobe_std_strings( sid );
289 
290       if ( !name )
291         continue;
292 
293       if ( !ft_strcmp( glyph_name, name ) )
294         return i;
295     }
296 
297     return 0;
298   }
299 
300 
301   FT_DEFINE_SERVICE_GLYPHDICTREC(cff_service_glyph_dict,
302     (FT_GlyphDict_GetNameFunc)  cff_get_glyph_name,
303     (FT_GlyphDict_NameIndexFunc)cff_get_name_index
304   )
305 
306 
307   /*
308    *  POSTSCRIPT INFO SERVICE
309    *
310    */
311 
312   static FT_Int
cff_ps_has_glyph_names(FT_Face face)313   cff_ps_has_glyph_names( FT_Face  face )
314   {
315     return ( face->face_flags & FT_FACE_FLAG_GLYPH_NAMES ) > 0;
316   }
317 
318 
319   static FT_Error
cff_ps_get_font_info(CFF_Face face,PS_FontInfoRec * afont_info)320   cff_ps_get_font_info( CFF_Face         face,
321                         PS_FontInfoRec*  afont_info )
322   {
323     CFF_Font  cff   = (CFF_Font)face->extra.data;
324     FT_Error  error = CFF_Err_Ok;
325 
326 
327     if ( cff && cff->font_info == NULL )
328     {
329       CFF_FontRecDict  dict   = &cff->top_font.font_dict;
330       PS_FontInfoRec  *font_info = NULL;
331       FT_Memory        memory = face->root.memory;
332 
333 
334       if ( FT_ALLOC( font_info, sizeof ( *font_info ) ) )
335         goto Fail;
336 
337       font_info->version     = cff_index_get_sid_string( cff,
338                                                          dict->version );
339       font_info->notice      = cff_index_get_sid_string( cff,
340                                                          dict->notice );
341       font_info->full_name   = cff_index_get_sid_string( cff,
342                                                          dict->full_name );
343       font_info->family_name = cff_index_get_sid_string( cff,
344                                                          dict->family_name );
345       font_info->weight      = cff_index_get_sid_string( cff,
346                                                          dict->weight );
347       font_info->italic_angle        = dict->italic_angle;
348       font_info->is_fixed_pitch      = dict->is_fixed_pitch;
349       font_info->underline_position  = (FT_Short)dict->underline_position;
350       font_info->underline_thickness = (FT_Short)dict->underline_thickness;
351 
352       cff->font_info = font_info;
353     }
354 
355     if ( cff )
356       *afont_info = *cff->font_info;
357 
358   Fail:
359     return error;
360   }
361 
362 
363   FT_DEFINE_SERVICE_PSINFOREC(cff_service_ps_info,
364     (PS_GetFontInfoFunc)   cff_ps_get_font_info,
365     (PS_GetFontExtraFunc)  NULL,
366     (PS_HasGlyphNamesFunc) cff_ps_has_glyph_names,
367     (PS_GetFontPrivateFunc)NULL,        /* unsupported with CFF fonts */
368     (PS_GetFontValueFunc)  NULL         /* not implemented            */
369   )
370 
371 
372   /*
373    *  POSTSCRIPT NAME SERVICE
374    *
375    */
376 
377   static const char*
cff_get_ps_name(CFF_Face face)378   cff_get_ps_name( CFF_Face  face )
379   {
380     CFF_Font  cff = (CFF_Font)face->extra.data;
381 
382 
383     return (const char*)cff->font_name;
384   }
385 
386 
387   FT_DEFINE_SERVICE_PSFONTNAMEREC(cff_service_ps_name,
388     (FT_PsName_GetFunc)cff_get_ps_name
389   )
390 
391 
392   /*
393    * TT CMAP INFO
394    *
395    * If the charmap is a synthetic Unicode encoding cmap or
396    * a Type 1 standard (or expert) encoding cmap, hide TT CMAP INFO
397    * service defined in SFNT module.
398    *
399    * Otherwise call the service function in the sfnt module.
400    *
401    */
402   static FT_Error
cff_get_cmap_info(FT_CharMap charmap,TT_CMapInfo * cmap_info)403   cff_get_cmap_info( FT_CharMap    charmap,
404                      TT_CMapInfo  *cmap_info )
405   {
406     FT_CMap   cmap  = FT_CMAP( charmap );
407     FT_Error  error = CFF_Err_Ok;
408     FT_Face    face    = FT_CMAP_FACE( cmap );
409     FT_Library library = FT_FACE_LIBRARY( face );
410 
411 
412     cmap_info->language = 0;
413     cmap_info->format   = 0;
414 
415     if ( cmap->clazz != &FT_CFF_CMAP_ENCODING_CLASS_REC_GET &&
416          cmap->clazz != &FT_CFF_CMAP_UNICODE_CLASS_REC_GET  )
417     {
418       FT_Module           sfnt    = FT_Get_Module( library, "sfnt" );
419       FT_Service_TTCMaps  service =
420         (FT_Service_TTCMaps)ft_module_get_service( sfnt,
421                                                    FT_SERVICE_ID_TT_CMAP );
422 
423 
424       if ( service && service->get_cmap_info )
425         error = service->get_cmap_info( charmap, cmap_info );
426     }
427 
428     return error;
429   }
430 
431 
432   FT_DEFINE_SERVICE_TTCMAPSREC(cff_service_get_cmap_info,
433     (TT_CMap_Info_GetFunc)cff_get_cmap_info
434   )
435 
436 
437   /*
438    *  CID INFO SERVICE
439    *
440    */
441   static FT_Error
cff_get_ros(CFF_Face face,const char ** registry,const char ** ordering,FT_Int * supplement)442   cff_get_ros( CFF_Face      face,
443                const char*  *registry,
444                const char*  *ordering,
445                FT_Int       *supplement )
446   {
447     FT_Error  error = CFF_Err_Ok;
448     CFF_Font  cff   = (CFF_Font)face->extra.data;
449 
450 
451     if ( cff )
452     {
453       CFF_FontRecDict  dict = &cff->top_font.font_dict;
454 
455 
456       if ( dict->cid_registry == 0xFFFFU )
457       {
458         error = CFF_Err_Invalid_Argument;
459         goto Fail;
460       }
461 
462       if ( registry )
463       {
464         if ( cff->registry == NULL )
465           cff->registry = cff_index_get_sid_string( cff,
466                                                     dict->cid_registry );
467         *registry = cff->registry;
468       }
469 
470       if ( ordering )
471       {
472         if ( cff->ordering == NULL )
473           cff->ordering = cff_index_get_sid_string( cff,
474                                                     dict->cid_ordering );
475         *ordering = cff->ordering;
476       }
477 
478       /*
479        * XXX: According to Adobe TechNote #5176, the supplement in CFF
480        *      can be a real number. We truncate it to fit public API
481        *      since freetype-2.3.6.
482        */
483       if ( supplement )
484       {
485         if ( dict->cid_supplement < FT_INT_MIN ||
486              dict->cid_supplement > FT_INT_MAX )
487           FT_TRACE1(( "cff_get_ros: too large supplement %d is truncated\n",
488                       dict->cid_supplement ));
489         *supplement = (FT_Int)dict->cid_supplement;
490       }
491     }
492 
493   Fail:
494     return error;
495   }
496 
497 
498   static FT_Error
cff_get_is_cid(CFF_Face face,FT_Bool * is_cid)499   cff_get_is_cid( CFF_Face  face,
500                   FT_Bool  *is_cid )
501   {
502     FT_Error  error = CFF_Err_Ok;
503     CFF_Font  cff   = (CFF_Font)face->extra.data;
504 
505 
506     *is_cid = 0;
507 
508     if ( cff )
509     {
510       CFF_FontRecDict  dict = &cff->top_font.font_dict;
511 
512 
513       if ( dict->cid_registry != 0xFFFFU )
514         *is_cid = 1;
515     }
516 
517     return error;
518   }
519 
520 
521   static FT_Error
cff_get_cid_from_glyph_index(CFF_Face face,FT_UInt glyph_index,FT_UInt * cid)522   cff_get_cid_from_glyph_index( CFF_Face  face,
523                                 FT_UInt   glyph_index,
524                                 FT_UInt  *cid )
525   {
526     FT_Error  error = CFF_Err_Ok;
527     CFF_Font  cff;
528 
529 
530     cff = (CFF_Font)face->extra.data;
531 
532     if ( cff )
533     {
534       FT_UInt          c;
535       CFF_FontRecDict  dict = &cff->top_font.font_dict;
536 
537 
538       if ( dict->cid_registry == 0xFFFFU )
539       {
540         error = CFF_Err_Invalid_Argument;
541         goto Fail;
542       }
543 
544       if ( glyph_index > cff->num_glyphs )
545       {
546         error = CFF_Err_Invalid_Argument;
547         goto Fail;
548       }
549 
550       c = cff->charset.sids[glyph_index];
551 
552       if ( cid )
553         *cid = c;
554     }
555 
556   Fail:
557     return error;
558   }
559 
560 
561   FT_DEFINE_SERVICE_CIDREC(cff_service_cid_info,
562     (FT_CID_GetRegistryOrderingSupplementFunc)cff_get_ros,
563     (FT_CID_GetIsInternallyCIDKeyedFunc)      cff_get_is_cid,
564     (FT_CID_GetCIDFromGlyphIndexFunc)         cff_get_cid_from_glyph_index
565   )
566 
567 
568   /*************************************************************************/
569   /*************************************************************************/
570   /*************************************************************************/
571   /****                                                                 ****/
572   /****                                                                 ****/
573   /****                D R I V E R  I N T E R F A C E                   ****/
574   /****                                                                 ****/
575   /****                                                                 ****/
576   /*************************************************************************/
577   /*************************************************************************/
578   /*************************************************************************/
579 #ifndef FT_CONFIG_OPTION_NO_GLYPH_NAMES
580   FT_DEFINE_SERVICEDESCREC6(cff_services,
581     FT_SERVICE_ID_XF86_NAME,            FT_XF86_FORMAT_CFF,
582     FT_SERVICE_ID_POSTSCRIPT_INFO,      &FT_CFF_SERVICE_PS_INFO_GET,
583     FT_SERVICE_ID_POSTSCRIPT_FONT_NAME, &FT_CFF_SERVICE_PS_NAME_GET,
584     FT_SERVICE_ID_GLYPH_DICT,           &FT_CFF_SERVICE_GLYPH_DICT_GET,
585     FT_SERVICE_ID_TT_CMAP,              &FT_CFF_SERVICE_GET_CMAP_INFO_GET,
586     FT_SERVICE_ID_CID,                  &FT_CFF_SERVICE_CID_INFO_GET
587   )
588 #else
589   FT_DEFINE_SERVICEDESCREC5(cff_services,
590     FT_SERVICE_ID_XF86_NAME,            FT_XF86_FORMAT_CFF,
591     FT_SERVICE_ID_POSTSCRIPT_INFO,      &FT_CFF_SERVICE_PS_INFO_GET,
592     FT_SERVICE_ID_POSTSCRIPT_FONT_NAME, &FT_CFF_SERVICE_PS_NAME_GET,
593     FT_SERVICE_ID_TT_CMAP,              &FT_CFF_SERVICE_GET_CMAP_INFO_GET,
594     FT_SERVICE_ID_CID,                  &FT_CFF_SERVICE_CID_INFO_GET
595   )
596 #endif
597 
FT_CALLBACK_DEF(FT_Module_Interface)598   FT_CALLBACK_DEF( FT_Module_Interface )
599   cff_get_interface( FT_Module    driver,       /* CFF_Driver */
600                      const char*  module_interface )
601   {
602     FT_Library           library;
603     FT_Module            sfnt;
604     FT_Module_Interface  result;
605 
606 
607     /* FT_CFF_SERVICES_GET derefers `library' in PIC mode */
608 #ifdef FT_CONFIG_OPTION_PIC
609     if ( !driver )
610       return NULL;
611     library = driver->library;
612     if ( !library )
613       return NULL;
614 #endif
615 
616     result = ft_service_list_lookup( FT_CFF_SERVICES_GET, module_interface );
617     if ( result != NULL )
618       return result;
619 
620     /* `driver' is not yet evaluated in non-PIC mode */
621 #ifndef FT_CONFIG_OPTION_PIC
622     if ( !driver )
623       return NULL;
624     library = driver->library;
625     if ( !library )
626       return NULL;
627 #endif
628 
629     /* we pass our request to the `sfnt' module */
630     sfnt = FT_Get_Module( library, "sfnt" );
631 
632     return sfnt ? sfnt->clazz->get_interface( sfnt, module_interface ) : 0;
633   }
634 
635 
636   /* The FT_DriverInterface structure is defined in ftdriver.h. */
637 
638 #ifdef TT_CONFIG_OPTION_EMBEDDED_BITMAPS
639 #define CFF_SIZE_SELECT cff_size_select
640 #else
641 #define CFF_SIZE_SELECT 0
642 #endif
643 
644   FT_DEFINE_DRIVER( cff_driver_class,
645 
646       FT_MODULE_FONT_DRIVER       |
647       FT_MODULE_DRIVER_SCALABLE   |
648       FT_MODULE_DRIVER_HAS_HINTER,
649 
650       sizeof ( CFF_DriverRec ),
651       "cff",
652       0x10000L,
653       0x20000L,
654 
655       0,   /* module-specific interface */
656 
657       cff_driver_init,
658       cff_driver_done,
659       cff_get_interface,
660 
661     /* now the specific driver fields */
662     sizeof ( TT_FaceRec ),
663     sizeof ( CFF_SizeRec ),
664     sizeof ( CFF_GlyphSlotRec ),
665 
666     cff_face_init,
667     cff_face_done,
668     cff_size_init,
669     cff_size_done,
670     cff_slot_init,
671     cff_slot_done,
672 
673     ft_stub_set_char_sizes,  /* FT_CONFIG_OPTION_OLD_INTERNALS */
674     ft_stub_set_pixel_sizes, /* FT_CONFIG_OPTION_OLD_INTERNALS */
675 
676     cff_glyph_load,
677 
678     cff_get_kerning,
679     0,                       /* FT_Face_AttachFunc */
680     cff_get_advances,
681 
682     cff_size_request,
683 
684     CFF_SIZE_SELECT
685   )
686 
687 
688 /* END */
689