Edit

kc3-lang/freetype/src/base/ftstroke.c

Branch :

  • Show log

    Commit

  • Author : Ben Wagner
    Date : 2024-12-16 14:29:36
    Hash : 38272bf8
    Message : [ftstroke] Fix invalid pointer assignement to `arc` In `FT_Stroker_ConicTo` and `FT_Stroker_CubicTo` there is a `bez_stack`. `arc` is initialized with `arc = bez_stack` and is never set to point into any different object. The main loop looks like `while ( arc >= bez_stack )` which is depending on a later `arc -= 2` (or `arc -= 3`) to make `arc` point to before `bez_stack`. However, using pointer subtraction to make `arc` point outside the array is undefined behavior, and attempting to use the value in the loop predicate is "very" undefined behavior. (C99 "Additive operators" 6.5.6.8.) This particular undefined behavior was discovered as either hangs or MemorySantizer issues after "[InstCombine] Infer nuw for gep inbounds from base of object" [0]. With this change, clang can infer that `arc` must always point into the `bez_stack` object and therefore cannot be at a "negative index" so the predicate is always true. [0] https://github.com/llvm/llvm-project/commit/e21ab4d16b555c28ded307571d138f594f33e325 * src/base/ftstroke.c (FT_Stroker_ConicTo, FT_Stroker_CubicTo): test loop exit condition (there are no more arcs to process) before decrementing `arc` Fixes: #1307

  • src/base/ftstroke.c
  • /****************************************************************************
     *
     * ftstroke.c
     *
     *   FreeType path stroker (body).
     *
     * Copyright (C) 2002-2024 by
     * David Turner, Robert Wilhelm, and Werner Lemberg.
     *
     * This file is part of the FreeType project, and may only be used,
     * modified, and distributed under the terms of the FreeType project
     * license, LICENSE.TXT.  By continuing to use, modify, or distribute
     * this file you indicate that you have read the license and
     * understand and accept it fully.
     *
     */
    
    
    #include <freetype/ftstroke.h>
    #include <freetype/fttrigon.h>
    #include <freetype/ftoutln.h>
    #include <freetype/internal/ftmemory.h>
    #include <freetype/internal/ftdebug.h>
    #include <freetype/internal/ftobjs.h>
    
    
      /* declare an extern to access `ft_outline_glyph_class' globally */
      /* allocated  in `ftglyph.c'                                     */
      FT_CALLBACK_TABLE const FT_Glyph_Class  ft_outline_glyph_class;
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_StrokerBorder )
      FT_Outline_GetInsideBorder( FT_Outline*  outline )
      {
        FT_Orientation  o = FT_Outline_Get_Orientation( outline );
    
    
        return o == FT_ORIENTATION_TRUETYPE ? FT_STROKER_BORDER_RIGHT
                                            : FT_STROKER_BORDER_LEFT;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_StrokerBorder )
      FT_Outline_GetOutsideBorder( FT_Outline*  outline )
      {
        FT_Orientation  o = FT_Outline_Get_Orientation( outline );
    
    
        return o == FT_ORIENTATION_TRUETYPE ? FT_STROKER_BORDER_LEFT
                                            : FT_STROKER_BORDER_RIGHT;
      }
    
    
      /*************************************************************************/
      /*************************************************************************/
      /*****                                                               *****/
      /*****                      BEZIER COMPUTATIONS                      *****/
      /*****                                                               *****/
      /*************************************************************************/
      /*************************************************************************/
    
    #define FT_SMALL_CONIC_THRESHOLD  ( FT_ANGLE_PI / 6 )
    #define FT_SMALL_CUBIC_THRESHOLD  ( FT_ANGLE_PI / 8 )
    
    #define FT_EPSILON  2
    
    #define FT_IS_SMALL( x )  ( (x) > -FT_EPSILON && (x) < FT_EPSILON )
    
    
      static FT_Pos
      ft_pos_abs( FT_Pos  x )
      {
        return x >= 0 ? x : -x;
      }
    
    
      static void
      ft_conic_split( FT_Vector*  base )
      {
        FT_Pos  a, b;
    
    
        base[4].x = base[2].x;
        a = base[0].x + base[1].x;
        b = base[1].x + base[2].x;
        base[3].x = b >> 1;
        base[2].x = ( a + b ) >> 2;
        base[1].x = a >> 1;
    
        base[4].y = base[2].y;
        a = base[0].y + base[1].y;
        b = base[1].y + base[2].y;
        base[3].y = b >> 1;
        base[2].y = ( a + b ) >> 2;
        base[1].y = a >> 1;
      }
    
    
      static FT_Bool
      ft_conic_is_small_enough( FT_Vector*  base,
                                FT_Angle   *angle_in,
                                FT_Angle   *angle_out )
      {
        FT_Vector  d1, d2;
        FT_Angle   theta;
        FT_Int     close1, close2;
    
    
        d1.x = base[1].x - base[2].x;
        d1.y = base[1].y - base[2].y;
        d2.x = base[0].x - base[1].x;
        d2.y = base[0].y - base[1].y;
    
        close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y );
        close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y );
    
        if ( close1 )
        {
          if ( close2 )
          {
            /* basically a point;                      */
            /* do nothing to retain original direction */
          }
          else
          {
            *angle_in  =
            *angle_out = FT_Atan2( d2.x, d2.y );
          }
        }
        else /* !close1 */
        {
          if ( close2 )
          {
            *angle_in  =
            *angle_out = FT_Atan2( d1.x, d1.y );
          }
          else
          {
            *angle_in  = FT_Atan2( d1.x, d1.y );
            *angle_out = FT_Atan2( d2.x, d2.y );
          }
        }
    
        theta = ft_pos_abs( FT_Angle_Diff( *angle_in, *angle_out ) );
    
        return FT_BOOL( theta < FT_SMALL_CONIC_THRESHOLD );
      }
    
    
      static void
      ft_cubic_split( FT_Vector*  base )
      {
        FT_Pos  a, b, c;
    
    
        base[6].x = base[3].x;
        a = base[0].x + base[1].x;
        b = base[1].x + base[2].x;
        c = base[2].x + base[3].x;
        base[5].x = c >> 1;
        c += b;
        base[4].x = c >> 2;
        base[1].x = a >> 1;
        a += b;
        base[2].x = a >> 2;
        base[3].x = ( a + c ) >> 3;
    
        base[6].y = base[3].y;
        a = base[0].y + base[1].y;
        b = base[1].y + base[2].y;
        c = base[2].y + base[3].y;
        base[5].y = c >> 1;
        c += b;
        base[4].y = c >> 2;
        base[1].y = a >> 1;
        a += b;
        base[2].y = a >> 2;
        base[3].y = ( a + c ) >> 3;
      }
    
    
      /* Return the average of `angle1' and `angle2'.            */
      /* This gives correct result even if `angle1' and `angle2' */
      /* have opposite signs.                                    */
      static FT_Angle
      ft_angle_mean( FT_Angle  angle1,
                     FT_Angle  angle2 )
      {
        return angle1 + FT_Angle_Diff( angle1, angle2 ) / 2;
      }
    
    
      static FT_Bool
      ft_cubic_is_small_enough( FT_Vector*  base,
                                FT_Angle   *angle_in,
                                FT_Angle   *angle_mid,
                                FT_Angle   *angle_out )
      {
        FT_Vector  d1, d2, d3;
        FT_Angle   theta1, theta2;
        FT_Int     close1, close2, close3;
    
    
        d1.x = base[2].x - base[3].x;
        d1.y = base[2].y - base[3].y;
        d2.x = base[1].x - base[2].x;
        d2.y = base[1].y - base[2].y;
        d3.x = base[0].x - base[1].x;
        d3.y = base[0].y - base[1].y;
    
        close1 = FT_IS_SMALL( d1.x ) && FT_IS_SMALL( d1.y );
        close2 = FT_IS_SMALL( d2.x ) && FT_IS_SMALL( d2.y );
        close3 = FT_IS_SMALL( d3.x ) && FT_IS_SMALL( d3.y );
    
        if ( close1 )
        {
          if ( close2 )
          {
            if ( close3 )
            {
              /* basically a point;                      */
              /* do nothing to retain original direction */
            }
            else /* !close3 */
            {
              *angle_in  =
              *angle_mid =
              *angle_out = FT_Atan2( d3.x, d3.y );
            }
          }
          else /* !close2 */
          {
            if ( close3 )
            {
              *angle_in  =
              *angle_mid =
              *angle_out = FT_Atan2( d2.x, d2.y );
            }
            else /* !close3 */
            {
              *angle_in  =
              *angle_mid = FT_Atan2( d2.x, d2.y );
              *angle_out = FT_Atan2( d3.x, d3.y );
            }
          }
        }
        else /* !close1 */
        {
          if ( close2 )
          {
            if ( close3 )
            {
              *angle_in  =
              *angle_mid =
              *angle_out = FT_Atan2( d1.x, d1.y );
            }
            else /* !close3 */
            {
              *angle_in  = FT_Atan2( d1.x, d1.y );
              *angle_out = FT_Atan2( d3.x, d3.y );
              *angle_mid = ft_angle_mean( *angle_in, *angle_out );
            }
          }
          else /* !close2 */
          {
            if ( close3 )
            {
              *angle_in  = FT_Atan2( d1.x, d1.y );
              *angle_mid =
              *angle_out = FT_Atan2( d2.x, d2.y );
            }
            else /* !close3 */
            {
              *angle_in  = FT_Atan2( d1.x, d1.y );
              *angle_mid = FT_Atan2( d2.x, d2.y );
              *angle_out = FT_Atan2( d3.x, d3.y );
            }
          }
        }
    
        theta1 = ft_pos_abs( FT_Angle_Diff( *angle_in,  *angle_mid ) );
        theta2 = ft_pos_abs( FT_Angle_Diff( *angle_mid, *angle_out ) );
    
        return FT_BOOL( theta1 < FT_SMALL_CUBIC_THRESHOLD &&
                        theta2 < FT_SMALL_CUBIC_THRESHOLD );
      }
    
    
      /*************************************************************************/
      /*************************************************************************/
      /*****                                                               *****/
      /*****                       STROKE BORDERS                          *****/
      /*****                                                               *****/
      /*************************************************************************/
      /*************************************************************************/
    
      typedef enum  FT_StrokeTags_
      {
        FT_STROKE_TAG_ON    = 1,   /* on-curve point  */
        FT_STROKE_TAG_CUBIC = 2,   /* cubic off-point */
        FT_STROKE_TAG_BEGIN = 4,   /* sub-path start  */
        FT_STROKE_TAG_END   = 8    /* sub-path end    */
    
      } FT_StrokeTags;
    
    #define  FT_STROKE_TAG_BEGIN_END  ( FT_STROKE_TAG_BEGIN | FT_STROKE_TAG_END )
    
      typedef struct  FT_StrokeBorderRec_
      {
        FT_UInt     num_points;
        FT_UInt     max_points;
        FT_Vector*  points;
        FT_Byte*    tags;
        FT_Bool     movable;  /* TRUE for ends of lineto borders */
        FT_Int      start;    /* index of current sub-path start point */
        FT_Memory   memory;
        FT_Bool     valid;
    
      } FT_StrokeBorderRec, *FT_StrokeBorder;
    
    
      static FT_Error
      ft_stroke_border_grow( FT_StrokeBorder  border,
                             FT_UInt          new_points )
      {
        FT_UInt   old_max = border->max_points;
        FT_UInt   new_max = border->num_points + new_points;
        FT_Error  error   = FT_Err_Ok;
    
    
        if ( new_max > old_max )
        {
          FT_UInt    cur_max = old_max;
          FT_Memory  memory  = border->memory;
    
    
          while ( cur_max < new_max )
            cur_max += ( cur_max >> 1 ) + 16;
    
          if ( FT_RENEW_ARRAY( border->points, old_max, cur_max ) ||
               FT_RENEW_ARRAY( border->tags,   old_max, cur_max ) )
            goto Exit;
    
          border->max_points = cur_max;
        }
    
      Exit:
        return error;
      }
    
    
      static void
      ft_stroke_border_close( FT_StrokeBorder  border,
                              FT_Bool          reverse )
      {
        FT_UInt  start = (FT_UInt)border->start;
        FT_UInt  count = border->num_points;
    
    
        FT_ASSERT( border->start >= 0 );
    
        /* don't record empty paths! */
        if ( count <= start + 1U )
          border->num_points = start;
        else
        {
          /* copy the last point to the start of this sub-path, since */
          /* it contains the `adjusted' starting coordinates          */
          border->num_points    = --count;
          border->points[start] = border->points[count];
          border->tags[start]   = border->tags[count];
    
          if ( reverse )
          {
            /* reverse the points */
            {
              FT_Vector*  vec1 = border->points + start + 1;
              FT_Vector*  vec2 = border->points + count - 1;
    
    
              for ( ; vec1 < vec2; vec1++, vec2-- )
              {
                FT_Vector  tmp;
    
    
                tmp   = *vec1;
                *vec1 = *vec2;
                *vec2 = tmp;
              }
            }
    
            /* then the tags */
            {
              FT_Byte*  tag1 = border->tags + start + 1;
              FT_Byte*  tag2 = border->tags + count - 1;
    
    
              for ( ; tag1 < tag2; tag1++, tag2-- )
              {
                FT_Byte  tmp;
    
    
                tmp   = *tag1;
                *tag1 = *tag2;
                *tag2 = tmp;
              }
            }
          }
    
          border->tags[start    ] |= FT_STROKE_TAG_BEGIN;
          border->tags[count - 1] |= FT_STROKE_TAG_END;
        }
    
        border->start   = -1;
        border->movable = FALSE;
      }
    
    
      static FT_Error
      ft_stroke_border_lineto( FT_StrokeBorder  border,
                               FT_Vector*       to,
                               FT_Bool          movable )
      {
        FT_Error  error = FT_Err_Ok;
    
    
        FT_ASSERT( border->start >= 0 );
    
        if ( border->movable )
        {
          /* move last point */
          border->points[border->num_points - 1] = *to;
        }
        else
        {
          /* don't add zero-length lineto, but always add moveto */
          if ( border->num_points > (FT_UInt)border->start                     &&
               FT_IS_SMALL( border->points[border->num_points - 1].x - to->x ) &&
               FT_IS_SMALL( border->points[border->num_points - 1].y - to->y ) )
            return error;
    
          /* add one point */
          error = ft_stroke_border_grow( border, 1 );
          if ( !error )
          {
            FT_Vector*  vec = border->points + border->num_points;
            FT_Byte*    tag = border->tags   + border->num_points;
    
    
            vec[0] = *to;
            tag[0] = FT_STROKE_TAG_ON;
    
            border->num_points += 1;
          }
        }
        border->movable = movable;
        return error;
      }
    
    
      static FT_Error
      ft_stroke_border_conicto( FT_StrokeBorder  border,
                                FT_Vector*       control,
                                FT_Vector*       to )
      {
        FT_Error  error;
    
    
        FT_ASSERT( border->start >= 0 );
    
        error = ft_stroke_border_grow( border, 2 );
        if ( !error )
        {
          FT_Vector*  vec = border->points + border->num_points;
          FT_Byte*    tag = border->tags   + border->num_points;
    
    
          vec[0] = *control;
          vec[1] = *to;
    
          tag[0] = 0;
          tag[1] = FT_STROKE_TAG_ON;
    
          border->num_points += 2;
        }
    
        border->movable = FALSE;
    
        return error;
      }
    
    
      static FT_Error
      ft_stroke_border_cubicto( FT_StrokeBorder  border,
                                FT_Vector*       control1,
                                FT_Vector*       control2,
                                FT_Vector*       to )
      {
        FT_Error  error;
    
    
        FT_ASSERT( border->start >= 0 );
    
        error = ft_stroke_border_grow( border, 3 );
        if ( !error )
        {
          FT_Vector*  vec = border->points + border->num_points;
          FT_Byte*    tag = border->tags   + border->num_points;
    
    
          vec[0] = *control1;
          vec[1] = *control2;
          vec[2] = *to;
    
          tag[0] = FT_STROKE_TAG_CUBIC;
          tag[1] = FT_STROKE_TAG_CUBIC;
          tag[2] = FT_STROKE_TAG_ON;
    
          border->num_points += 3;
        }
    
        border->movable = FALSE;
    
        return error;
      }
    
    
    #define FT_ARC_CUBIC_ANGLE  ( FT_ANGLE_PI / 2 )
    
    
      static FT_Error
      ft_stroke_border_arcto( FT_StrokeBorder  border,
                              FT_Vector*       center,
                              FT_Fixed         radius,
                              FT_Angle         angle_start,
                              FT_Angle         angle_diff )
      {
        FT_Fixed   coef;
        FT_Vector  a0, a1, a2, a3;
        FT_Int     i, arcs = 1;
        FT_Error   error = FT_Err_Ok;
    
    
        /* number of cubic arcs to draw */
        while (  angle_diff > FT_ARC_CUBIC_ANGLE * arcs ||
                -angle_diff > FT_ARC_CUBIC_ANGLE * arcs )
          arcs++;
    
        /* control tangents */
        coef  = FT_Tan( angle_diff / ( 4 * arcs ) );
        coef += coef / 3;
    
        /* compute start and first control point */
        FT_Vector_From_Polar( &a0, radius, angle_start );
        a1.x = FT_MulFix( -a0.y, coef );
        a1.y = FT_MulFix(  a0.x, coef );
    
        a0.x += center->x;
        a0.y += center->y;
        a1.x += a0.x;
        a1.y += a0.y;
    
        for ( i = 1; i <= arcs; i++ )
        {
          /* compute end and second control point */
          FT_Vector_From_Polar( &a3, radius,
                                angle_start + i * angle_diff / arcs );
          a2.x = FT_MulFix(  a3.y, coef );
          a2.y = FT_MulFix( -a3.x, coef );
    
          a3.x += center->x;
          a3.y += center->y;
          a2.x += a3.x;
          a2.y += a3.y;
    
          /* add cubic arc */
          error = ft_stroke_border_cubicto( border, &a1, &a2, &a3 );
          if ( error )
            break;
    
          /* a0 = a3; */
          a1.x = a3.x - a2.x + a3.x;
          a1.y = a3.y - a2.y + a3.y;
        }
    
        return error;
      }
    
    
      static FT_Error
      ft_stroke_border_moveto( FT_StrokeBorder  border,
                               FT_Vector*       to )
      {
        /* close current open path if any ? */
        if ( border->start >= 0 )
          ft_stroke_border_close( border, FALSE );
    
        border->start = (FT_Int)border->num_points;
        border->movable = FALSE;
    
        return ft_stroke_border_lineto( border, to, FALSE );
      }
    
    
      static void
      ft_stroke_border_init( FT_StrokeBorder  border,
                             FT_Memory        memory )
      {
        border->memory = memory;
        border->points = NULL;
        border->tags   = NULL;
    
        border->num_points = 0;
        border->max_points = 0;
        border->start      = -1;
        border->valid      = FALSE;
      }
    
    
      static void
      ft_stroke_border_reset( FT_StrokeBorder  border )
      {
        border->num_points = 0;
        border->start      = -1;
        border->valid      = FALSE;
      }
    
    
      static void
      ft_stroke_border_done( FT_StrokeBorder  border )
      {
        FT_Memory  memory = border->memory;
    
    
        FT_FREE( border->points );
        FT_FREE( border->tags );
    
        border->num_points = 0;
        border->max_points = 0;
        border->start      = -1;
        border->valid      = FALSE;
      }
    
    
      static FT_Error
      ft_stroke_border_get_counts( FT_StrokeBorder  border,
                                   FT_UInt         *anum_points,
                                   FT_UInt         *anum_contours )
      {
        FT_Error  error        = FT_Err_Ok;
        FT_UInt   num_points   = 0;
        FT_UInt   num_contours = 0;
    
        FT_UInt     count      = border->num_points;
        FT_Vector*  point      = border->points;
        FT_Byte*    tags       = border->tags;
        FT_Int      in_contour = 0;
    
    
        for ( ; count > 0; count--, num_points++, point++, tags++ )
        {
          if ( tags[0] & FT_STROKE_TAG_BEGIN )
          {
            if ( in_contour != 0 )
              goto Fail;
    
            in_contour = 1;
          }
          else if ( in_contour == 0 )
            goto Fail;
    
          if ( tags[0] & FT_STROKE_TAG_END )
          {
            in_contour = 0;
            num_contours++;
          }
        }
    
        if ( in_contour != 0 )
          goto Fail;
    
        border->valid = TRUE;
    
      Exit:
        *anum_points   = num_points;
        *anum_contours = num_contours;
        return error;
    
      Fail:
        num_points   = 0;
        num_contours = 0;
        goto Exit;
      }
    
    
      static void
      ft_stroke_border_export( FT_StrokeBorder  border,
                               FT_Outline*      outline )
      {
        /* copy point locations */
        if ( border->num_points )
          FT_ARRAY_COPY( outline->points + outline->n_points,
                         border->points,
                         border->num_points );
    
        /* copy tags */
        {
          FT_UInt   count = border->num_points;
          FT_Byte*  read  = border->tags;
          FT_Byte*  write = outline->tags + outline->n_points;
    
    
          for ( ; count > 0; count--, read++, write++ )
          {
            if ( *read & FT_STROKE_TAG_ON )
              *write = FT_CURVE_TAG_ON;
            else if ( *read & FT_STROKE_TAG_CUBIC )
              *write = FT_CURVE_TAG_CUBIC;
            else
              *write = FT_CURVE_TAG_CONIC;
          }
        }
    
        /* copy contours */
        {
          FT_UInt     count = border->num_points;
          FT_Byte*    tags  = border->tags;
          FT_UShort*  write = outline->contours + outline->n_contours;
          FT_UShort   idx   = outline->n_points;
    
    
          for ( ; count > 0; count--, tags++, idx++ )
          {
            if ( *tags & FT_STROKE_TAG_END )
            {
              *write++ = idx;
              outline->n_contours++;
            }
          }
        }
    
        outline->n_points += (FT_UShort)border->num_points;
    
        FT_ASSERT( FT_Outline_Check( outline ) == 0 );
      }
    
    
      /*************************************************************************/
      /*************************************************************************/
      /*****                                                               *****/
      /*****                           STROKER                             *****/
      /*****                                                               *****/
      /*************************************************************************/
      /*************************************************************************/
    
    #define FT_SIDE_TO_ROTATE( s )   ( FT_ANGLE_PI2 - (s) * FT_ANGLE_PI )
    
      typedef struct  FT_StrokerRec_
      {
        FT_Angle             angle_in;             /* direction into curr join */
        FT_Angle             angle_out;            /* direction out of join  */
        FT_Vector            center;               /* current position */
        FT_Fixed             line_length;          /* length of last lineto */
        FT_Bool              first_point;          /* is this the start? */
        FT_Bool              subpath_open;         /* is the subpath open? */
        FT_Angle             subpath_angle;        /* subpath start direction */
        FT_Vector            subpath_start;        /* subpath start position */
        FT_Fixed             subpath_line_length;  /* subpath start lineto len */
        FT_Bool              handle_wide_strokes;  /* use wide strokes logic? */
    
        FT_Stroker_LineCap   line_cap;
        FT_Stroker_LineJoin  line_join;
        FT_Stroker_LineJoin  line_join_saved;
        FT_Fixed             miter_limit;
        FT_Fixed             radius;
    
        FT_StrokeBorderRec   borders[2];
        FT_Library           library;
    
      } FT_StrokerRec;
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_New( FT_Library   library,
                      FT_Stroker  *astroker )
      {
        FT_Error    error;           /* assigned in FT_NEW */
        FT_Memory   memory;
        FT_Stroker  stroker = NULL;
    
    
        if ( !library )
          return FT_THROW( Invalid_Library_Handle );
    
        if ( !astroker )
          return FT_THROW( Invalid_Argument );
    
        memory = library->memory;
    
        if ( !FT_NEW( stroker ) )
        {
          stroker->library = library;
    
          ft_stroke_border_init( &stroker->borders[0], memory );
          ft_stroke_border_init( &stroker->borders[1], memory );
        }
    
        *astroker = stroker;
    
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( void )
      FT_Stroker_Set( FT_Stroker           stroker,
                      FT_Fixed             radius,
                      FT_Stroker_LineCap   line_cap,
                      FT_Stroker_LineJoin  line_join,
                      FT_Fixed             miter_limit )
      {
        if ( !stroker )
          return;
    
        stroker->radius      = radius;
        stroker->line_cap    = line_cap;
        stroker->line_join   = line_join;
        stroker->miter_limit = miter_limit;
    
        /* ensure miter limit has sensible value */
        if ( stroker->miter_limit < 0x10000L )
          stroker->miter_limit = 0x10000L;
    
        /* save line join style:                                           */
        /* line join style can be temporarily changed when stroking curves */
        stroker->line_join_saved = line_join;
    
        FT_Stroker_Rewind( stroker );
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( void )
      FT_Stroker_Rewind( FT_Stroker  stroker )
      {
        if ( stroker )
        {
          ft_stroke_border_reset( &stroker->borders[0] );
          ft_stroke_border_reset( &stroker->borders[1] );
        }
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( void )
      FT_Stroker_Done( FT_Stroker  stroker )
      {
        if ( stroker )
        {
          FT_Memory  memory = stroker->library->memory;
    
    
          ft_stroke_border_done( &stroker->borders[0] );
          ft_stroke_border_done( &stroker->borders[1] );
    
          stroker->library = NULL;
          FT_FREE( stroker );
        }
      }
    
    
      /* create a circular arc at a corner or cap */
      static FT_Error
      ft_stroker_arcto( FT_Stroker  stroker,
                        FT_Int      side )
      {
        FT_Angle         total, rotate;
        FT_Fixed         radius = stroker->radius;
        FT_Error         error  = FT_Err_Ok;
        FT_StrokeBorder  border = stroker->borders + side;
    
    
        rotate = FT_SIDE_TO_ROTATE( side );
    
        total = FT_Angle_Diff( stroker->angle_in, stroker->angle_out );
        if ( total == FT_ANGLE_PI )
          total = -rotate * 2;
    
        error = ft_stroke_border_arcto( border,
                                        &stroker->center,
                                        radius,
                                        stroker->angle_in + rotate,
                                        total );
        border->movable = FALSE;
        return error;
      }
    
    
      /* add a cap at the end of an opened path */
      static FT_Error
      ft_stroker_cap( FT_Stroker  stroker,
                      FT_Angle    angle,
                      FT_Int      side )
      {
        FT_Error  error = FT_Err_Ok;
    
    
        if ( stroker->line_cap == FT_STROKER_LINECAP_ROUND )
        {
          /* add a round cap */
          stroker->angle_in  = angle;
          stroker->angle_out = angle + FT_ANGLE_PI;
    
          error = ft_stroker_arcto( stroker, side );
        }
        else
        {
          /* add a square or butt cap */
          FT_Vector        middle, delta;
          FT_Fixed         radius = stroker->radius;
          FT_StrokeBorder  border = stroker->borders + side;
    
    
          /* compute middle point and first angle point */
          FT_Vector_From_Polar( &middle, radius, angle );
          delta.x = side ?  middle.y : -middle.y;
          delta.y = side ? -middle.x :  middle.x;
    
          if ( stroker->line_cap == FT_STROKER_LINECAP_SQUARE )
          {
            middle.x += stroker->center.x;
            middle.y += stroker->center.y;
          }
          else  /* FT_STROKER_LINECAP_BUTT */
          {
            middle.x  = stroker->center.x;
            middle.y  = stroker->center.y;
          }
    
          delta.x  += middle.x;
          delta.y  += middle.y;
    
          error = ft_stroke_border_lineto( border, &delta, FALSE );
          if ( error )
            goto Exit;
    
          /* compute second angle point */
          delta.x = middle.x - delta.x + middle.x;
          delta.y = middle.y - delta.y + middle.y;
    
          error = ft_stroke_border_lineto( border, &delta, FALSE );
        }
    
      Exit:
        return error;
      }
    
    
      /* process an inside corner, i.e. compute intersection */
      static FT_Error
      ft_stroker_inside( FT_Stroker  stroker,
                         FT_Int      side,
                         FT_Fixed    line_length )
      {
        FT_StrokeBorder  border = stroker->borders + side;
        FT_Angle         phi, theta, rotate;
        FT_Fixed         length;
        FT_Vector        sigma = { 0, 0 };
        FT_Vector        delta;
        FT_Error         error = FT_Err_Ok;
        FT_Bool          intersect;          /* use intersection of lines? */
    
    
        rotate = FT_SIDE_TO_ROTATE( side );
    
        theta = FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2;
    
        /* Only intersect borders if between two lineto's and both */
        /* lines are long enough (line_length is zero for curves). */
        /* Also avoid U-turns of nearly 180 degree.                */
        if ( !border->movable || line_length == 0  ||
             theta > 0x59C000 || theta < -0x59C000 )
          intersect = FALSE;
        else
        {
          /* compute minimum required length of lines */
          FT_Fixed  min_length;
    
    
          FT_Vector_Unit( &sigma, theta );
          min_length =
            ft_pos_abs( FT_MulDiv( stroker->radius, sigma.y, sigma.x ) );
    
          intersect = FT_BOOL( min_length                         &&
                               stroker->line_length >= min_length &&
                               line_length          >= min_length );
        }
    
        if ( !intersect )
        {
          FT_Vector_From_Polar( &delta, stroker->radius,
                                stroker->angle_out + rotate );
          delta.x += stroker->center.x;
          delta.y += stroker->center.y;
    
          border->movable = FALSE;
        }
        else
        {
          /* compute median angle */
          phi = stroker->angle_in + theta + rotate;
    
          length = FT_DivFix( stroker->radius, sigma.x );
    
          FT_Vector_From_Polar( &delta, length, phi );
          delta.x += stroker->center.x;
          delta.y += stroker->center.y;
        }
    
        error = ft_stroke_border_lineto( border, &delta, FALSE );
    
        return error;
      }
    
    
      /* process an outside corner, i.e. compute bevel/miter/round */
      static FT_Error
      ft_stroker_outside( FT_Stroker  stroker,
                          FT_Int      side,
                          FT_Fixed    line_length )
      {
        FT_StrokeBorder  border = stroker->borders + side;
        FT_Error         error;
        FT_Angle         rotate;
    
    
        if ( stroker->line_join == FT_STROKER_LINEJOIN_ROUND )
          error = ft_stroker_arcto( stroker, side );
        else
        {
          /* this is a mitered (pointed) or beveled (truncated) corner */
          FT_Fixed   radius = stroker->radius;
          FT_Vector  sigma = { 0, 0 };
          FT_Angle   theta = 0, phi = 0;
          FT_Bool    bevel, fixed_bevel;
    
    
          rotate = FT_SIDE_TO_ROTATE( side );
    
          bevel =
            FT_BOOL( stroker->line_join == FT_STROKER_LINEJOIN_BEVEL );
    
          fixed_bevel =
            FT_BOOL( stroker->line_join != FT_STROKER_LINEJOIN_MITER_VARIABLE );
    
          /* check miter limit first */
          if ( !bevel )
          {
            theta = FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2;
    
            if ( theta == FT_ANGLE_PI2 )
              theta = -rotate;
    
            phi    = stroker->angle_in + theta + rotate;
    
            FT_Vector_From_Polar( &sigma, stroker->miter_limit, theta );
    
            /* is miter limit exceeded? */
            if ( sigma.x < 0x10000L )
            {
              /* don't create variable bevels for very small deviations; */
              /* FT_Sin(x) = 0 for x <= 57                               */
              if ( fixed_bevel || ft_pos_abs( theta ) > 57 )
                bevel = TRUE;
            }
          }
    
          if ( bevel )  /* this is a bevel (broken angle) */
          {
            if ( fixed_bevel )
            {
              /* the outer corners are simply joined together */
              FT_Vector  delta;
    
    
              /* add bevel */
              FT_Vector_From_Polar( &delta,
                                    radius,
                                    stroker->angle_out + rotate );
              delta.x += stroker->center.x;
              delta.y += stroker->center.y;
    
              border->movable = FALSE;
              error = ft_stroke_border_lineto( border, &delta, FALSE );
            }
            else /* variable bevel or clipped miter */
            {
              /* the miter is truncated */
              FT_Vector  middle, delta;
              FT_Fixed   coef;
    
    
              /* compute middle point and first angle point */
              FT_Vector_From_Polar( &middle,
                                    FT_MulFix( radius, stroker->miter_limit ),
                                    phi );
    
              coef    = FT_DivFix(  0x10000L - sigma.x, sigma.y );
              delta.x = FT_MulFix(  middle.y, coef );
              delta.y = FT_MulFix( -middle.x, coef );
    
              middle.x += stroker->center.x;
              middle.y += stroker->center.y;
              delta.x  += middle.x;
              delta.y  += middle.y;
    
              error = ft_stroke_border_lineto( border, &delta, FALSE );
              if ( error )
                goto Exit;
    
              /* compute second angle point */
              delta.x = middle.x - delta.x + middle.x;
              delta.y = middle.y - delta.y + middle.y;
    
              error = ft_stroke_border_lineto( border, &delta, FALSE );
              if ( error )
                goto Exit;
    
              /* finally, add an end point; only needed if not lineto */
              /* (line_length is zero for curves)                     */
              if ( line_length == 0 )
              {
                FT_Vector_From_Polar( &delta,
                                      radius,
                                      stroker->angle_out + rotate );
    
                delta.x += stroker->center.x;
                delta.y += stroker->center.y;
    
                error = ft_stroke_border_lineto( border, &delta, FALSE );
              }
            }
          }
          else /* this is a miter (intersection) */
          {
            FT_Fixed   length;
            FT_Vector  delta;
    
    
            length = FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x );
    
            FT_Vector_From_Polar( &delta, length, phi );
            delta.x += stroker->center.x;
            delta.y += stroker->center.y;
    
            error = ft_stroke_border_lineto( border, &delta, FALSE );
            if ( error )
              goto Exit;
    
            /* now add an end point; only needed if not lineto */
            /* (line_length is zero for curves)                */
            if ( line_length == 0 )
            {
              FT_Vector_From_Polar( &delta,
                                    stroker->radius,
                                    stroker->angle_out + rotate );
              delta.x += stroker->center.x;
              delta.y += stroker->center.y;
    
              error = ft_stroke_border_lineto( border, &delta, FALSE );
            }
          }
        }
    
      Exit:
        return error;
      }
    
    
      static FT_Error
      ft_stroker_process_corner( FT_Stroker  stroker,
                                 FT_Fixed    line_length )
      {
        FT_Error  error = FT_Err_Ok;
        FT_Angle  turn;
        FT_Int    inside_side;
    
    
        turn = FT_Angle_Diff( stroker->angle_in, stroker->angle_out );
    
        /* no specific corner processing is required if the turn is 0 */
        if ( turn == 0 )
          goto Exit;
    
        /* when we turn to the right, the inside side is 0 */
        /* otherwise, the inside side is 1 */
        inside_side = ( turn < 0 );
    
        /* process the inside side */
        error = ft_stroker_inside( stroker, inside_side, line_length );
        if ( error )
          goto Exit;
    
        /* process the outside side */
        error = ft_stroker_outside( stroker, !inside_side, line_length );
    
      Exit:
        return error;
      }
    
    
      /* add two points to the left and right borders corresponding to the */
      /* start of the subpath                                              */
      static FT_Error
      ft_stroker_subpath_start( FT_Stroker  stroker,
                                FT_Angle    start_angle,
                                FT_Fixed    line_length )
      {
        FT_Vector        delta;
        FT_Vector        point;
        FT_Error         error;
        FT_StrokeBorder  border;
    
    
        FT_Vector_From_Polar( &delta, stroker->radius,
                              start_angle + FT_ANGLE_PI2 );
    
        point.x = stroker->center.x + delta.x;
        point.y = stroker->center.y + delta.y;
    
        border = stroker->borders;
        error = ft_stroke_border_moveto( border, &point );
        if ( error )
          goto Exit;
    
        point.x = stroker->center.x - delta.x;
        point.y = stroker->center.y - delta.y;
    
        border++;
        error = ft_stroke_border_moveto( border, &point );
    
        /* save angle, position, and line length for last join */
        /* (line_length is zero for curves)                    */
        stroker->subpath_angle       = start_angle;
        stroker->first_point         = FALSE;
        stroker->subpath_line_length = line_length;
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_LineTo( FT_Stroker  stroker,
                         FT_Vector*  to )
      {
        FT_Error         error = FT_Err_Ok;
        FT_StrokeBorder  border;
        FT_Vector        delta;
        FT_Angle         angle;
        FT_Int           side;
        FT_Fixed         line_length;
    
    
        if ( !stroker || !to )
          return FT_THROW( Invalid_Argument );
    
        delta.x = to->x - stroker->center.x;
        delta.y = to->y - stroker->center.y;
    
        /* a zero-length lineto is a no-op; avoid creating a spurious corner */
        if ( delta.x == 0 && delta.y == 0 )
           goto Exit;
    
        /* compute length of line */
        line_length = FT_Vector_Length( &delta );
    
        angle = FT_Atan2( delta.x, delta.y );
        FT_Vector_From_Polar( &delta, stroker->radius, angle + FT_ANGLE_PI2 );
    
        /* process corner if necessary */
        if ( stroker->first_point )
        {
          /* This is the first segment of a subpath.  We need to     */
          /* add a point to each border at their respective starting */
          /* point locations.                                        */
          error = ft_stroker_subpath_start( stroker, angle, line_length );
          if ( error )
            goto Exit;
        }
        else
        {
          /* process the current corner */
          stroker->angle_out = angle;
          error = ft_stroker_process_corner( stroker, line_length );
          if ( error )
            goto Exit;
        }
    
        /* now add a line segment to both the `inside' and `outside' paths */
        for ( border = stroker->borders, side = 1; side >= 0; side--, border++ )
        {
          FT_Vector  point;
    
    
          point.x = to->x + delta.x;
          point.y = to->y + delta.y;
    
          /* the ends of lineto borders are movable */
          error = ft_stroke_border_lineto( border, &point, TRUE );
          if ( error )
            goto Exit;
    
          delta.x = -delta.x;
          delta.y = -delta.y;
        }
    
        stroker->angle_in    = angle;
        stroker->center      = *to;
        stroker->line_length = line_length;
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_ConicTo( FT_Stroker  stroker,
                          FT_Vector*  control,
                          FT_Vector*  to )
      {
        FT_Error    error = FT_Err_Ok;
        FT_Vector   bez_stack[34];
        FT_Vector*  arc;
        FT_Vector*  limit = bez_stack + 30;
        FT_Bool     first_arc = TRUE;
    
    
        if ( !stroker || !control || !to )
        {
          error = FT_THROW( Invalid_Argument );
          goto Exit;
        }
    
        /* if all control points are coincident, this is a no-op; */
        /* avoid creating a spurious corner                       */
        if ( FT_IS_SMALL( stroker->center.x - control->x ) &&
             FT_IS_SMALL( stroker->center.y - control->y ) &&
             FT_IS_SMALL( control->x        - to->x      ) &&
             FT_IS_SMALL( control->y        - to->y      ) )
        {
           stroker->center = *to;
           goto Exit;
        }
    
        arc    = bez_stack;
        arc[0] = *to;
        arc[1] = *control;
        arc[2] = stroker->center;
    
        do
        {
          FT_Angle  angle_in, angle_out;
    
    
          /* initialize with current direction */
          angle_in = angle_out = stroker->angle_in;
    
          if ( arc < limit                                             &&
               !ft_conic_is_small_enough( arc, &angle_in, &angle_out ) )
          {
            if ( stroker->first_point )
              stroker->angle_in = angle_in;
    
            ft_conic_split( arc );
            arc += 2;
            continue;
          }
    
          if ( first_arc )
          {
            first_arc = FALSE;
    
            /* process corner if necessary */
            if ( stroker->first_point )
              error = ft_stroker_subpath_start( stroker, angle_in, 0 );
            else
            {
              stroker->angle_out = angle_in;
              error = ft_stroker_process_corner( stroker, 0 );
            }
          }
          else if ( ft_pos_abs( FT_Angle_Diff( stroker->angle_in, angle_in ) ) >
                      FT_SMALL_CONIC_THRESHOLD / 4                             )
          {
            /* if the deviation from one arc to the next is too great, */
            /* add a round corner                                      */
            stroker->center    = arc[2];
            stroker->angle_out = angle_in;
            stroker->line_join = FT_STROKER_LINEJOIN_ROUND;
    
            error = ft_stroker_process_corner( stroker, 0 );
    
            /* reinstate line join style */
            stroker->line_join = stroker->line_join_saved;
          }
    
          if ( error )
            goto Exit;
    
          /* the arc's angle is small enough; we can add it directly to each */
          /* border                                                          */
          {
            FT_Vector        ctrl, end;
            FT_Angle         theta, phi, rotate, alpha0 = 0;
            FT_Fixed         length;
            FT_StrokeBorder  border;
            FT_Int           side;
    
    
            theta  = FT_Angle_Diff( angle_in, angle_out ) / 2;
            phi    = angle_in + theta;
            length = FT_DivFix( stroker->radius, FT_Cos( theta ) );
    
            /* compute direction of original arc */
            if ( stroker->handle_wide_strokes )
              alpha0 = FT_Atan2( arc[0].x - arc[2].x, arc[0].y - arc[2].y );
    
            for ( border = stroker->borders, side = 0;
                  side <= 1;
                  side++, border++ )
            {
              rotate = FT_SIDE_TO_ROTATE( side );
    
              /* compute control point */
              FT_Vector_From_Polar( &ctrl, length, phi + rotate );
              ctrl.x += arc[1].x;
              ctrl.y += arc[1].y;
    
              /* compute end point */
              FT_Vector_From_Polar( &end, stroker->radius, angle_out + rotate );
              end.x += arc[0].x;
              end.y += arc[0].y;
    
              if ( stroker->handle_wide_strokes )
              {
                FT_Vector  start;
                FT_Angle   alpha1;
    
    
                /* determine whether the border radius is greater than the */
                /* radius of curvature of the original arc                 */
                start = border->points[border->num_points - 1];
    
                alpha1 = FT_Atan2( end.x - start.x, end.y - start.y );
    
                /* is the direction of the border arc opposite to */
                /* that of the original arc? */
                if ( ft_pos_abs( FT_Angle_Diff( alpha0, alpha1 ) ) >
                       FT_ANGLE_PI / 2                             )
                {
                  FT_Angle   beta, gamma;
                  FT_Vector  bvec, delta;
                  FT_Fixed   blen, sinA, sinB, alen;
    
    
                  /* use the sine rule to find the intersection point */
                  beta  = FT_Atan2( arc[2].x - start.x, arc[2].y - start.y );
                  gamma = FT_Atan2( arc[0].x - end.x,   arc[0].y - end.y );
    
                  bvec.x = end.x - start.x;
                  bvec.y = end.y - start.y;
    
                  blen = FT_Vector_Length( &bvec );
    
                  sinA = ft_pos_abs( FT_Sin( alpha1 - gamma ) );
                  sinB = ft_pos_abs( FT_Sin( beta - gamma ) );
    
                  alen = FT_MulDiv( blen, sinA, sinB );
    
                  FT_Vector_From_Polar( &delta, alen, beta );
                  delta.x += start.x;
                  delta.y += start.y;
    
                  /* circumnavigate the negative sector backwards */
                  border->movable = FALSE;
                  error = ft_stroke_border_lineto( border, &delta, FALSE );
                  if ( error )
                    goto Exit;
                  error = ft_stroke_border_lineto( border, &end, FALSE );
                  if ( error )
                    goto Exit;
                  error = ft_stroke_border_conicto( border, &ctrl, &start );
                  if ( error )
                    goto Exit;
                  /* and then move to the endpoint */
                  error = ft_stroke_border_lineto( border, &end, FALSE );
                  if ( error )
                    goto Exit;
    
                  continue;
                }
    
                /* else fall through */
              }
    
              /* simply add an arc */
              error = ft_stroke_border_conicto( border, &ctrl, &end );
              if ( error )
                goto Exit;
            }
          }
    
          stroker->angle_in = angle_out;
    
          if ( arc == bez_stack )
            break;
          arc -= 2;
        } while ( 1 );
    
        stroker->center      = *to;
        stroker->line_length = 0;
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_CubicTo( FT_Stroker  stroker,
                          FT_Vector*  control1,
                          FT_Vector*  control2,
                          FT_Vector*  to )
      {
        FT_Error    error = FT_Err_Ok;
        FT_Vector   bez_stack[37];
        FT_Vector*  arc;
        FT_Vector*  limit = bez_stack + 32;
        FT_Bool     first_arc = TRUE;
    
    
        if ( !stroker || !control1 || !control2 || !to )
        {
          error = FT_THROW( Invalid_Argument );
          goto Exit;
        }
    
        /* if all control points are coincident, this is a no-op; */
        /* avoid creating a spurious corner */
        if ( FT_IS_SMALL( stroker->center.x - control1->x ) &&
             FT_IS_SMALL( stroker->center.y - control1->y ) &&
             FT_IS_SMALL( control1->x       - control2->x ) &&
             FT_IS_SMALL( control1->y       - control2->y ) &&
             FT_IS_SMALL( control2->x       - to->x       ) &&
             FT_IS_SMALL( control2->y       - to->y       ) )
        {
           stroker->center = *to;
           goto Exit;
        }
    
        arc    = bez_stack;
        arc[0] = *to;
        arc[1] = *control2;
        arc[2] = *control1;
        arc[3] = stroker->center;
    
        do
        {
          FT_Angle  angle_in, angle_mid, angle_out;
    
    
          /* initialize with current direction */
          angle_in = angle_out = angle_mid = stroker->angle_in;
    
          if ( arc < limit                                         &&
               !ft_cubic_is_small_enough( arc, &angle_in,
                                          &angle_mid, &angle_out ) )
          {
            if ( stroker->first_point )
              stroker->angle_in = angle_in;
    
            ft_cubic_split( arc );
            arc += 3;
            continue;
          }
    
          if ( first_arc )
          {
            first_arc = FALSE;
    
            /* process corner if necessary */
            if ( stroker->first_point )
              error = ft_stroker_subpath_start( stroker, angle_in, 0 );
            else
            {
              stroker->angle_out = angle_in;
              error = ft_stroker_process_corner( stroker, 0 );
            }
          }
          else if ( ft_pos_abs( FT_Angle_Diff( stroker->angle_in, angle_in ) ) >
                      FT_SMALL_CUBIC_THRESHOLD / 4                             )
          {
            /* if the deviation from one arc to the next is too great, */
            /* add a round corner                                      */
            stroker->center    = arc[3];
            stroker->angle_out = angle_in;
            stroker->line_join = FT_STROKER_LINEJOIN_ROUND;
    
            error = ft_stroker_process_corner( stroker, 0 );
    
            /* reinstate line join style */
            stroker->line_join = stroker->line_join_saved;
          }
    
          if ( error )
            goto Exit;
    
          /* the arc's angle is small enough; we can add it directly to each */
          /* border                                                          */
          {
            FT_Vector        ctrl1, ctrl2, end;
            FT_Angle         theta1, phi1, theta2, phi2, rotate, alpha0 = 0;
            FT_Fixed         length1, length2;
            FT_StrokeBorder  border;
            FT_Int           side;
    
    
            theta1  = FT_Angle_Diff( angle_in,  angle_mid ) / 2;
            theta2  = FT_Angle_Diff( angle_mid, angle_out ) / 2;
            phi1    = ft_angle_mean( angle_in,  angle_mid );
            phi2    = ft_angle_mean( angle_mid, angle_out );
            length1 = FT_DivFix( stroker->radius, FT_Cos( theta1 ) );
            length2 = FT_DivFix( stroker->radius, FT_Cos( theta2 ) );
    
            /* compute direction of original arc */
            if ( stroker->handle_wide_strokes )
              alpha0 = FT_Atan2( arc[0].x - arc[3].x, arc[0].y - arc[3].y );
    
            for ( border = stroker->borders, side = 0;
                  side <= 1;
                  side++, border++ )
            {
              rotate = FT_SIDE_TO_ROTATE( side );
    
              /* compute control points */
              FT_Vector_From_Polar( &ctrl1, length1, phi1 + rotate );
              ctrl1.x += arc[2].x;
              ctrl1.y += arc[2].y;
    
              FT_Vector_From_Polar( &ctrl2, length2, phi2 + rotate );
              ctrl2.x += arc[1].x;
              ctrl2.y += arc[1].y;
    
              /* compute end point */
              FT_Vector_From_Polar( &end, stroker->radius, angle_out + rotate );
              end.x += arc[0].x;
              end.y += arc[0].y;
    
              if ( stroker->handle_wide_strokes )
              {
                FT_Vector  start;
                FT_Angle   alpha1;
    
    
                /* determine whether the border radius is greater than the */
                /* radius of curvature of the original arc                 */
                start = border->points[border->num_points - 1];
    
                alpha1 = FT_Atan2( end.x - start.x, end.y - start.y );
    
                /* is the direction of the border arc opposite to */
                /* that of the original arc? */
                if ( ft_pos_abs( FT_Angle_Diff( alpha0, alpha1 ) ) >
                       FT_ANGLE_PI / 2                             )
                {
                  FT_Angle   beta, gamma;
                  FT_Vector  bvec, delta;
                  FT_Fixed   blen, sinA, sinB, alen;
    
    
                  /* use the sine rule to find the intersection point */
                  beta  = FT_Atan2( arc[3].x - start.x, arc[3].y - start.y );
                  gamma = FT_Atan2( arc[0].x - end.x,   arc[0].y - end.y );
    
                  bvec.x = end.x - start.x;
                  bvec.y = end.y - start.y;
    
                  blen = FT_Vector_Length( &bvec );
    
                  sinA = ft_pos_abs( FT_Sin( alpha1 - gamma ) );
                  sinB = ft_pos_abs( FT_Sin( beta - gamma ) );
    
                  alen = FT_MulDiv( blen, sinA, sinB );
    
                  FT_Vector_From_Polar( &delta, alen, beta );
                  delta.x += start.x;
                  delta.y += start.y;
    
                  /* circumnavigate the negative sector backwards */
                  border->movable = FALSE;
                  error = ft_stroke_border_lineto( border, &delta, FALSE );
                  if ( error )
                    goto Exit;
                  error = ft_stroke_border_lineto( border, &end, FALSE );
                  if ( error )
                    goto Exit;
                  error = ft_stroke_border_cubicto( border,
                                                    &ctrl2,
                                                    &ctrl1,
                                                    &start );
                  if ( error )
                    goto Exit;
                  /* and then move to the endpoint */
                  error = ft_stroke_border_lineto( border, &end, FALSE );
                  if ( error )
                    goto Exit;
    
                  continue;
                }
    
                /* else fall through */
              }
    
              /* simply add an arc */
              error = ft_stroke_border_cubicto( border, &ctrl1, &ctrl2, &end );
              if ( error )
                goto Exit;
            }
          }
    
          stroker->angle_in = angle_out;
    
          if ( arc == bez_stack )
            break;
          arc -= 3;
        } while ( 1 );
    
        stroker->center      = *to;
        stroker->line_length = 0;
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_BeginSubPath( FT_Stroker  stroker,
                               FT_Vector*  to,
                               FT_Bool     open )
      {
        if ( !stroker || !to )
          return FT_THROW( Invalid_Argument );
    
        /* We cannot process the first point, because there is not enough      */
        /* information regarding its corner/cap.  The latter will be processed */
        /* in the `FT_Stroker_EndSubPath' routine.                             */
        /*                                                                     */
        stroker->first_point  = TRUE;
        stroker->center       = *to;
        stroker->subpath_open = open;
    
        /* Determine if we need to check whether the border radius is greater */
        /* than the radius of curvature of a curve, to handle this case       */
        /* specially.  This is only required if bevel joins or butt caps may  */
        /* be created, because round & miter joins and round & square caps    */
        /* cover the negative sector created with wide strokes.               */
        stroker->handle_wide_strokes =
          FT_BOOL( stroker->line_join != FT_STROKER_LINEJOIN_ROUND  ||
                   ( stroker->subpath_open                        &&
                     stroker->line_cap == FT_STROKER_LINECAP_BUTT ) );
    
        /* record the subpath start point for each border */
        stroker->subpath_start = *to;
    
        stroker->angle_in = 0;
    
        return FT_Err_Ok;
      }
    
    
      static FT_Error
      ft_stroker_add_reverse_left( FT_Stroker  stroker,
                                   FT_Bool     open )
      {
        FT_StrokeBorder  right = stroker->borders + 0;
        FT_StrokeBorder  left  = stroker->borders + 1;
        FT_Int           new_points;
        FT_Error         error = FT_Err_Ok;
    
    
        FT_ASSERT( left->start >= 0 );
    
        new_points = (FT_Int)left->num_points - left->start;
        if ( new_points > 0 )
        {
          error = ft_stroke_border_grow( right, (FT_UInt)new_points );
          if ( error )
            goto Exit;
    
          {
            FT_Vector*  dst_point = right->points + right->num_points;
            FT_Byte*    dst_tag   = right->tags   + right->num_points;
            FT_Vector*  src_point = left->points  + left->num_points - 1;
            FT_Byte*    src_tag   = left->tags    + left->num_points - 1;
    
    
            while ( src_point >= left->points + left->start )
            {
              *dst_point = *src_point;
              *dst_tag   = *src_tag;
    
              if ( open )
                dst_tag[0] &= ~FT_STROKE_TAG_BEGIN_END;
              else
              {
                FT_Byte  ttag =
                           (FT_Byte)( dst_tag[0] & FT_STROKE_TAG_BEGIN_END );
    
    
                /* switch begin/end tags if necessary */
                if ( ttag == FT_STROKE_TAG_BEGIN ||
                     ttag == FT_STROKE_TAG_END   )
                  dst_tag[0] ^= FT_STROKE_TAG_BEGIN_END;
              }
    
              src_point--;
              src_tag--;
              dst_point++;
              dst_tag++;
            }
          }
    
          left->num_points   = (FT_UInt)left->start;
          right->num_points += (FT_UInt)new_points;
    
          right->movable = FALSE;
          left->movable  = FALSE;
        }
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      /* there's a lot of magic in this function! */
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_EndSubPath( FT_Stroker  stroker )
      {
        FT_Error  error = FT_Err_Ok;
    
    
        if ( !stroker )
        {
          error = FT_THROW( Invalid_Argument );
          goto Exit;
        }
    
        if ( stroker->subpath_open )
        {
          FT_StrokeBorder  right = stroker->borders;
    
    
          /* All right, this is an opened path, we need to add a cap between */
          /* right & left, add the reverse of left, then add a final cap     */
          /* between left & right.                                           */
          error = ft_stroker_cap( stroker, stroker->angle_in, 0 );
          if ( error )
            goto Exit;
    
          /* add reversed points from `left' to `right' */
          error = ft_stroker_add_reverse_left( stroker, TRUE );
          if ( error )
            goto Exit;
    
          /* now add the final cap */
          stroker->center = stroker->subpath_start;
          error = ft_stroker_cap( stroker,
                                  stroker->subpath_angle + FT_ANGLE_PI, 0 );
          if ( error )
            goto Exit;
    
          /* Now end the right subpath accordingly.  The left one is */
          /* rewind and doesn't need further processing.             */
          ft_stroke_border_close( right, FALSE );
        }
        else
        {
          /* close the path if needed */
          if ( !FT_IS_SMALL( stroker->center.x - stroker->subpath_start.x ) ||
               !FT_IS_SMALL( stroker->center.y - stroker->subpath_start.y ) )
          {
             error = FT_Stroker_LineTo( stroker, &stroker->subpath_start );
             if ( error )
               goto Exit;
          }
    
          /* process the corner */
          stroker->angle_out = stroker->subpath_angle;
    
          error = ft_stroker_process_corner( stroker,
                                             stroker->subpath_line_length );
          if ( error )
            goto Exit;
    
          /* then end our two subpaths */
          ft_stroke_border_close( stroker->borders + 0, FALSE );
          ft_stroke_border_close( stroker->borders + 1, TRUE );
        }
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_GetBorderCounts( FT_Stroker        stroker,
                                  FT_StrokerBorder  border,
                                  FT_UInt          *anum_points,
                                  FT_UInt          *anum_contours )
      {
        FT_UInt   num_points = 0, num_contours = 0;
        FT_Error  error;
    
    
        if ( !stroker || border > 1 )
        {
          error = FT_THROW( Invalid_Argument );
          goto Exit;
        }
    
        error = ft_stroke_border_get_counts( stroker->borders + border,
                                             &num_points, &num_contours );
      Exit:
        if ( anum_points )
          *anum_points = num_points;
    
        if ( anum_contours )
          *anum_contours = num_contours;
    
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_GetCounts( FT_Stroker  stroker,
                            FT_UInt    *anum_points,
                            FT_UInt    *anum_contours )
      {
        FT_UInt   count1, count2, num_points   = 0;
        FT_UInt   count3, count4, num_contours = 0;
        FT_Error  error;
    
    
        if ( !stroker )
        {
          error = FT_THROW( Invalid_Argument );
          goto Exit;
        }
    
        error = ft_stroke_border_get_counts( stroker->borders + 0,
                                             &count1, &count2 );
        if ( error )
          goto Exit;
    
        error = ft_stroke_border_get_counts( stroker->borders + 1,
                                             &count3, &count4 );
        if ( error )
          goto Exit;
    
        num_points   = count1 + count3;
        num_contours = count2 + count4;
    
      Exit:
        if ( anum_points )
          *anum_points   = num_points;
    
        if ( anum_contours )
          *anum_contours = num_contours;
    
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( void )
      FT_Stroker_ExportBorder( FT_Stroker        stroker,
                               FT_StrokerBorder  border,
                               FT_Outline*       outline )
      {
        if ( !stroker || !outline )
          return;
    
        if ( border == FT_STROKER_BORDER_LEFT  ||
             border == FT_STROKER_BORDER_RIGHT )
        {
          FT_StrokeBorder  sborder = & stroker->borders[border];
    
    
          if ( sborder->valid )
            ft_stroke_border_export( sborder, outline );
        }
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( void )
      FT_Stroker_Export( FT_Stroker   stroker,
                         FT_Outline*  outline )
      {
        FT_Stroker_ExportBorder( stroker, FT_STROKER_BORDER_LEFT, outline );
        FT_Stroker_ExportBorder( stroker, FT_STROKER_BORDER_RIGHT, outline );
      }
    
    
      /* documentation is in ftstroke.h */
    
      /*
       * The following is very similar to FT_Outline_Decompose, except
       * that we do support opened paths, and do not scale the outline.
       */
      FT_EXPORT_DEF( FT_Error )
      FT_Stroker_ParseOutline( FT_Stroker   stroker,
                               FT_Outline*  outline,
                               FT_Bool      opened )
      {
        FT_Vector   v_last;
        FT_Vector   v_control;
        FT_Vector   v_start;
    
        FT_Vector*  point;
        FT_Vector*  limit;
        FT_Byte*    tags;
    
        FT_Error    error;
    
        FT_Int      n;         /* index of contour in outline     */
        FT_Int      first;     /* index of first point in contour */
        FT_Int      last;      /* index of last point in contour  */
    
        FT_Int      tag;       /* current point's state           */
    
    
        if ( !outline )
          return FT_THROW( Invalid_Outline );
    
        if ( !stroker )
          return FT_THROW( Invalid_Argument );
    
        FT_Stroker_Rewind( stroker );
    
        last = -1;
        for ( n = 0; n < outline->n_contours; n++ )
        {
          first = last + 1;
          last  = outline->contours[n];
    
          /* skip empty points; we don't stroke these */
          if ( last <= first )
            continue;
    
          limit = outline->points + last;
    
          v_start = outline->points[first];
          v_last  = outline->points[last];
    
          v_control = v_start;
    
          point = outline->points + first;
          tags  = outline->tags   + first;
          tag   = FT_CURVE_TAG( tags[0] );
    
          /* A contour cannot start with a cubic control point! */
          if ( tag == FT_CURVE_TAG_CUBIC )
            goto Invalid_Outline;
    
          /* check first point to determine origin */
          if ( tag == FT_CURVE_TAG_CONIC )
          {
            /* First point is conic control.  Yes, this happens. */
            if ( FT_CURVE_TAG( outline->tags[last] ) == FT_CURVE_TAG_ON )
            {
              /* start at last point if it is on the curve */
              v_start = v_last;
              limit--;
            }
            else
            {
              /* if both first and last points are conic, */
              /* start at their middle                    */
              v_start.x = ( v_start.x + v_last.x ) / 2;
              v_start.y = ( v_start.y + v_last.y ) / 2;
            }
            point--;
            tags--;
          }
    
          error = FT_Stroker_BeginSubPath( stroker, &v_start, opened );
          if ( error )
            goto Exit;
    
          while ( point < limit )
          {
            point++;
            tags++;
    
            tag = FT_CURVE_TAG( tags[0] );
            switch ( tag )
            {
            case FT_CURVE_TAG_ON:  /* emit a single line_to */
              {
                FT_Vector  vec;
    
    
                vec.x = point->x;
                vec.y = point->y;
    
                error = FT_Stroker_LineTo( stroker, &vec );
                if ( error )
                  goto Exit;
                continue;
              }
    
            case FT_CURVE_TAG_CONIC:  /* consume conic arcs */
              v_control.x = point->x;
              v_control.y = point->y;
    
            Do_Conic:
              if ( point < limit )
              {
                FT_Vector  vec;
                FT_Vector  v_middle;
    
    
                point++;
                tags++;
                tag = FT_CURVE_TAG( tags[0] );
    
                vec = point[0];
    
                if ( tag == FT_CURVE_TAG_ON )
                {
                  error = FT_Stroker_ConicTo( stroker, &v_control, &vec );
                  if ( error )
                    goto Exit;
                  continue;
                }
    
                if ( tag != FT_CURVE_TAG_CONIC )
                  goto Invalid_Outline;
    
                v_middle.x = ( v_control.x + vec.x ) / 2;
                v_middle.y = ( v_control.y + vec.y ) / 2;
    
                error = FT_Stroker_ConicTo( stroker, &v_control, &v_middle );
                if ( error )
                  goto Exit;
    
                v_control = vec;
                goto Do_Conic;
              }
    
              error = FT_Stroker_ConicTo( stroker, &v_control, &v_start );
              goto Close;
    
            default:  /* FT_CURVE_TAG_CUBIC */
              {
                FT_Vector  vec1, vec2;
    
    
                if ( point + 1 > limit                             ||
                     FT_CURVE_TAG( tags[1] ) != FT_CURVE_TAG_CUBIC )
                  goto Invalid_Outline;
    
                point += 2;
                tags  += 2;
    
                vec1 = point[-2];
                vec2 = point[-1];
    
                if ( point <= limit )
                {
                  FT_Vector  vec;
    
    
                  vec = point[0];
    
                  error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &vec );
                  if ( error )
                    goto Exit;
                  continue;
                }
    
                error = FT_Stroker_CubicTo( stroker, &vec1, &vec2, &v_start );
                goto Close;
              }
            }
          }
    
        Close:
          if ( error )
            goto Exit;
    
          /* don't try to end the path if no segments have been generated */
          if ( !stroker->first_point )
          {
            error = FT_Stroker_EndSubPath( stroker );
            if ( error )
              goto Exit;
          }
        }
    
        return FT_Err_Ok;
    
      Exit:
        return error;
    
      Invalid_Outline:
        return FT_THROW( Invalid_Outline );
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Glyph_Stroke( FT_Glyph    *pglyph,
                       FT_Stroker   stroker,
                       FT_Bool      destroy )
      {
        FT_Error  error = FT_ERR( Invalid_Argument );
        FT_Glyph  glyph = NULL;
    
    
        if ( !pglyph )
          goto Exit;
    
        glyph = *pglyph;
        if ( !glyph || glyph->clazz != &ft_outline_glyph_class )
          goto Exit;
    
        {
          FT_Glyph  copy;
    
    
          error = FT_Glyph_Copy( glyph, &copy );
          if ( error )
            goto Exit;
    
          glyph = copy;
        }
    
        {
          FT_OutlineGlyph  oglyph  = (FT_OutlineGlyph)glyph;
          FT_Outline*      outline = &oglyph->outline;
          FT_UInt          num_points, num_contours;
    
    
          error = FT_Stroker_ParseOutline( stroker, outline, FALSE );
          if ( error )
            goto Fail;
    
          FT_Stroker_GetCounts( stroker, &num_points, &num_contours );
    
          FT_Outline_Done( glyph->library, outline );
    
          error = FT_Outline_New( glyph->library,
                                  num_points,
                                  (FT_Int)num_contours,
                                  outline );
          if ( error )
            goto Fail;
    
          outline->n_points   = 0;
          outline->n_contours = 0;
    
          FT_Stroker_Export( stroker, outline );
        }
    
        if ( destroy )
          FT_Done_Glyph( *pglyph );
    
        *pglyph = glyph;
        goto Exit;
    
      Fail:
        FT_Done_Glyph( glyph );
        glyph = NULL;
    
        if ( !destroy )
          *pglyph = NULL;
    
      Exit:
        return error;
      }
    
    
      /* documentation is in ftstroke.h */
    
      FT_EXPORT_DEF( FT_Error )
      FT_Glyph_StrokeBorder( FT_Glyph    *pglyph,
                             FT_Stroker   stroker,
                             FT_Bool      inside,
                             FT_Bool      destroy )
      {
        FT_Error  error = FT_ERR( Invalid_Argument );
        FT_Glyph  glyph = NULL;
    
    
        if ( !pglyph )
          goto Exit;
    
        glyph = *pglyph;
        if ( !glyph || glyph->clazz != &ft_outline_glyph_class )
          goto Exit;
    
        {
          FT_Glyph  copy;
    
    
          error = FT_Glyph_Copy( glyph, &copy );
          if ( error )
            goto Exit;
    
          glyph = copy;
        }
    
        {
          FT_OutlineGlyph   oglyph  = (FT_OutlineGlyph)glyph;
          FT_StrokerBorder  border;
          FT_Outline*       outline = &oglyph->outline;
          FT_UInt           num_points, num_contours;
    
    
          border = FT_Outline_GetOutsideBorder( outline );
          if ( inside )
          {
            if ( border == FT_STROKER_BORDER_LEFT )
              border = FT_STROKER_BORDER_RIGHT;
            else
              border = FT_STROKER_BORDER_LEFT;
          }
    
          error = FT_Stroker_ParseOutline( stroker, outline, FALSE );
          if ( error )
            goto Fail;
    
          FT_Stroker_GetBorderCounts( stroker, border,
                                      &num_points, &num_contours );
    
          FT_Outline_Done( glyph->library, outline );
    
          error = FT_Outline_New( glyph->library,
                                  num_points,
                                  (FT_Int)num_contours,
                                  outline );
          if ( error )
            goto Fail;
    
          outline->n_points   = 0;
          outline->n_contours = 0;
    
          FT_Stroker_ExportBorder( stroker, border, outline );
        }
    
        if ( destroy )
          FT_Done_Glyph( *pglyph );
    
        *pglyph = glyph;
        goto Exit;
    
      Fail:
        FT_Done_Glyph( glyph );
        glyph = NULL;
    
        if ( !destroy )
          *pglyph = NULL;
    
      Exit:
        return error;
      }
    
    
    /* END */