1/***************************************************************************/
2/*                                                                         */
3/*  pfrgload.c                                                             */
4/*                                                                         */
5/*    FreeType PFR glyph loader (body).                                    */
6/*                                                                         */
7/*  Copyright 2002-2018 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 "pfrgload.h"
20#include "pfrsbit.h"
21#include "pfrload.h"            /* for macro definitions */
22#include FT_INTERNAL_DEBUG_H
23
24#include "pfrerror.h"
25
26#undef  FT_COMPONENT
27#define FT_COMPONENT  trace_pfr
28
29
30  /*************************************************************************/
31  /*************************************************************************/
32  /*****                                                               *****/
33  /*****                      PFR GLYPH BUILDER                        *****/
34  /*****                                                               *****/
35  /*************************************************************************/
36  /*************************************************************************/
37
38
39  FT_LOCAL_DEF( void )
40  pfr_glyph_init( PFR_Glyph       glyph,
41                  FT_GlyphLoader  loader )
42  {
43    FT_ZERO( glyph );
44
45    glyph->loader     = loader;
46    glyph->path_begun = 0;
47
48    FT_GlyphLoader_Rewind( loader );
49  }
50
51
52  FT_LOCAL_DEF( void )
53  pfr_glyph_done( PFR_Glyph  glyph )
54  {
55    FT_Memory  memory = glyph->loader->memory;
56
57
58    FT_FREE( glyph->x_control );
59    glyph->y_control = NULL;
60
61    glyph->max_xy_control = 0;
62#if 0
63    glyph->num_x_control  = 0;
64    glyph->num_y_control  = 0;
65#endif
66
67    FT_FREE( glyph->subs );
68
69    glyph->max_subs = 0;
70    glyph->num_subs = 0;
71
72    glyph->loader     = NULL;
73    glyph->path_begun = 0;
74  }
75
76
77  /* close current contour, if any */
78  static void
79  pfr_glyph_close_contour( PFR_Glyph  glyph )
80  {
81    FT_GlyphLoader  loader  = glyph->loader;
82    FT_Outline*     outline = &loader->current.outline;
83    FT_Int          last, first;
84
85
86    if ( !glyph->path_begun )
87      return;
88
89    /* compute first and last point indices in current glyph outline */
90    last  = outline->n_points - 1;
91    first = 0;
92    if ( outline->n_contours > 0 )
93      first = outline->contours[outline->n_contours - 1];
94
95    /* if the last point falls on the same location as the first one */
96    /* we need to delete it                                          */
97    if ( last > first )
98    {
99      FT_Vector*  p1 = outline->points + first;
100      FT_Vector*  p2 = outline->points + last;
101
102
103      if ( p1->x == p2->x && p1->y == p2->y )
104      {
105        outline->n_points--;
106        last--;
107      }
108    }
109
110    /* don't add empty contours */
111    if ( last >= first )
112      outline->contours[outline->n_contours++] = (short)last;
113
114    glyph->path_begun = 0;
115  }
116
117
118  /* reset glyph to start the loading of a new glyph */
119  static void
120  pfr_glyph_start( PFR_Glyph  glyph )
121  {
122    glyph->path_begun = 0;
123  }
124
125
126  static FT_Error
127  pfr_glyph_line_to( PFR_Glyph   glyph,
128                     FT_Vector*  to )
129  {
130    FT_GlyphLoader  loader  = glyph->loader;
131    FT_Outline*     outline = &loader->current.outline;
132    FT_Error        error;
133
134
135    /* check that we have begun a new path */
136    if ( !glyph->path_begun )
137    {
138      error = FT_THROW( Invalid_Table );
139      FT_ERROR(( "pfr_glyph_line_to: invalid glyph data\n" ));
140      goto Exit;
141    }
142
143    error = FT_GLYPHLOADER_CHECK_POINTS( loader, 1, 0 );
144    if ( !error )
145    {
146      FT_Int  n = outline->n_points;
147
148
149      outline->points[n] = *to;
150      outline->tags  [n] = FT_CURVE_TAG_ON;
151
152      outline->n_points++;
153    }
154
155  Exit:
156    return error;
157  }
158
159
160  static FT_Error
161  pfr_glyph_curve_to( PFR_Glyph   glyph,
162                      FT_Vector*  control1,
163                      FT_Vector*  control2,
164                      FT_Vector*  to )
165  {
166    FT_GlyphLoader  loader  = glyph->loader;
167    FT_Outline*     outline = &loader->current.outline;
168    FT_Error        error;
169
170
171    /* check that we have begun a new path */
172    if ( !glyph->path_begun )
173    {
174      error = FT_THROW( Invalid_Table );
175      FT_ERROR(( "pfr_glyph_line_to: invalid glyph data\n" ));
176      goto Exit;
177    }
178
179    error = FT_GLYPHLOADER_CHECK_POINTS( loader, 3, 0 );
180    if ( !error )
181    {
182      FT_Vector*  vec = outline->points         + outline->n_points;
183      FT_Byte*    tag = (FT_Byte*)outline->tags + outline->n_points;
184
185
186      vec[0] = *control1;
187      vec[1] = *control2;
188      vec[2] = *to;
189      tag[0] = FT_CURVE_TAG_CUBIC;
190      tag[1] = FT_CURVE_TAG_CUBIC;
191      tag[2] = FT_CURVE_TAG_ON;
192
193      outline->n_points = (FT_Short)( outline->n_points + 3 );
194    }
195
196  Exit:
197    return error;
198  }
199
200
201  static FT_Error
202  pfr_glyph_move_to( PFR_Glyph   glyph,
203                     FT_Vector*  to )
204  {
205    FT_GlyphLoader  loader  = glyph->loader;
206    FT_Error        error;
207
208
209    /* close current contour if any */
210    pfr_glyph_close_contour( glyph );
211
212    /* indicate that a new contour has started */
213    glyph->path_begun = 1;
214
215    /* check that there is space for a new contour and a new point */
216    error = FT_GLYPHLOADER_CHECK_POINTS( loader, 1, 1 );
217    if ( !error )
218    {
219      /* add new start point */
220      error = pfr_glyph_line_to( glyph, to );
221    }
222
223    return error;
224  }
225
226
227  static void
228  pfr_glyph_end( PFR_Glyph  glyph )
229  {
230    /* close current contour if any */
231    pfr_glyph_close_contour( glyph );
232
233    /* merge the current glyph into the stack */
234    FT_GlyphLoader_Add( glyph->loader );
235  }
236
237
238  /*************************************************************************/
239  /*************************************************************************/
240  /*****                                                               *****/
241  /*****                      PFR GLYPH LOADER                         *****/
242  /*****                                                               *****/
243  /*************************************************************************/
244  /*************************************************************************/
245
246
247  /* load a simple glyph */
248  static FT_Error
249  pfr_glyph_load_simple( PFR_Glyph  glyph,
250                         FT_Byte*   p,
251                         FT_Byte*   limit )
252  {
253    FT_Error   error  = FT_Err_Ok;
254    FT_Memory  memory = glyph->loader->memory;
255    FT_UInt    flags, x_count, y_count, i, count, mask;
256    FT_Int     x;
257
258
259    PFR_CHECK( 1 );
260    flags = PFR_NEXT_BYTE( p );
261
262    /* test for composite glyphs */
263    if ( flags & PFR_GLYPH_IS_COMPOUND )
264      goto Failure;
265
266    x_count = 0;
267    y_count = 0;
268
269    if ( flags & PFR_GLYPH_1BYTE_XYCOUNT )
270    {
271      PFR_CHECK( 1 );
272      count   = PFR_NEXT_BYTE( p );
273      x_count = count & 15;
274      y_count = count >> 4;
275    }
276    else
277    {
278      if ( flags & PFR_GLYPH_XCOUNT )
279      {
280        PFR_CHECK( 1 );
281        x_count = PFR_NEXT_BYTE( p );
282      }
283
284      if ( flags & PFR_GLYPH_YCOUNT )
285      {
286        PFR_CHECK( 1 );
287        y_count = PFR_NEXT_BYTE( p );
288      }
289    }
290
291    count = x_count + y_count;
292
293    /* re-allocate array when necessary */
294    if ( count > glyph->max_xy_control )
295    {
296      FT_UInt  new_max = FT_PAD_CEIL( count, 8 );
297
298
299      if ( FT_RENEW_ARRAY( glyph->x_control,
300                           glyph->max_xy_control,
301                           new_max ) )
302        goto Exit;
303
304      glyph->max_xy_control = new_max;
305    }
306
307    glyph->y_control = glyph->x_control + x_count;
308
309    mask = 0;
310    x    = 0;
311
312    for ( i = 0; i < count; i++ )
313    {
314      if ( ( i & 7 ) == 0 )
315      {
316        PFR_CHECK( 1 );
317        mask = PFR_NEXT_BYTE( p );
318      }
319
320      if ( mask & 1 )
321      {
322        PFR_CHECK( 2 );
323        x = PFR_NEXT_SHORT( p );
324      }
325      else
326      {
327        PFR_CHECK( 1 );
328        x += PFR_NEXT_BYTE( p );
329      }
330
331      glyph->x_control[i] = x;
332
333      mask >>= 1;
334    }
335
336    /* XXX: we ignore the secondary stroke and edge definitions */
337    /*      since we don't support native PFR hinting           */
338    /*                                                          */
339    if ( flags & PFR_GLYPH_SINGLE_EXTRA_ITEMS )
340    {
341      error = pfr_extra_items_skip( &p, limit );
342      if ( error )
343        goto Exit;
344    }
345
346    pfr_glyph_start( glyph );
347
348    /* now load a simple glyph */
349    {
350      FT_Vector   pos[4];
351      FT_Vector*  cur;
352
353
354      pos[0].x = pos[0].y = 0;
355      pos[3]   = pos[0];
356
357      for (;;)
358      {
359        FT_UInt  format, format_low, args_format = 0, args_count, n;
360
361
362        /***************************************************************/
363        /*  read instruction                                           */
364        /*                                                             */
365        PFR_CHECK( 1 );
366        format     = PFR_NEXT_BYTE( p );
367        format_low = format & 15;
368
369        switch ( format >> 4 )
370        {
371        case 0:                                               /* end glyph */
372          FT_TRACE6(( "- end glyph" ));
373          args_count = 0;
374          break;
375
376        case 1:                                  /* general line operation */
377          FT_TRACE6(( "- general line" ));
378          goto Line1;
379
380        case 4:                                 /* move to inside contour  */
381          FT_TRACE6(( "- move to inside" ));
382          goto Line1;
383
384        case 5:                                 /* move to outside contour */
385          FT_TRACE6(( "- move to outside" ));
386        Line1:
387          args_format = format_low;
388          args_count  = 1;
389          break;
390
391        case 2:                                      /* horizontal line to */
392          FT_TRACE6(( "- horizontal line to cx.%d", format_low ));
393          if ( format_low >= x_count )
394            goto Failure;
395          pos[0].x   = glyph->x_control[format_low];
396          pos[0].y   = pos[3].y;
397          pos[3]     = pos[0];
398          args_count = 0;
399          break;
400
401        case 3:                                        /* vertical line to */
402          FT_TRACE6(( "- vertical line to cy.%d", format_low ));
403          if ( format_low >= y_count )
404            goto Failure;
405          pos[0].x   = pos[3].x;
406          pos[0].y   = glyph->y_control[format_low];
407          pos[3]     = pos[0];
408          args_count = 0;
409          break;
410
411        case 6:                            /* horizontal to vertical curve */
412          FT_TRACE6(( "- hv curve " ));
413          args_format = 0xB8E;
414          args_count  = 3;
415          break;
416
417        case 7:                            /* vertical to horizontal curve */
418          FT_TRACE6(( "- vh curve" ));
419          args_format = 0xE2B;
420          args_count  = 3;
421          break;
422
423        default:                                       /* general curve to */
424          FT_TRACE6(( "- general curve" ));
425          args_count  = 4;
426          args_format = format_low;
427        }
428
429        /***********************************************************/
430        /*  now read arguments                                     */
431        /*                                                         */
432        cur = pos;
433        for ( n = 0; n < args_count; n++ )
434        {
435          FT_UInt  idx;
436          FT_Int   delta;
437
438
439          /* read the X argument */
440          switch ( args_format & 3 )
441          {
442          case 0:                           /* 8-bit index */
443            PFR_CHECK( 1 );
444            idx = PFR_NEXT_BYTE( p );
445            if ( idx >= x_count )
446              goto Failure;
447            cur->x = glyph->x_control[idx];
448            FT_TRACE7(( " cx#%d", idx ));
449            break;
450
451          case 1:                           /* 16-bit absolute value */
452            PFR_CHECK( 2 );
453            cur->x = PFR_NEXT_SHORT( p );
454            FT_TRACE7(( " x.%d", cur->x ));
455            break;
456
457          case 2:                           /* 8-bit delta */
458            PFR_CHECK( 1 );
459            delta  = PFR_NEXT_INT8( p );
460            cur->x = pos[3].x + delta;
461            FT_TRACE7(( " dx.%d", delta ));
462            break;
463
464          default:
465            FT_TRACE7(( " |" ));
466            cur->x = pos[3].x;
467          }
468
469          /* read the Y argument */
470          switch ( ( args_format >> 2 ) & 3 )
471          {
472          case 0:                           /* 8-bit index */
473            PFR_CHECK( 1 );
474            idx  = PFR_NEXT_BYTE( p );
475            if ( idx >= y_count )
476              goto Failure;
477            cur->y = glyph->y_control[idx];
478            FT_TRACE7(( " cy#%d", idx ));
479            break;
480
481          case 1:                           /* 16-bit absolute value */
482            PFR_CHECK( 2 );
483            cur->y = PFR_NEXT_SHORT( p );
484            FT_TRACE7(( " y.%d", cur->y ));
485            break;
486
487          case 2:                           /* 8-bit delta */
488            PFR_CHECK( 1 );
489            delta  = PFR_NEXT_INT8( p );
490            cur->y = pos[3].y + delta;
491            FT_TRACE7(( " dy.%d", delta ));
492            break;
493
494          default:
495            FT_TRACE7(( " -" ));
496            cur->y = pos[3].y;
497          }
498
499          /* read the additional format flag for the general curve */
500          if ( n == 0 && args_count == 4 )
501          {
502            PFR_CHECK( 1 );
503            args_format = PFR_NEXT_BYTE( p );
504            args_count--;
505          }
506          else
507            args_format >>= 4;
508
509          /* save the previous point */
510          pos[3] = cur[0];
511          cur++;
512        }
513
514        FT_TRACE7(( "\n" ));
515
516        /***********************************************************/
517        /*  finally, execute instruction                           */
518        /*                                                         */
519        switch ( format >> 4 )
520        {
521        case 0:                                       /* end glyph => EXIT */
522          pfr_glyph_end( glyph );
523          goto Exit;
524
525        case 1:                                         /* line operations */
526        case 2:
527        case 3:
528          error = pfr_glyph_line_to( glyph, pos );
529          goto Test_Error;
530
531        case 4:                                 /* move to inside contour  */
532        case 5:                                 /* move to outside contour */
533          error = pfr_glyph_move_to( glyph, pos );
534          goto Test_Error;
535
536        default:                                       /* curve operations */
537          error = pfr_glyph_curve_to( glyph, pos, pos + 1, pos + 2 );
538
539        Test_Error:  /* test error condition */
540          if ( error )
541            goto Exit;
542        }
543      } /* for (;;) */
544    }
545
546  Exit:
547    return error;
548
549  Failure:
550  Too_Short:
551    error = FT_THROW( Invalid_Table );
552    FT_ERROR(( "pfr_glyph_load_simple: invalid glyph data\n" ));
553    goto Exit;
554  }
555
556
557  /* load a composite/compound glyph */
558  static FT_Error
559  pfr_glyph_load_compound( PFR_Glyph  glyph,
560                           FT_Byte*   p,
561                           FT_Byte*   limit )
562  {
563    FT_Error        error  = FT_Err_Ok;
564    FT_GlyphLoader  loader = glyph->loader;
565    FT_Memory       memory = loader->memory;
566    PFR_SubGlyph    subglyph;
567    FT_UInt         flags, i, count, org_count;
568    FT_Int          x_pos, y_pos;
569
570
571    PFR_CHECK( 1 );
572    flags = PFR_NEXT_BYTE( p );
573
574    /* test for composite glyphs */
575    if ( !( flags & PFR_GLYPH_IS_COMPOUND ) )
576      goto Failure;
577
578    count = flags & 0x3F;
579
580    /* ignore extra items when present */
581    /*                                 */
582    if ( flags & PFR_GLYPH_COMPOUND_EXTRA_ITEMS )
583    {
584      error = pfr_extra_items_skip( &p, limit );
585      if ( error )
586        goto Exit;
587    }
588
589    /* we can't rely on the FT_GlyphLoader to load sub-glyphs, because   */
590    /* the PFR format is dumb, using direct file offsets to point to the */
591    /* sub-glyphs (instead of glyph indices).  Sigh.                     */
592    /*                                                                   */
593    /* For now, we load the list of sub-glyphs into a different array    */
594    /* but this will prevent us from using the auto-hinter at its best   */
595    /* quality.                                                          */
596    /*                                                                   */
597    org_count = glyph->num_subs;
598
599    if ( org_count + count > glyph->max_subs )
600    {
601      FT_UInt  new_max = ( org_count + count + 3 ) & (FT_UInt)-4;
602
603
604      /* we arbitrarily limit the number of subglyphs */
605      /* to avoid endless recursion                   */
606      if ( new_max > 64 )
607      {
608        error = FT_THROW( Invalid_Table );
609        FT_ERROR(( "pfr_glyph_load_compound:"
610                   " too many compound glyphs components\n" ));
611        goto Exit;
612      }
613
614      if ( FT_RENEW_ARRAY( glyph->subs, glyph->max_subs, new_max ) )
615        goto Exit;
616
617      glyph->max_subs = new_max;
618    }
619
620    subglyph = glyph->subs + org_count;
621
622    for ( i = 0; i < count; i++, subglyph++ )
623    {
624      FT_UInt  format;
625
626
627      x_pos = 0;
628      y_pos = 0;
629
630      PFR_CHECK( 1 );
631      format = PFR_NEXT_BYTE( p );
632
633      /* read scale when available */
634      subglyph->x_scale = 0x10000L;
635      if ( format & PFR_SUBGLYPH_XSCALE )
636      {
637        PFR_CHECK( 2 );
638        subglyph->x_scale = PFR_NEXT_SHORT( p ) * 16;
639      }
640
641      subglyph->y_scale = 0x10000L;
642      if ( format & PFR_SUBGLYPH_YSCALE )
643      {
644        PFR_CHECK( 2 );
645        subglyph->y_scale = PFR_NEXT_SHORT( p ) * 16;
646      }
647
648      /* read offset */
649      switch ( format & 3 )
650      {
651      case 1:
652        PFR_CHECK( 2 );
653        x_pos = PFR_NEXT_SHORT( p );
654        break;
655
656      case 2:
657        PFR_CHECK( 1 );
658        x_pos += PFR_NEXT_INT8( p );
659        break;
660
661      default:
662        ;
663      }
664
665      switch ( ( format >> 2 ) & 3 )
666      {
667      case 1:
668        PFR_CHECK( 2 );
669        y_pos = PFR_NEXT_SHORT( p );
670        break;
671
672      case 2:
673        PFR_CHECK( 1 );
674        y_pos += PFR_NEXT_INT8( p );
675        break;
676
677      default:
678        ;
679      }
680
681      subglyph->x_delta = x_pos;
682      subglyph->y_delta = y_pos;
683
684      /* read glyph position and size now */
685      if ( format & PFR_SUBGLYPH_2BYTE_SIZE )
686      {
687        PFR_CHECK( 2 );
688        subglyph->gps_size = PFR_NEXT_USHORT( p );
689      }
690      else
691      {
692        PFR_CHECK( 1 );
693        subglyph->gps_size = PFR_NEXT_BYTE( p );
694      }
695
696      if ( format & PFR_SUBGLYPH_3BYTE_OFFSET )
697      {
698        PFR_CHECK( 3 );
699        subglyph->gps_offset = PFR_NEXT_ULONG( p );
700      }
701      else
702      {
703        PFR_CHECK( 2 );
704        subglyph->gps_offset = PFR_NEXT_USHORT( p );
705      }
706
707      glyph->num_subs++;
708    }
709
710  Exit:
711    return error;
712
713  Failure:
714  Too_Short:
715    error = FT_THROW( Invalid_Table );
716    FT_ERROR(( "pfr_glyph_load_compound: invalid glyph data\n" ));
717    goto Exit;
718  }
719
720
721  static FT_Error
722  pfr_glyph_load_rec( PFR_Glyph  glyph,
723                      FT_Stream  stream,
724                      FT_ULong   gps_offset,
725                      FT_ULong   offset,
726                      FT_ULong   size )
727  {
728    FT_Error  error;
729    FT_Byte*  p;
730    FT_Byte*  limit;
731
732
733    if ( FT_STREAM_SEEK( gps_offset + offset ) ||
734         FT_FRAME_ENTER( size )                )
735      goto Exit;
736
737    p     = (FT_Byte*)stream->cursor;
738    limit = p + size;
739
740    if ( size > 0 && *p & PFR_GLYPH_IS_COMPOUND )
741    {
742      FT_UInt         n, old_count, count;
743      FT_GlyphLoader  loader = glyph->loader;
744      FT_Outline*     base   = &loader->base.outline;
745
746
747      old_count = glyph->num_subs;
748
749      /* this is a compound glyph - load it */
750      error = pfr_glyph_load_compound( glyph, p, limit );
751
752      FT_FRAME_EXIT();
753
754      if ( error )
755        goto Exit;
756
757      count = glyph->num_subs - old_count;
758
759      FT_TRACE4(( "compound glyph with %d element%s (offset %lu):\n",
760                  count,
761                  count == 1 ? "" : "s",
762                  offset ));
763
764      /* now, load each individual glyph */
765      for ( n = 0; n < count; n++ )
766      {
767        FT_Int        i, old_points, num_points;
768        PFR_SubGlyph  subglyph;
769
770
771        FT_TRACE4(( "  subglyph %d:\n", n ));
772
773        subglyph   = glyph->subs + old_count + n;
774        old_points = base->n_points;
775
776        error = pfr_glyph_load_rec( glyph, stream, gps_offset,
777                                    subglyph->gps_offset,
778                                    subglyph->gps_size );
779        if ( error )
780          break;
781
782        /* note that `glyph->subs' might have been re-allocated */
783        subglyph   = glyph->subs + old_count + n;
784        num_points = base->n_points - old_points;
785
786        /* translate and eventually scale the new glyph points */
787        if ( subglyph->x_scale != 0x10000L || subglyph->y_scale != 0x10000L )
788        {
789          FT_Vector*  vec = base->points + old_points;
790
791
792          for ( i = 0; i < num_points; i++, vec++ )
793          {
794            vec->x = FT_MulFix( vec->x, subglyph->x_scale ) +
795                       subglyph->x_delta;
796            vec->y = FT_MulFix( vec->y, subglyph->y_scale ) +
797                       subglyph->y_delta;
798          }
799        }
800        else
801        {
802          FT_Vector*  vec = loader->base.outline.points + old_points;
803
804
805          for ( i = 0; i < num_points; i++, vec++ )
806          {
807            vec->x += subglyph->x_delta;
808            vec->y += subglyph->y_delta;
809          }
810        }
811
812        /* proceed to next sub-glyph */
813      }
814
815      FT_TRACE4(( "end compound glyph with %d element%s\n",
816                  count,
817                  count == 1 ? "" : "s" ));
818    }
819    else
820    {
821      FT_TRACE4(( "simple glyph (offset %lu)\n", offset ));
822
823      /* load a simple glyph */
824      error = pfr_glyph_load_simple( glyph, p, limit );
825
826      FT_FRAME_EXIT();
827    }
828
829  Exit:
830    return error;
831  }
832
833
834  FT_LOCAL_DEF( FT_Error )
835  pfr_glyph_load( PFR_Glyph  glyph,
836                  FT_Stream  stream,
837                  FT_ULong   gps_offset,
838                  FT_ULong   offset,
839                  FT_ULong   size )
840  {
841    /* initialize glyph loader */
842    FT_GlyphLoader_Rewind( glyph->loader );
843
844    glyph->num_subs = 0;
845
846    /* load the glyph, recursively when needed */
847    return pfr_glyph_load_rec( glyph, stream, gps_offset, offset, size );
848  }
849
850
851/* END */
852