Initialize Tizen 2.3
[framework/graphics/freetype.git] / src / pfr / pfrgload.c
1 /***************************************************************************/
2 /*                                                                         */
3 /*  pfrgload.c                                                             */
4 /*                                                                         */
5 /*    FreeType PFR glyph loader (body).                                    */
6 /*                                                                         */
7 /*  Copyright 2002, 2003, 2005, 2007, 2010 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 than 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 = PFR_Err_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_UInt  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 = PFR_Err_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       /* add new start point */
219       error = pfr_glyph_line_to( glyph, to );
220
221     return error;
222   }
223
224
225   static void
226   pfr_glyph_end( PFR_Glyph  glyph )
227   {
228     /* close current contour if any */
229     pfr_glyph_close_contour( glyph );
230
231     /* merge the current glyph into the stack */
232     FT_GlyphLoader_Add( glyph->loader );
233   }
234
235
236   /*************************************************************************/
237   /*************************************************************************/
238   /*****                                                               *****/
239   /*****                      PFR GLYPH LOADER                         *****/
240   /*****                                                               *****/
241   /*************************************************************************/
242   /*************************************************************************/
243
244
245   /* load a simple glyph */
246   static FT_Error
247   pfr_glyph_load_simple( PFR_Glyph  glyph,
248                          FT_Byte*   p,
249                          FT_Byte*   limit )
250   {
251     FT_Error   error  = PFR_Err_Ok;
252     FT_Memory  memory = glyph->loader->memory;
253     FT_UInt    flags, x_count, y_count, i, count, mask;
254     FT_Int     x;
255
256
257     PFR_CHECK( 1 );
258     flags = PFR_NEXT_BYTE( p );
259
260     /* test for composite glyphs */
261     if ( flags & PFR_GLYPH_IS_COMPOUND )
262       goto Failure;
263
264     x_count = 0;
265     y_count = 0;
266
267     if ( flags & PFR_GLYPH_1BYTE_XYCOUNT )
268     {
269       PFR_CHECK( 1 );
270       count   = PFR_NEXT_BYTE( p );
271       x_count = count & 15;
272       y_count = count >> 4;
273     }
274     else
275     {
276       if ( flags & PFR_GLYPH_XCOUNT )
277       {
278         PFR_CHECK( 1 );
279         x_count = PFR_NEXT_BYTE( p );
280       }
281
282       if ( flags & PFR_GLYPH_YCOUNT )
283       {
284         PFR_CHECK( 1 );
285         y_count = PFR_NEXT_BYTE( p );
286       }
287     }
288
289     count = x_count + y_count;
290
291     /* re-allocate array when necessary */
292     if ( count > glyph->max_xy_control )
293     {
294       FT_UInt  new_max = FT_PAD_CEIL( count, 8 );
295
296
297       if ( FT_RENEW_ARRAY( glyph->x_control,
298                            glyph->max_xy_control,
299                            new_max ) )
300         goto Exit;
301
302       glyph->max_xy_control = new_max;
303     }
304
305     glyph->y_control = glyph->x_control + x_count;
306
307     mask  = 0;
308     x     = 0;
309
310     for ( i = 0; i < count; i++ )
311     {
312       if ( ( i & 7 ) == 0 )
313       {
314         PFR_CHECK( 1 );
315         mask = PFR_NEXT_BYTE( p );
316       }
317
318       if ( mask & 1 )
319       {
320         PFR_CHECK( 2 );
321         x = PFR_NEXT_SHORT( p );
322       }
323       else
324       {
325         PFR_CHECK( 1 );
326         x += PFR_NEXT_BYTE( p );
327       }
328
329       glyph->x_control[i] = x;
330
331       mask >>= 1;
332     }
333
334     /* XXX: for now we ignore the secondary stroke and edge definitions */
335     /*      since we don't want to support native PFR hinting           */
336     /*                                                                  */
337     if ( flags & PFR_GLYPH_EXTRA_ITEMS )
338     {
339       error = pfr_extra_items_skip( &p, limit );
340       if ( error )
341         goto Exit;
342     }
343
344     pfr_glyph_start( glyph );
345
346     /* now load a simple glyph */
347     {
348       FT_Vector   pos[4];
349       FT_Vector*  cur;
350
351
352       pos[0].x = pos[0].y = 0;
353       pos[3]   = pos[0];
354
355       for (;;)
356       {
357         FT_UInt  format, format_low, args_format = 0, args_count, n;
358
359
360         /***************************************************************/
361         /*  read instruction                                           */
362         /*                                                             */
363         PFR_CHECK( 1 );
364         format     = PFR_NEXT_BYTE( p );
365         format_low = format & 15;
366
367         switch ( format >> 4 )
368         {
369         case 0:                             /* end glyph */
370           FT_TRACE6(( "- end glyph" ));
371           args_count = 0;
372           break;
373
374         case 1:                             /* general line operation */
375           FT_TRACE6(( "- general line" ));
376           goto Line1;
377
378         case 4:                             /* move to inside contour  */
379           FT_TRACE6(( "- move to inside" ));
380           goto Line1;
381
382         case 5:                             /* move to outside contour */
383           FT_TRACE6(( "- move to outside" ));
384         Line1:
385           args_format = format_low;
386           args_count  = 1;
387           break;
388
389         case 2:                             /* horizontal line to */
390           FT_TRACE6(( "- horizontal line to cx.%d", format_low ));
391           if ( format_low >= x_count )
392             goto Failure;
393           pos[0].x   = glyph->x_control[format_low];
394           pos[0].y   = pos[3].y;
395           pos[3]     = pos[0];
396           args_count = 0;
397           break;
398
399         case 3:                             /* vertical line to */
400           FT_TRACE6(( "- vertical line to cy.%d", format_low ));
401           if ( format_low >= y_count )
402             goto Failure;
403           pos[0].x   = pos[3].x;
404           pos[0].y   = glyph->y_control[format_low];
405           pos[3]     = pos[0];
406           args_count = 0;
407           break;
408
409         case 6:                             /* horizontal to vertical curve */
410           FT_TRACE6(( "- hv curve " ));
411           args_format = 0xB8E;
412           args_count  = 3;
413           break;
414
415         case 7:                             /* vertical to horizontal curve */
416           FT_TRACE6(( "- vh curve" ));
417           args_format = 0xE2B;
418           args_count  = 3;
419           break;
420
421         default:                            /* general curve to */
422           FT_TRACE6(( "- general curve" ));
423           args_count  = 4;
424           args_format = format_low;
425         }
426
427         /***********************************************************/
428         /*  now read arguments                                     */
429         /*                                                         */
430         cur = pos;
431         for ( n = 0; n < args_count; n++ )
432         {
433           FT_UInt  idx;
434           FT_Int   delta;
435
436
437           /* read the X argument */
438           switch ( args_format & 3 )
439           {
440           case 0:                           /* 8-bit index */
441             PFR_CHECK( 1 );
442             idx  = PFR_NEXT_BYTE( p );
443             if ( idx >= x_count )
444               goto Failure;
445             cur->x = glyph->x_control[idx];
446             FT_TRACE7(( " cx#%d", idx ));
447             break;
448
449           case 1:                           /* 16-bit value */
450             PFR_CHECK( 2 );
451             cur->x = PFR_NEXT_SHORT( p );
452             FT_TRACE7(( " x.%d", cur->x ));
453             break;
454
455           case 2:                           /* 8-bit delta */
456             PFR_CHECK( 1 );
457             delta  = PFR_NEXT_INT8( p );
458             cur->x = pos[3].x + delta;
459             FT_TRACE7(( " dx.%d", delta ));
460             break;
461
462           default:
463             FT_TRACE7(( " |" ));
464             cur->x = pos[3].x;
465           }
466
467           /* read the Y argument */
468           switch ( ( args_format >> 2 ) & 3 )
469           {
470           case 0:                           /* 8-bit index */
471             PFR_CHECK( 1 );
472             idx  = PFR_NEXT_BYTE( p );
473             if ( idx >= y_count )
474               goto Failure;
475             cur->y = glyph->y_control[idx];
476             FT_TRACE7(( " cy#%d", idx ));
477             break;
478
479           case 1:                           /* 16-bit absolute value */
480             PFR_CHECK( 2 );
481             cur->y = PFR_NEXT_SHORT( p );
482             FT_TRACE7(( " y.%d", cur->y ));
483             break;
484
485           case 2:                           /* 8-bit delta */
486             PFR_CHECK( 1 );
487             delta  = PFR_NEXT_INT8( p );
488             cur->y = pos[3].y + delta;
489             FT_TRACE7(( " dy.%d", delta ));
490             break;
491
492           default:
493             FT_TRACE7(( " -" ));
494             cur->y = pos[3].y;
495           }
496
497           /* read the additional format flag for the general curve */
498           if ( n == 0 && args_count == 4 )
499           {
500             PFR_CHECK( 1 );
501             args_format = PFR_NEXT_BYTE( p );
502             args_count--;
503           }
504           else
505             args_format >>= 4;
506
507           /* save the previous point */
508           pos[3] = cur[0];
509           cur++;
510         }
511
512         FT_TRACE7(( "\n" ));
513
514         /***********************************************************/
515         /*  finally, execute instruction                           */
516         /*                                                         */
517         switch ( format >> 4 )
518         {
519         case 0:                             /* end glyph => EXIT */
520           pfr_glyph_end( glyph );
521           goto Exit;
522
523         case 1:                             /* line operations */
524         case 2:
525         case 3:
526           error = pfr_glyph_line_to( glyph, pos );
527           goto Test_Error;
528
529         case 4:                             /* move to inside contour  */
530         case 5:                             /* move to outside contour */
531           error = pfr_glyph_move_to( glyph, pos );
532           goto Test_Error;
533
534         default:                            /* curve operations */
535           error = pfr_glyph_curve_to( glyph, pos, pos + 1, pos + 2 );
536
537         Test_Error:  /* test error condition */
538           if ( error )
539             goto Exit;
540         }
541       } /* for (;;) */
542     }
543
544   Exit:
545     return error;
546
547   Failure:
548   Too_Short:
549     error = PFR_Err_Invalid_Table;
550     FT_ERROR(( "pfr_glyph_load_simple: invalid glyph data\n" ));
551     goto Exit;
552   }
553
554
555   /* load a composite/compound glyph */
556   static FT_Error
557   pfr_glyph_load_compound( PFR_Glyph  glyph,
558                            FT_Byte*   p,
559                            FT_Byte*   limit )
560   {
561     FT_Error        error  = PFR_Err_Ok;
562     FT_GlyphLoader  loader = glyph->loader;
563     FT_Memory       memory = loader->memory;
564     PFR_SubGlyph    subglyph;
565     FT_UInt         flags, i, count, org_count;
566     FT_Int          x_pos, y_pos;
567
568
569     PFR_CHECK( 1 );
570     flags = PFR_NEXT_BYTE( p );
571
572     /* test for composite glyphs */
573     if ( !( flags & PFR_GLYPH_IS_COMPOUND ) )
574       goto Failure;
575
576     count = flags & 0x3F;
577
578     /* ignore extra items when present */
579     /*                                 */
580     if ( flags & PFR_GLYPH_EXTRA_ITEMS )
581     {
582       error = pfr_extra_items_skip( &p, limit );
583       if (error) goto Exit;
584     }
585
586     /* we can't rely on the FT_GlyphLoader to load sub-glyphs, because   */
587     /* the PFR format is dumb, using direct file offsets to point to the */
588     /* sub-glyphs (instead of glyph indices).  Sigh.                     */
589     /*                                                                   */
590     /* For now, we load the list of sub-glyphs into a different array    */
591     /* but this will prevent us from using the auto-hinter at its best   */
592     /* quality.                                                          */
593     /*                                                                   */
594     org_count = glyph->num_subs;
595
596     if ( org_count + count > glyph->max_subs )
597     {
598       FT_UInt  new_max = ( org_count + count + 3 ) & (FT_UInt)-4;
599
600
601       /* we arbitrarily limit the number of subglyphs */
602       /* to avoid endless recursion                   */
603       if ( new_max > 64 )
604       {
605         error = PFR_Err_Invalid_Table;
606         FT_ERROR(( "pfr_glyph_load_compound:"
607                    " too many compound glyphs components\n" ));
608         goto Exit;
609       }
610
611       if ( FT_RENEW_ARRAY( glyph->subs, glyph->max_subs, new_max ) )
612         goto Exit;
613
614       glyph->max_subs = new_max;
615     }
616
617     subglyph = glyph->subs + org_count;
618
619     for ( i = 0; i < count; i++, subglyph++ )
620     {
621       FT_UInt  format;
622
623
624       x_pos = 0;
625       y_pos = 0;
626
627       PFR_CHECK( 1 );
628       format = PFR_NEXT_BYTE( p );
629
630       /* read scale when available */
631       subglyph->x_scale = 0x10000L;
632       if ( format & PFR_SUBGLYPH_XSCALE )
633       {
634         PFR_CHECK( 2 );
635         subglyph->x_scale = PFR_NEXT_SHORT( p ) << 4;
636       }
637
638       subglyph->y_scale = 0x10000L;
639       if ( format & PFR_SUBGLYPH_YSCALE )
640       {
641         PFR_CHECK( 2 );
642         subglyph->y_scale = PFR_NEXT_SHORT( p ) << 4;
643       }
644
645       /* read offset */
646       switch ( format & 3 )
647       {
648       case 1:
649         PFR_CHECK( 2 );
650         x_pos = PFR_NEXT_SHORT( p );
651         break;
652
653       case 2:
654         PFR_CHECK( 1 );
655         x_pos += PFR_NEXT_INT8( p );
656         break;
657
658       default:
659         ;
660       }
661
662       switch ( ( format >> 2 ) & 3 )
663       {
664       case 1:
665         PFR_CHECK( 2 );
666         y_pos = PFR_NEXT_SHORT( p );
667         break;
668
669       case 2:
670         PFR_CHECK( 1 );
671         y_pos += PFR_NEXT_INT8( p );
672         break;
673
674       default:
675         ;
676       }
677
678       subglyph->x_delta = x_pos;
679       subglyph->y_delta = y_pos;
680
681       /* read glyph position and size now */
682       if ( format & PFR_SUBGLYPH_2BYTE_SIZE )
683       {
684         PFR_CHECK( 2 );
685         subglyph->gps_size = PFR_NEXT_USHORT( p );
686       }
687       else
688       {
689         PFR_CHECK( 1 );
690         subglyph->gps_size = PFR_NEXT_BYTE( p );
691       }
692
693       if ( format & PFR_SUBGLYPH_3BYTE_OFFSET )
694       {
695         PFR_CHECK( 3 );
696         subglyph->gps_offset = PFR_NEXT_LONG( p );
697       }
698       else
699       {
700         PFR_CHECK( 2 );
701         subglyph->gps_offset = PFR_NEXT_USHORT( p );
702       }
703
704       glyph->num_subs++;
705     }
706
707   Exit:
708     return error;
709
710   Failure:
711   Too_Short:
712     error = PFR_Err_Invalid_Table;
713     FT_ERROR(( "pfr_glyph_load_compound: invalid glyph data\n" ));
714     goto Exit;
715   }
716
717
718   static FT_Error
719   pfr_glyph_load_rec( PFR_Glyph  glyph,
720                       FT_Stream  stream,
721                       FT_ULong   gps_offset,
722                       FT_ULong   offset,
723                       FT_ULong   size )
724   {
725     FT_Error  error;
726     FT_Byte*  p;
727     FT_Byte*  limit;
728
729
730     if ( FT_STREAM_SEEK( gps_offset + offset ) ||
731          FT_FRAME_ENTER( size )                )
732       goto Exit;
733
734     p     = (FT_Byte*)stream->cursor;
735     limit = p + size;
736
737     if ( size > 0 && *p & PFR_GLYPH_IS_COMPOUND )
738     {
739       FT_Int          n, old_count, count;
740       FT_GlyphLoader  loader = glyph->loader;
741       FT_Outline*     base   = &loader->base.outline;
742
743
744       old_count = glyph->num_subs;
745
746       /* this is a compound glyph - load it */
747       error = pfr_glyph_load_compound( glyph, p, limit );
748
749       FT_FRAME_EXIT();
750
751       if ( error )
752         goto Exit;
753
754       count = glyph->num_subs - old_count;
755
756       FT_TRACE4(( "compound glyph with %d elements (offset %lu):\n",
757                   count, offset ));
758
759       /* now, load each individual glyph */
760       for ( n = 0; n < count; n++ )
761       {
762         FT_Int        i, old_points, num_points;
763         PFR_SubGlyph  subglyph;
764
765
766         FT_TRACE4(( "subglyph %d:\n", n ));
767
768         subglyph   = glyph->subs + old_count + n;
769         old_points = base->n_points;
770
771         error = pfr_glyph_load_rec( glyph, stream, gps_offset,
772                                     subglyph->gps_offset,
773                                     subglyph->gps_size );
774         if ( error )
775           break;
776
777         /* note that `glyph->subs' might have been re-allocated */
778         subglyph   = glyph->subs + old_count + n;
779         num_points = base->n_points - old_points;
780
781         /* translate and eventually scale the new glyph points */
782         if ( subglyph->x_scale != 0x10000L || subglyph->y_scale != 0x10000L )
783         {
784           FT_Vector*  vec = base->points + old_points;
785
786
787           for ( i = 0; i < num_points; i++, vec++ )
788           {
789             vec->x = FT_MulFix( vec->x, subglyph->x_scale ) +
790                        subglyph->x_delta;
791             vec->y = FT_MulFix( vec->y, subglyph->y_scale ) +
792                        subglyph->y_delta;
793           }
794         }
795         else
796         {
797           FT_Vector*  vec = loader->base.outline.points + old_points;
798
799
800           for ( i = 0; i < num_points; i++, vec++ )
801           {
802             vec->x += subglyph->x_delta;
803             vec->y += subglyph->y_delta;
804           }
805         }
806
807         /* proceed to next sub-glyph */
808       }
809
810       FT_TRACE4(( "end compound glyph with %d elements\n", count ));
811     }
812     else
813     {
814       FT_TRACE4(( "simple glyph (offset %lu)\n", offset ));
815
816       /* load a simple glyph */
817       error = pfr_glyph_load_simple( glyph, p, limit );
818
819       FT_FRAME_EXIT();
820     }
821
822   Exit:
823     return error;
824   }
825
826
827   FT_LOCAL_DEF( FT_Error )
828   pfr_glyph_load( PFR_Glyph  glyph,
829                   FT_Stream  stream,
830                   FT_ULong   gps_offset,
831                   FT_ULong   offset,
832                   FT_ULong   size )
833   {
834     /* initialize glyph loader */
835     FT_GlyphLoader_Rewind( glyph->loader );
836
837     glyph->num_subs = 0;
838
839     /* load the glyph, recursively when needed */
840     return pfr_glyph_load_rec( glyph, stream, gps_offset, offset, size );
841   }
842
843
844 /* END */