Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  1. /***************************************************************************/
  2. /*                                                                         */
  3. /*  ftbbox.c                                                               */
  4. /*                                                                         */
  5. /*    FreeType bbox computation (body).                                    */
  6. /*                                                                         */
  7. /*  Copyright 1996-2002, 2004, 2006, 2010, 2013 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.   /*************************************************************************/
  20.   /*                                                                       */
  21.   /* This component has a _single_ role: to compute exact outline bounding */
  22.   /* boxes.                                                                */
  23.   /*                                                                       */
  24.   /*************************************************************************/
  25.  
  26.  
  27. #include <ft2build.h>
  28. #include FT_INTERNAL_DEBUG_H
  29.  
  30. #include FT_BBOX_H
  31. #include FT_IMAGE_H
  32. #include FT_OUTLINE_H
  33. #include FT_INTERNAL_CALC_H
  34. #include FT_INTERNAL_OBJECTS_H
  35.  
  36.  
  37.   typedef struct  TBBox_Rec_
  38.   {
  39.     FT_Vector  last;
  40.     FT_BBox    bbox;
  41.  
  42.   } TBBox_Rec;
  43.  
  44.  
  45.   /*************************************************************************/
  46.   /*                                                                       */
  47.   /* <Function>                                                            */
  48.   /*    BBox_Move_To                                                       */
  49.   /*                                                                       */
  50.   /* <Description>                                                         */
  51.   /*    This function is used as a `move_to' and `line_to' emitter during  */
  52.   /*    FT_Outline_Decompose().  It simply records the destination point   */
  53.   /*    in `user->last'; no further computations are necessary since we    */
  54.   /*    use the cbox as the starting bbox which must be refined.           */
  55.   /*                                                                       */
  56.   /* <Input>                                                               */
  57.   /*    to   :: A pointer to the destination vector.                       */
  58.   /*                                                                       */
  59.   /* <InOut>                                                               */
  60.   /*    user :: A pointer to the current walk context.                     */
  61.   /*                                                                       */
  62.   /* <Return>                                                              */
  63.   /*    Always 0.  Needed for the interface only.                          */
  64.   /*                                                                       */
  65.   static int
  66.   BBox_Move_To( FT_Vector*  to,
  67.                 TBBox_Rec*  user )
  68.   {
  69.     user->last = *to;
  70.  
  71.     return 0;
  72.   }
  73.  
  74.  
  75. #define CHECK_X( p, bbox )  \
  76.           ( p->x < bbox.xMin || p->x > bbox.xMax )
  77.  
  78. #define CHECK_Y( p, bbox )  \
  79.           ( p->y < bbox.yMin || p->y > bbox.yMax )
  80.  
  81.  
  82.   /*************************************************************************/
  83.   /*                                                                       */
  84.   /* <Function>                                                            */
  85.   /*    BBox_Conic_Check                                                   */
  86.   /*                                                                       */
  87.   /* <Description>                                                         */
  88.   /*    Finds the extrema of a 1-dimensional conic Bezier curve and update */
  89.   /*    a bounding range.  This version uses direct computation, as it     */
  90.   /*    doesn't need square roots.                                         */
  91.   /*                                                                       */
  92.   /* <Input>                                                               */
  93.   /*    y1  :: The start coordinate.                                       */
  94.   /*                                                                       */
  95.   /*    y2  :: The coordinate of the control point.                        */
  96.   /*                                                                       */
  97.   /*    y3  :: The end coordinate.                                         */
  98.   /*                                                                       */
  99.   /* <InOut>                                                               */
  100.   /*    min :: The address of the current minimum.                         */
  101.   /*                                                                       */
  102.   /*    max :: The address of the current maximum.                         */
  103.   /*                                                                       */
  104.   static void
  105.   BBox_Conic_Check( FT_Pos   y1,
  106.                     FT_Pos   y2,
  107.                     FT_Pos   y3,
  108.                     FT_Pos*  min,
  109.                     FT_Pos*  max )
  110.   {
  111.     if ( y1 <= y3 && y2 == y1 )     /* flat arc */
  112.       goto Suite;
  113.  
  114.     if ( y1 < y3 )
  115.     {
  116.       if ( y2 >= y1 && y2 <= y3 )   /* ascending arc */
  117.         goto Suite;
  118.     }
  119.     else
  120.     {
  121.       if ( y2 >= y3 && y2 <= y1 )   /* descending arc */
  122.       {
  123.         y2 = y1;
  124.         y1 = y3;
  125.         y3 = y2;
  126.         goto Suite;
  127.       }
  128.     }
  129.  
  130.     y1 = y3 = y1 - FT_MulDiv( y2 - y1, y2 - y1, y1 - 2*y2 + y3 );
  131.  
  132.   Suite:
  133.     if ( y1 < *min ) *min = y1;
  134.     if ( y3 > *max ) *max = y3;
  135.   }
  136.  
  137.  
  138.   /*************************************************************************/
  139.   /*                                                                       */
  140.   /* <Function>                                                            */
  141.   /*    BBox_Conic_To                                                      */
  142.   /*                                                                       */
  143.   /* <Description>                                                         */
  144.   /*    This function is used as a `conic_to' emitter during               */
  145.   /*    FT_Outline_Decompose().  It checks a conic Bezier curve with the   */
  146.   /*    current bounding box, and computes its extrema if necessary to     */
  147.   /*    update it.                                                         */
  148.   /*                                                                       */
  149.   /* <Input>                                                               */
  150.   /*    control :: A pointer to a control point.                           */
  151.   /*                                                                       */
  152.   /*    to      :: A pointer to the destination vector.                    */
  153.   /*                                                                       */
  154.   /* <InOut>                                                               */
  155.   /*    user    :: The address of the current walk context.                */
  156.   /*                                                                       */
  157.   /* <Return>                                                              */
  158.   /*    Always 0.  Needed for the interface only.                          */
  159.   /*                                                                       */
  160.   /* <Note>                                                                */
  161.   /*    In the case of a non-monotonous arc, we compute directly the       */
  162.   /*    extremum coordinates, as it is sufficiently fast.                  */
  163.   /*                                                                       */
  164.   static int
  165.   BBox_Conic_To( FT_Vector*  control,
  166.                  FT_Vector*  to,
  167.                  TBBox_Rec*  user )
  168.   {
  169.     /* we don't need to check `to' since it is always an `on' point, thus */
  170.     /* within the bbox                                                    */
  171.  
  172.     if ( CHECK_X( control, user->bbox ) )
  173.       BBox_Conic_Check( user->last.x,
  174.                         control->x,
  175.                         to->x,
  176.                         &user->bbox.xMin,
  177.                         &user->bbox.xMax );
  178.  
  179.     if ( CHECK_Y( control, user->bbox ) )
  180.       BBox_Conic_Check( user->last.y,
  181.                         control->y,
  182.                         to->y,
  183.                         &user->bbox.yMin,
  184.                         &user->bbox.yMax );
  185.  
  186.     user->last = *to;
  187.  
  188.     return 0;
  189.   }
  190.  
  191.  
  192.   /*************************************************************************/
  193.   /*                                                                       */
  194.   /* <Function>                                                            */
  195.   /*    BBox_Cubic_Check                                                   */
  196.   /*                                                                       */
  197.   /* <Description>                                                         */
  198.   /*    Finds the extrema of a 1-dimensional cubic Bezier curve and        */
  199.   /*    updates a bounding range.  This version uses splitting because we  */
  200.   /*    don't want to use square roots and extra accuracy.                 */
  201.   /*                                                                       */
  202.   /* <Input>                                                               */
  203.   /*    p1  :: The start coordinate.                                       */
  204.   /*                                                                       */
  205.   /*    p2  :: The coordinate of the first control point.                  */
  206.   /*                                                                       */
  207.   /*    p3  :: The coordinate of the second control point.                 */
  208.   /*                                                                       */
  209.   /*    p4  :: The end coordinate.                                         */
  210.   /*                                                                       */
  211.   /* <InOut>                                                               */
  212.   /*    min :: The address of the current minimum.                         */
  213.   /*                                                                       */
  214.   /*    max :: The address of the current maximum.                         */
  215.   /*                                                                       */
  216.  
  217. #if 0
  218.  
  219.   static void
  220.   BBox_Cubic_Check( FT_Pos   p1,
  221.                     FT_Pos   p2,
  222.                     FT_Pos   p3,
  223.                     FT_Pos   p4,
  224.                     FT_Pos*  min,
  225.                     FT_Pos*  max )
  226.   {
  227.     FT_Pos  q1, q2, q3, q4;
  228.  
  229.  
  230.     q1 = p1;
  231.     q2 = p2;
  232.     q3 = p3;
  233.     q4 = p4;
  234.  
  235.     /* for a conic segment to possibly reach new maximum     */
  236.     /* one of its off-points must be above the current value */
  237.     while ( q2 > *max || q3 > *max )
  238.     {
  239.       /* determine which half contains the maximum and split */
  240.       if ( q1 + q2 > q3 + q4 ) /* first half */
  241.       {
  242.         q4 = q4 + q3;
  243.         q3 = q3 + q2;
  244.         q2 = q2 + q1;
  245.         q4 = q4 + q3;
  246.         q3 = q3 + q2;
  247.         q4 = ( q4 + q3 ) / 8;
  248.         q3 = q3 / 4;
  249.         q2 = q2 / 2;
  250.       }
  251.       else                     /* second half */
  252.       {
  253.         q1 = q1 + q2;
  254.         q2 = q2 + q3;
  255.         q3 = q3 + q4;
  256.         q1 = q1 + q2;
  257.         q2 = q2 + q3;
  258.         q1 = ( q1 + q2 ) / 8;
  259.         q2 = q2 / 4;
  260.         q3 = q3 / 2;
  261.       }
  262.  
  263.       /* check if either end reached the maximum */
  264.       if ( q1 == q2 && q1 >= q3 )
  265.       {
  266.         *max = q1;
  267.         break;
  268.       }
  269.       if ( q3 == q4 && q2 <= q4 )
  270.       {
  271.         *max = q4;
  272.         break;
  273.       }
  274.     }
  275.  
  276.     q1 = p1;
  277.     q2 = p2;
  278.     q3 = p3;
  279.     q4 = p4;
  280.  
  281.     /* for a conic segment to possibly reach new minimum     */
  282.     /* one of its off-points must be below the current value */
  283.     while ( q2 < *min || q3 < *min )
  284.     {
  285.       /* determine which half contains the minimum and split */
  286.       if ( q1 + q2 < q3 + q4 ) /* first half */
  287.       {
  288.         q4 = q4 + q3;
  289.         q3 = q3 + q2;
  290.         q2 = q2 + q1;
  291.         q4 = q4 + q3;
  292.         q3 = q3 + q2;
  293.         q4 = ( q4 + q3 ) / 8;
  294.         q3 = q3 / 4;
  295.         q2 = q2 / 2;
  296.       }
  297.       else                     /* second half */
  298.       {
  299.         q1 = q1 + q2;
  300.         q2 = q2 + q3;
  301.         q3 = q3 + q4;
  302.         q1 = q1 + q2;
  303.         q2 = q2 + q3;
  304.         q1 = ( q1 + q2 ) / 8;
  305.         q2 = q2 / 4;
  306.         q3 = q3 / 2;
  307.       }
  308.  
  309.       /* check if either end reached the minimum */
  310.       if ( q1 == q2 && q1 <= q3 )
  311.       {
  312.         *min = q1;
  313.         break;
  314.       }
  315.       if ( q3 == q4 && q2 >= q4 )
  316.       {
  317.         *min = q4;
  318.         break;
  319.       }
  320.     }
  321.   }
  322.  
  323. #else
  324.  
  325.   static void
  326.   test_cubic_extrema( FT_Pos    y1,
  327.                       FT_Pos    y2,
  328.                       FT_Pos    y3,
  329.                       FT_Pos    y4,
  330.                       FT_Fixed  u,
  331.                       FT_Pos*   min,
  332.                       FT_Pos*   max )
  333.   {
  334.  /* FT_Pos    a = y4 - 3*y3 + 3*y2 - y1; */
  335.     FT_Pos    b = y3 - 2*y2 + y1;
  336.     FT_Pos    c = y2 - y1;
  337.     FT_Pos    d = y1;
  338.     FT_Pos    y;
  339.     FT_Fixed  uu;
  340.  
  341.     FT_UNUSED ( y4 );
  342.  
  343.  
  344.     /* The polynomial is                      */
  345.     /*                                        */
  346.     /*    P(x) = a*x^3 + 3b*x^2 + 3c*x + d  , */
  347.     /*                                        */
  348.     /*   dP/dx = 3a*x^2 + 6b*x + 3c         . */
  349.     /*                                        */
  350.     /* However, we also have                  */
  351.     /*                                        */
  352.     /*   dP/dx(u) = 0                       , */
  353.     /*                                        */
  354.     /* which implies by subtraction that      */
  355.     /*                                        */
  356.     /*   P(u) = b*u^2 + 2c*u + d            . */
  357.  
  358.     if ( u > 0 && u < 0x10000L )
  359.     {
  360.       uu = FT_MulFix( u, u );
  361.       y  = d + FT_MulFix( c, 2*u ) + FT_MulFix( b, uu );
  362.  
  363.       if ( y < *min ) *min = y;
  364.       if ( y > *max ) *max = y;
  365.     }
  366.   }
  367.  
  368.  
  369.   static void
  370.   BBox_Cubic_Check( FT_Pos   y1,
  371.                     FT_Pos   y2,
  372.                     FT_Pos   y3,
  373.                     FT_Pos   y4,
  374.                     FT_Pos*  min,
  375.                     FT_Pos*  max )
  376.   {
  377.     /* always compare first and last points */
  378.     if      ( y1 < *min )  *min = y1;
  379.     else if ( y1 > *max )  *max = y1;
  380.  
  381.     if      ( y4 < *min )  *min = y4;
  382.     else if ( y4 > *max )  *max = y4;
  383.  
  384.     /* now, try to see if there are split points here */
  385.     if ( y1 <= y4 )
  386.     {
  387.       /* flat or ascending arc test */
  388.       if ( y1 <= y2 && y2 <= y4 && y1 <= y3 && y3 <= y4 )
  389.         return;
  390.     }
  391.     else /* y1 > y4 */
  392.     {
  393.       /* descending arc test */
  394.       if ( y1 >= y2 && y2 >= y4 && y1 >= y3 && y3 >= y4 )
  395.         return;
  396.     }
  397.  
  398.     /* There are some split points.  Find them.                        */
  399.     /* We already made sure that a, b, and c below cannot be all zero. */
  400.     {
  401.       FT_Pos    a = y4 - 3*y3 + 3*y2 - y1;
  402.       FT_Pos    b = y3 - 2*y2 + y1;
  403.       FT_Pos    c = y2 - y1;
  404.       FT_Pos    d;
  405.       FT_Fixed  t;
  406.       FT_Int    shift;
  407.  
  408.  
  409.       /* We need to solve `ax^2+2bx+c' here, without floating points!      */
  410.       /* The trick is to normalize to a different representation in order  */
  411.       /* to use our 16.16 fixed-point routines.                            */
  412.       /*                                                                   */
  413.       /* We compute FT_MulFix(b,b) and FT_MulFix(a,c) after normalization. */
  414.       /* These values must fit into a single 16.16 value.                  */
  415.       /*                                                                   */
  416.       /* We normalize a, b, and c to `8.16' fixed-point values to ensure   */
  417.       /* that their product is held in a `16.16' value including the sign. */
  418.       /* Necessarily, we need to shift `a', `b', and `c' so that the most  */
  419.       /* significant bit of their absolute values is at position 22.       */
  420.       /*                                                                   */
  421.       /* This also means that we are using 23 bits of precision to compute */
  422.       /* the zeros, independently of the range of the original polynomial  */
  423.       /* coefficients.                                                     */
  424.       /*                                                                   */
  425.       /* This algorithm should ensure reasonably accurate values for the   */
  426.       /* zeros.  Note that they are only expressed with 16 bits when       */
  427.       /* computing the extrema (the zeros need to be in 0..1 exclusive     */
  428.       /* to be considered part of the arc).                                */
  429.  
  430.       shift = FT_MSB( FT_ABS( a ) | FT_ABS( b ) | FT_ABS( c ) );
  431.  
  432.       if ( shift > 22 )
  433.       {
  434.         shift -= 22;
  435.  
  436.         /* this loses some bits of precision, but we use 23 of them */
  437.         /* for the computation anyway                               */
  438.         a >>= shift;
  439.         b >>= shift;
  440.         c >>= shift;
  441.       }
  442.       else
  443.       {
  444.         shift = 22 - shift;
  445.  
  446.         a <<= shift;
  447.         b <<= shift;
  448.         c <<= shift;
  449.       }
  450.  
  451.       /* handle a == 0 */
  452.       if ( a == 0 )
  453.       {
  454.         if ( b != 0 )
  455.         {
  456.           t = - FT_DivFix( c, b ) / 2;
  457.           test_cubic_extrema( y1, y2, y3, y4, t, min, max );
  458.         }
  459.       }
  460.       else
  461.       {
  462.         /* solve the equation now */
  463.         d = FT_MulFix( b, b ) - FT_MulFix( a, c );
  464.         if ( d < 0 )
  465.           return;
  466.  
  467.         if ( d == 0 )
  468.         {
  469.           /* there is a single split point at -b/a */
  470.           t = - FT_DivFix( b, a );
  471.           test_cubic_extrema( y1, y2, y3, y4, t, min, max );
  472.         }
  473.         else
  474.         {
  475.           /* there are two solutions; we need to filter them */
  476.           d = FT_SqrtFixed( (FT_Int32)d );
  477.           t = - FT_DivFix( b - d, a );
  478.           test_cubic_extrema( y1, y2, y3, y4, t, min, max );
  479.  
  480.           t = - FT_DivFix( b + d, a );
  481.           test_cubic_extrema( y1, y2, y3, y4, t, min, max );
  482.         }
  483.       }
  484.     }
  485.   }
  486.  
  487. #endif
  488.  
  489.  
  490.   /*************************************************************************/
  491.   /*                                                                       */
  492.   /* <Function>                                                            */
  493.   /*    BBox_Cubic_To                                                      */
  494.   /*                                                                       */
  495.   /* <Description>                                                         */
  496.   /*    This function is used as a `cubic_to' emitter during               */
  497.   /*    FT_Outline_Decompose().  It checks a cubic Bezier curve with the   */
  498.   /*    current bounding box, and computes its extrema if necessary to     */
  499.   /*    update it.                                                         */
  500.   /*                                                                       */
  501.   /* <Input>                                                               */
  502.   /*    control1 :: A pointer to the first control point.                  */
  503.   /*                                                                       */
  504.   /*    control2 :: A pointer to the second control point.                 */
  505.   /*                                                                       */
  506.   /*    to       :: A pointer to the destination vector.                   */
  507.   /*                                                                       */
  508.   /* <InOut>                                                               */
  509.   /*    user     :: The address of the current walk context.               */
  510.   /*                                                                       */
  511.   /* <Return>                                                              */
  512.   /*    Always 0.  Needed for the interface only.                          */
  513.   /*                                                                       */
  514.   /* <Note>                                                                */
  515.   /*    In the case of a non-monotonous arc, we don't compute directly     */
  516.   /*    extremum coordinates, we subdivide instead.                        */
  517.   /*                                                                       */
  518.   static int
  519.   BBox_Cubic_To( FT_Vector*  control1,
  520.                  FT_Vector*  control2,
  521.                  FT_Vector*  to,
  522.                  TBBox_Rec*  user )
  523.   {
  524.     /* we don't need to check `to' since it is always an `on' point, thus */
  525.     /* within the bbox                                                    */
  526.  
  527.     if ( CHECK_X( control1, user->bbox ) ||
  528.          CHECK_X( control2, user->bbox ) )
  529.       BBox_Cubic_Check( user->last.x,
  530.                         control1->x,
  531.                         control2->x,
  532.                         to->x,
  533.                         &user->bbox.xMin,
  534.                         &user->bbox.xMax );
  535.  
  536.     if ( CHECK_Y( control1, user->bbox ) ||
  537.          CHECK_Y( control2, user->bbox ) )
  538.       BBox_Cubic_Check( user->last.y,
  539.                         control1->y,
  540.                         control2->y,
  541.                         to->y,
  542.                         &user->bbox.yMin,
  543.                         &user->bbox.yMax );
  544.  
  545.     user->last = *to;
  546.  
  547.     return 0;
  548.   }
  549.  
  550. FT_DEFINE_OUTLINE_FUNCS(bbox_interface,
  551.     (FT_Outline_MoveTo_Func) BBox_Move_To,
  552.     (FT_Outline_LineTo_Func) BBox_Move_To,
  553.     (FT_Outline_ConicTo_Func)BBox_Conic_To,
  554.     (FT_Outline_CubicTo_Func)BBox_Cubic_To,
  555.     0, 0
  556.   )
  557.  
  558.   /* documentation is in ftbbox.h */
  559.  
  560.   FT_EXPORT_DEF( FT_Error )
  561.   FT_Outline_Get_BBox( FT_Outline*  outline,
  562.                        FT_BBox     *abbox )
  563.   {
  564.     FT_BBox     cbox;
  565.     FT_BBox     bbox;
  566.     FT_Vector*  vec;
  567.     FT_UShort   n;
  568.  
  569.  
  570.     if ( !abbox )
  571.       return FT_THROW( Invalid_Argument );
  572.  
  573.     if ( !outline )
  574.       return FT_THROW( Invalid_Outline );
  575.  
  576.     /* if outline is empty, return (0,0,0,0) */
  577.     if ( outline->n_points == 0 || outline->n_contours <= 0 )
  578.     {
  579.       abbox->xMin = abbox->xMax = 0;
  580.       abbox->yMin = abbox->yMax = 0;
  581.       return 0;
  582.     }
  583.  
  584.     /* We compute the control box as well as the bounding box of  */
  585.     /* all `on' points in the outline.  Then, if the two boxes    */
  586.     /* coincide, we exit immediately.                             */
  587.  
  588.     vec = outline->points;
  589.     bbox.xMin = bbox.xMax = cbox.xMin = cbox.xMax = vec->x;
  590.     bbox.yMin = bbox.yMax = cbox.yMin = cbox.yMax = vec->y;
  591.     vec++;
  592.  
  593.     for ( n = 1; n < outline->n_points; n++ )
  594.     {
  595.       FT_Pos  x = vec->x;
  596.       FT_Pos  y = vec->y;
  597.  
  598.  
  599.       /* update control box */
  600.       if ( x < cbox.xMin ) cbox.xMin = x;
  601.       if ( x > cbox.xMax ) cbox.xMax = x;
  602.  
  603.       if ( y < cbox.yMin ) cbox.yMin = y;
  604.       if ( y > cbox.yMax ) cbox.yMax = y;
  605.  
  606.       if ( FT_CURVE_TAG( outline->tags[n] ) == FT_CURVE_TAG_ON )
  607.       {
  608.         /* update bbox for `on' points only */
  609.         if ( x < bbox.xMin ) bbox.xMin = x;
  610.         if ( x > bbox.xMax ) bbox.xMax = x;
  611.  
  612.         if ( y < bbox.yMin ) bbox.yMin = y;
  613.         if ( y > bbox.yMax ) bbox.yMax = y;
  614.       }
  615.  
  616.       vec++;
  617.     }
  618.  
  619.     /* test two boxes for equality */
  620.     if ( cbox.xMin < bbox.xMin || cbox.xMax > bbox.xMax ||
  621.          cbox.yMin < bbox.yMin || cbox.yMax > bbox.yMax )
  622.     {
  623.       /* the two boxes are different, now walk over the outline to */
  624.       /* get the Bezier arc extrema.                               */
  625.  
  626.       FT_Error   error;
  627.       TBBox_Rec  user;
  628.  
  629. #ifdef FT_CONFIG_OPTION_PIC
  630.       FT_Outline_Funcs bbox_interface;
  631.       Init_Class_bbox_interface(&bbox_interface);
  632. #endif
  633.  
  634.       user.bbox = bbox;
  635.  
  636.       error = FT_Outline_Decompose( outline, &bbox_interface, &user );
  637.       if ( error )
  638.         return error;
  639.  
  640.       *abbox = user.bbox;
  641.     }
  642.     else
  643.       *abbox = bbox;
  644.  
  645.     return FT_Err_Ok;
  646.   }
  647.  
  648.  
  649. /* END */
  650.