base[3].y = ( a + c ) >> 3;
}
-static void gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1,
+
+static void
+gray_render_cubic(RAS_ARG_ const SW_FT_Vector* control1,
const SW_FT_Vector* control2,
const SW_FT_Vector* to)
{
- SW_FT_Vector* arc;
- TPos min, max, y;
-
- arc = ras.bez_stack;
- arc[0].x = UPSCALE(to->x);
- arc[0].y = UPSCALE(to->y);
- arc[1].x = UPSCALE(control2->x);
- arc[1].y = UPSCALE(control2->y);
- arc[2].x = UPSCALE(control1->x);
- arc[2].y = UPSCALE(control1->y);
+ SW_FT_Vector* arc = ras.bez_stack;
+
+ arc[0].x = UPSCALE( to->x );
+ arc[0].y = UPSCALE( to->y );
+ arc[1].x = UPSCALE( control2->x );
+ arc[1].y = UPSCALE( control2->y );
+ arc[2].x = UPSCALE( control1->x );
+ arc[2].y = UPSCALE( control1->y );
arc[3].x = ras.x;
arc[3].y = ras.y;
- /* Short-cut the arc that crosses the current band. */
- min = max = arc[0].y;
-
- y = arc[1].y;
- if (y < min) min = y;
- if (y > max) max = y;
-
- y = arc[2].y;
- if (y < min) min = y;
- if (y > max) max = y;
-
- y = arc[3].y;
- if (y < min) min = y;
- if (y > max) max = y;
-
- if (TRUNC(min) >= ras.max_ey || TRUNC(max) < ras.min_ey) goto Draw;
-
- for (;;) {
- /* Decide whether to split or draw. See `Rapid Termination */
- /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */
- /* F. Hain, at */
- /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf
- */
-
- {
- TPos dx, dy, dx_, dy_;
- TPos dx1, dy1, dx2, dy2;
- TPos L, s, s_limit;
-
- /* dx and dy are x and y components of the P0-P3 chord vector. */
- dx = dx_ = arc[3].x - arc[0].x;
- dy = dy_ = arc[3].y - arc[0].y;
-
- L = SW_FT_HYPOT(dx_, dy_);
-
- /* Avoid possible arithmetic overflow below by splitting. */
- if (L > 32767) goto Split;
-
- /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */
- s_limit = L * (TPos)(ONE_PIXEL / 6);
-
- /* s is L * the perpendicular distance from P1 to the line P0-P3. */
- dx1 = arc[1].x - arc[0].x;
- dy1 = arc[1].y - arc[0].y;
- s = SW_FT_ABS(dy * dx1 - dx * dy1);
-
- if (s > s_limit) goto Split;
-
- /* s is L * the perpendicular distance from P2 to the line P0-P3. */
- dx2 = arc[2].x - arc[0].x;
- dy2 = arc[2].y - arc[0].y;
- s = SW_FT_ABS(dy * dx2 - dx * dy2);
-
- if (s > s_limit) goto Split;
+ /* short-cut the arc that crosses the current band */
+ if ( ( TRUNC( arc[0].y ) >= ras.max_ey &&
+ TRUNC( arc[1].y ) >= ras.max_ey &&
+ TRUNC( arc[2].y ) >= ras.max_ey &&
+ TRUNC( arc[3].y ) >= ras.max_ey ) ||
+ ( TRUNC( arc[0].y ) < ras.min_ey &&
+ TRUNC( arc[1].y ) < ras.min_ey &&
+ TRUNC( arc[2].y ) < ras.min_ey &&
+ TRUNC( arc[3].y ) < ras.min_ey ) )
+ {
+ ras.x = arc[0].x;
+ ras.y = arc[0].y;
+ return;
+ }
- /* Split super curvy segments where the off points are so far
- from the chord that the angles P0-P1-P3 or P0-P2-P3 become
- acute as detected by appropriate dot products. */
- if (dx1 * (dx1 - dx) + dy1 * (dy1 - dy) > 0 ||
- dx2 * (dx2 - dx) + dy2 * (dy2 - dy) > 0)
- goto Split;
+ for (;;)
+ {
+ /* with each split, control points quickly converge towards */
+ /* chord trisection points and the vanishing distances below */
+ /* indicate when the segment is flat enough to draw */
+ if ( SW_FT_ABS( 2 * arc[0].x - 3 * arc[1].x + arc[3].x ) > ONE_PIXEL / 2 ||
+ SW_FT_ABS( 2 * arc[0].y - 3 * arc[1].y + arc[3].y ) > ONE_PIXEL / 2 ||
+ SW_FT_ABS( arc[0].x - 3 * arc[2].x + 2 * arc[3].x ) > ONE_PIXEL / 2 ||
+ SW_FT_ABS( arc[0].y - 3 * arc[2].y + 2 * arc[3].y ) > ONE_PIXEL / 2 )
+ goto Split;
+
+ gray_render_line( RAS_VAR_ arc[0].x, arc[0].y );
+
+ if ( arc == ras.bez_stack )
+ return;
- /* No reason to split. */
- goto Draw;
- }
+ arc -= 3;
+ continue;
Split:
- gray_split_cubic(arc);
- arc += 3;
- continue;
-
- Draw:
- gray_render_line(RAS_VAR_ arc[0].x, arc[0].y);
-
- if (arc == ras.bez_stack) return;
-
- arc -= 3;
+ gray_split_cubic( arc );
+ arc += 3;
}
}
#define SW_FT_ARC_CUBIC_ANGLE (SW_FT_ANGLE_PI / 2)
-static SW_FT_Error ft_stroke_border_arcto(SW_FT_StrokeBorder border,
- SW_FT_Vector* center,
- SW_FT_Fixed radius,
- SW_FT_Angle angle_start,
- SW_FT_Angle angle_diff)
+
+static SW_FT_Error
+ft_stroke_border_arcto( SW_FT_StrokeBorder border,
+ SW_FT_Vector* center,
+ SW_FT_Fixed radius,
+ SW_FT_Angle angle_start,
+ SW_FT_Angle angle_diff )
{
- SW_FT_Angle total, angle, step, rotate, next, theta;
- SW_FT_Vector a, b, a2, b2;
- SW_FT_Fixed length;
- SW_FT_Error error = 0;
-
- /* compute start point */
- SW_FT_Vector_From_Polar(&a, radius, angle_start);
- a.x += center->x;
- a.y += center->y;
-
- total = angle_diff;
- angle = angle_start;
- rotate = (angle_diff >= 0) ? SW_FT_ANGLE_PI2 : -SW_FT_ANGLE_PI2;
-
- while (total != 0) {
- step = total;
- if (step > SW_FT_ARC_CUBIC_ANGLE)
- step = SW_FT_ARC_CUBIC_ANGLE;
-
- else if (step < -SW_FT_ARC_CUBIC_ANGLE)
- step = -SW_FT_ARC_CUBIC_ANGLE;
-
- next = angle + step;
- theta = step;
- if (theta < 0) theta = -theta;
-
- theta >>= 1;
-
- /* compute end point */
- SW_FT_Vector_From_Polar(&b, radius, next);
- b.x += center->x;
- b.y += center->y;
-
- /* compute first and second control points */
- length = SW_FT_MulDiv(radius, SW_FT_Sin(theta) * 4,
- (0x10000L + SW_FT_Cos(theta)) * 3);
-
- SW_FT_Vector_From_Polar(&a2, length, angle + rotate);
- a2.x += a.x;
- a2.y += a.y;
-
- SW_FT_Vector_From_Polar(&b2, length, next - rotate);
- b2.x += b.x;
- b2.y += b.y;
-
- /* add cubic arc */
- error = ft_stroke_border_cubicto(border, &a2, &b2, &b);
- if (error) break;
-
- /* process the rest of the arc ?? */
- a = b;
- total -= step;
- angle = next;
+ SW_FT_Fixed coef;
+ SW_FT_Vector a0, a1, a2, a3;
+ SW_FT_Int i, arcs = 1;
+ SW_FT_Error error = 0;
+
+
+ /* number of cubic arcs to draw */
+ while ( angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs ||
+ -angle_diff > SW_FT_ARC_CUBIC_ANGLE * arcs )
+ arcs++;
+
+ /* control tangents */
+ coef = SW_FT_Tan( angle_diff / ( 4 * arcs ) );
+ coef += coef / 3;
+
+ /* compute start and first control point */
+ SW_FT_Vector_From_Polar( &a0, radius, angle_start );
+ a1.x = SW_FT_MulFix( -a0.y, coef );
+ a1.y = SW_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 */
+ SW_FT_Vector_From_Polar( &a3, radius,
+ angle_start + i * angle_diff / arcs );
+ a2.x = SW_FT_MulFix( a3.y, coef );
+ a2.y = SW_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;
}
/* add a cap at the end of an opened path */
-static SW_FT_Error ft_stroker_cap(SW_FT_Stroker stroker, SW_FT_Angle angle,
- SW_FT_Int side)
+static SW_FT_Error
+ft_stroker_cap(SW_FT_Stroker stroker,
+ SW_FT_Angle angle,
+ SW_FT_Int side)
{
SW_FT_Error error = 0;
- if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND) {
+ if (stroker->line_cap == SW_FT_STROKER_LINECAP_ROUND)
+ {
/* add a round cap */
stroker->angle_in = angle;
stroker->angle_out = angle + SW_FT_ANGLE_PI;
error = ft_stroker_arcto(stroker, side);
- } else if (stroker->line_cap == SW_FT_STROKER_LINECAP_SQUARE) {
- /* add a square cap */
- SW_FT_Vector delta, delta2;
- SW_FT_Angle rotate = SW_FT_SIDE_TO_ROTATE(side);
- SW_FT_Fixed radius = stroker->radius;
- SW_FT_StrokeBorder border = stroker->borders + side;
-
- SW_FT_Vector_From_Polar(&delta2, radius, angle + rotate);
- SW_FT_Vector_From_Polar(&delta, radius, angle);
-
- delta.x += stroker->center.x + delta2.x;
- delta.y += stroker->center.y + delta2.y;
-
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- if (error) goto Exit;
-
- SW_FT_Vector_From_Polar(&delta2, radius, angle - rotate);
- SW_FT_Vector_From_Polar(&delta, radius, angle);
-
- delta.x += delta2.x + stroker->center.x;
- delta.y += delta2.y + stroker->center.y;
-
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- } else if (stroker->line_cap == SW_FT_STROKER_LINECAP_BUTT) {
- /* add a butt ending */
- SW_FT_Vector delta;
- SW_FT_Angle rotate = SW_FT_SIDE_TO_ROTATE(side);
- SW_FT_Fixed radius = stroker->radius;
- SW_FT_StrokeBorder border = stroker->borders + side;
+ }
+ else
+ {
+ /* add a square or butt cap */
+ SW_FT_Vector middle, delta;
+ SW_FT_Fixed radius = stroker->radius;
+ SW_FT_StrokeBorder border = stroker->borders + side;
- SW_FT_Vector_From_Polar(&delta, radius, angle + rotate);
+ /* compute middle point and first angle point */
+ SW_FT_Vector_From_Polar( &middle, radius, angle );
+ delta.x = side ? middle.y : -middle.y;
+ delta.y = side ? -middle.x : middle.x;
- delta.x += stroker->center.x;
- delta.y += stroker->center.y;
+ if ( stroker->line_cap == SW_FT_STROKER_LINECAP_SQUARE )
+ {
+ middle.x += stroker->center.x;
+ middle.y += stroker->center.y;
+ }
+ else /* SW_FT_STROKER_LINECAP_BUTT */
+ {
+ middle.x = stroker->center.x;
+ middle.y = stroker->center.y;
+ }
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- if (error) goto Exit;
+ delta.x += middle.x;
+ delta.y += middle.y;
- SW_FT_Vector_From_Polar(&delta, radius, angle - rotate);
+ error = ft_stroke_border_lineto( border, &delta, FALSE );
+ if ( error )
+ goto Exit;
- delta.x += stroker->center.x;
- delta.y += stroker->center.y;
+ /* 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);
+ error = ft_stroke_border_lineto( border, &delta, FALSE );
}
Exit:
{
SW_FT_StrokeBorder border = stroker->borders + side;
SW_FT_Angle phi, theta, rotate;
- SW_FT_Fixed length, thcos;
- SW_FT_Vector delta;
+ SW_FT_Fixed length;
+ SW_FT_Vector sigma, delta;
SW_FT_Error error = 0;
SW_FT_Bool intersect; /* use intersection of lines? */
/* Only intersect borders if between two lineto's and both */
/* lines are long enough (line_length is zero for curves). */
- if (!border->movable || line_length == 0)
+ if (!border->movable || line_length == 0 ||
+ theta > 0x59C000 || theta < -0x59C000 )
intersect = FALSE;
else {
- /* compute minimum required length of lines */
- SW_FT_Fixed min_length =
- ft_pos_abs(SW_FT_MulFix(stroker->radius, SW_FT_Tan(theta)));
+ /* compute minimum required length of lines */
+ SW_FT_Fixed min_length;
- intersect = SW_FT_BOOL(stroker->line_length >= min_length &&
- line_length >= min_length);
+
+ SW_FT_Vector_Unit( &sigma, theta );
+ min_length =
+ ft_pos_abs( SW_FT_MulDiv( stroker->radius, sigma.y, sigma.x ) );
+
+ intersect = SW_FT_BOOL( min_length &&
+ stroker->line_length >= min_length &&
+ line_length >= min_length );
}
if (!intersect) {
border->movable = FALSE;
} else {
/* compute median angle */
- phi = stroker->angle_in + theta;
+ phi = stroker->angle_in + theta + rotate;
- thcos = SW_FT_Cos(theta);
+ length = SW_FT_DivFix( stroker->radius, sigma.x );
- length = SW_FT_DivFix(stroker->radius, thcos);
-
- SW_FT_Vector_From_Polar(&delta, length, phi + rotate);
- delta.x += stroker->center.x;
- delta.y += stroker->center.y;
+ SW_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 SW_FT_Error ft_stroker_outside(SW_FT_Stroker stroker, SW_FT_Int side,
- SW_FT_Fixed line_length)
+ /* process an outside corner, i.e. compute bevel/miter/round */
+static SW_FT_Error
+ft_stroker_outside( SW_FT_Stroker stroker,
+ SW_FT_Int side,
+ SW_FT_Fixed line_length )
{
- SW_FT_StrokeBorder border = stroker->borders + side;
- SW_FT_Error error;
- SW_FT_Angle rotate;
+ SW_FT_StrokeBorder border = stroker->borders + side;
+ SW_FT_Error error;
+ SW_FT_Angle rotate;
- if (stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND)
- error = ft_stroker_arcto(stroker, side);
- else {
- /* this is a mitered (pointed) or beveled (truncated) corner */
- SW_FT_Fixed sigma = 0, radius = stroker->radius;
- SW_FT_Angle theta = 0, phi = 0;
- SW_FT_Fixed thcos = 0;
- SW_FT_Bool bevel, fixed_bevel;
- rotate = SW_FT_SIDE_TO_ROTATE(side);
+ if ( stroker->line_join == SW_FT_STROKER_LINEJOIN_ROUND )
+ error = ft_stroker_arcto( stroker, side );
+ else
+ {
+ /* this is a mitered (pointed) or beveled (truncated) corner */
+ SW_FT_Fixed radius = stroker->radius;
+ SW_FT_Vector sigma;
+ SW_FT_Angle theta = 0, phi = 0;
+ SW_FT_Bool bevel, fixed_bevel;
- bevel = SW_FT_BOOL(stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL);
- fixed_bevel = SW_FT_BOOL(stroker->line_join !=
- SW_FT_STROKER_LINEJOIN_MITER_VARIABLE);
+ rotate = SW_FT_SIDE_TO_ROTATE( side );
- if (!bevel) {
- theta = SW_FT_Angle_Diff(stroker->angle_in, stroker->angle_out);
+ bevel =
+ SW_FT_BOOL( stroker->line_join == SW_FT_STROKER_LINEJOIN_BEVEL );
- if (theta == SW_FT_ANGLE_PI) {
- theta = rotate;
- phi = stroker->angle_in;
- } else {
- theta /= 2;
- phi = stroker->angle_in + theta + rotate;
- }
+ fixed_bevel =
+ SW_FT_BOOL( stroker->line_join != SW_FT_STROKER_LINEJOIN_MITER_VARIABLE );
- thcos = SW_FT_Cos(theta);
- sigma = SW_FT_MulFix(stroker->miter_limit, thcos);
+ /* check miter limit first */
+ if ( !bevel )
+ {
+ theta = SW_FT_Angle_Diff( stroker->angle_in, stroker->angle_out ) / 2;
- /* is miter limit exceeded? */
- if (sigma < 0x10000L) {
- /* don't create variable bevels for very small deviations; */
- /* SW_FT_Sin(x) = 0 for x <= 57 */
- if (fixed_bevel || ft_pos_abs(theta) > 57) bevel = TRUE;
- }
+ if ( theta == SW_FT_ANGLE_PI2 )
+ theta = -rotate;
+
+ phi = stroker->angle_in + theta + rotate;
+
+ SW_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 ( bevel ) /* this is a bevel (broken angle) */
+ {
+ if ( fixed_bevel )
{
- if (fixed_bevel) {
- /* the outer corners are simply joined together */
- SW_FT_Vector delta;
-
- /* add bevel */
- SW_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 */
- {
- /* the miter is truncated */
- SW_FT_Vector middle, delta;
- SW_FT_Fixed length;
+ /* the outer corners are simply joined together */
+ SW_FT_Vector delta;
- /* compute middle point */
- SW_FT_Vector_From_Polar(
- &middle, SW_FT_MulFix(radius, stroker->miter_limit), phi);
- middle.x += stroker->center.x;
- middle.y += stroker->center.y;
- /* compute first angle point */
- length = SW_FT_MulDiv(radius, 0x10000L - sigma,
- ft_pos_abs(SW_FT_Sin(theta)));
+ /* add bevel */
+ SW_FT_Vector_From_Polar( &delta,
+ radius,
+ stroker->angle_out + rotate );
+ delta.x += stroker->center.x;
+ delta.y += stroker->center.y;
- SW_FT_Vector_From_Polar(&delta, length, phi + rotate);
- delta.x += middle.x;
- delta.y += middle.y;
+ border->movable = FALSE;
+ error = ft_stroke_border_lineto( border, &delta, FALSE );
+ }
+ else /* variable bevel or clipped miter */
+ {
+ /* the miter is truncated */
+ SW_FT_Vector middle, delta;
+ SW_FT_Fixed coef;
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- if (error) goto Exit;
- /* compute second angle point */
- SW_FT_Vector_From_Polar(&delta, length, phi - rotate);
- delta.x += middle.x;
- delta.y += middle.y;
+ /* compute middle point and first angle point */
+ SW_FT_Vector_From_Polar( &middle,
+ SW_FT_MulFix( radius, stroker->miter_limit ),
+ phi );
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- if (error) goto Exit;
+ coef = SW_FT_DivFix( 0x10000L - sigma.x, sigma.y );
+ delta.x = SW_FT_MulFix( middle.y, coef );
+ delta.y = SW_FT_MulFix( -middle.x, coef );
- /* finally, add an end point; only needed if not lineto */
- /* (line_length is zero for curves) */
- if (line_length == 0) {
- SW_FT_Vector_From_Polar(&delta, radius,
- stroker->angle_out + rotate);
+ middle.x += stroker->center.x;
+ middle.y += stroker->center.y;
+ delta.x += middle.x;
+ delta.y += middle.y;
- delta.x += stroker->center.x;
- delta.y += stroker->center.y;
+ error = ft_stroke_border_lineto( border, &delta, FALSE );
+ if ( error )
+ goto Exit;
- error = ft_stroke_border_lineto(border, &delta, FALSE);
- }
- }
- } else /* this is a miter (intersection) */
- {
- SW_FT_Fixed length;
- SW_FT_Vector delta;
+ /* compute second angle point */
+ delta.x = middle.x - delta.x + middle.x;
+ delta.y = middle.y - delta.y + middle.y;
- length = SW_FT_DivFix(stroker->radius, thcos);
+ 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 )
+ {
+ SW_FT_Vector_From_Polar( &delta,
+ radius,
+ stroker->angle_out + rotate );
- SW_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;
+ error = ft_stroke_border_lineto( border, &delta, FALSE );
+ }
+ }
+ }
+ else /* this is a miter (intersection) */
+ {
+ SW_FT_Fixed length;
+ SW_FT_Vector delta;
- /* now add an end point; only needed if not lineto */
- /* (line_length is zero for curves) */
- if (line_length == 0) {
- SW_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);
- }
+ length = SW_FT_MulDiv( stroker->radius, stroker->miter_limit, sigma.x );
+
+ SW_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 )
+ {
+ SW_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:
+ Exit:
return error;
}