/*
 * xft.c
 *
 * Copyright(c) 2007-2023 Jianjun Jiang <8192542@qq.com>
 * Mobile phone: +86-18665388956
 * QQ: 8192542
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "xft.h"

/*
 * type
 */
typedef int64_t			XCG_FT_Int64;
typedef uint64_t		XCG_FT_UInt64;
typedef int32_t			XCG_FT_Int32;
typedef uint32_t		XCG_FT_UInt32;

#define XCG_FT_BOOL(x)	((XCG_FT_Bool)(x))
#ifndef TRUE
#define TRUE			1
#endif
#ifndef FALSE
#define FALSE			0
#endif

/*
 * math
 */
#define XCG_FT_MIN(a, b)		((a) < (b) ? (a) : (b))
#define XCG_FT_MAX(a, b)		((a) > (b) ? (a) : (b))
#define XCG_FT_ABS(a)			((a) < 0 ? -(a) : (a))
#define XCG_FT_HYPOT(x, y)		(x = XCG_FT_ABS(x), y = XCG_FT_ABS(y), x > y ? x + (3 * y >> 3) : y + (3 * x >> 3))
#define XCG_FT_ANGLE_PI			(180L << 16)
#define XCG_FT_ANGLE_2PI		(XCG_FT_ANGLE_PI * 2)
#define XCG_FT_ANGLE_PI2		(XCG_FT_ANGLE_PI / 2)
#define XCG_FT_ANGLE_PI4		(XCG_FT_ANGLE_PI / 4)

#define XCG_FT_MSB(x)			(31 - __builtin_clz(x))
#define XCG_FT_PAD_FLOOR(x, n)	((x) & ~((n)-1))
#define XCG_FT_PAD_ROUND(x, n)	XCG_FT_PAD_FLOOR((x) + ((n) / 2), n)
#define XCG_FT_PAD_CEIL(x, n)	XCG_FT_PAD_FLOOR((x) + ((n) - 1), n)

#define XCG_FT_MOVE_SIGN(x, s) \
	do { \
	if(x < 0) { \
			x = -x; \
			s = -s; \
		} \
	} while(0)

XCG_FT_Long XCG_FT_MulFix(XCG_FT_Long a, XCG_FT_Long b)
{
	XCG_FT_Int s = 1;
	XCG_FT_Long c;

	XCG_FT_MOVE_SIGN(a, s);
	XCG_FT_MOVE_SIGN(b, s);
	c = (XCG_FT_Long)(((XCG_FT_Int64)a * b + 0x8000L) >> 16);

	return (s > 0) ? c : -c;
}

XCG_FT_Long XCG_FT_MulDiv(XCG_FT_Long a, XCG_FT_Long b, XCG_FT_Long c)
{
	XCG_FT_Int s = 1;
	XCG_FT_Long d;

	XCG_FT_MOVE_SIGN(a, s);
	XCG_FT_MOVE_SIGN(b, s);
	XCG_FT_MOVE_SIGN(c, s);
	d = (XCG_FT_Long)(c > 0 ? ((XCG_FT_Int64)a * b + (c >> 1)) / c : 0x7FFFFFFFL);
	return (s > 0) ? d : -d;
}

XCG_FT_Long XCG_FT_DivFix(XCG_FT_Long a, XCG_FT_Long b)
{
	XCG_FT_Int s = 1;
	XCG_FT_Long q;

	XCG_FT_MOVE_SIGN(a, s);
	XCG_FT_MOVE_SIGN(b, s);
	q = (XCG_FT_Long)(b > 0 ? (((XCG_FT_UInt64)a << 16) + (b >> 1)) / b : 0x7FFFFFFFL);
	return (s < 0 ? -q : q);
}

#define XCG_FT_TRIG_SCALE 		(0xDBD95B16UL)
#define XCG_FT_TRIG_SAFE_MSB	(29)
#define XCG_FT_TRIG_MAX_ITERS	(23)

static const XCG_FT_Fixed ft_trig_arctan_table[] = {
	1740967L, 919879L, 466945L, 234379L, 117304L, 58666L, 29335L, 14668L,
	7334L,    3667L,   1833L,   917L,    458L,    229L,   115L,   57L,
	29L,      14L,     7L,      4L,      2L,      1L
};

static XCG_FT_Fixed ft_trig_downscale(XCG_FT_Fixed val)
{
	XCG_FT_Fixed s;
	XCG_FT_Int64 v;

	s = val;
	val = XCG_FT_ABS(val);
	v = (val * (XCG_FT_Int64)XCG_FT_TRIG_SCALE) + 0x100000000UL;
	val = (XCG_FT_Fixed)(v >> 32);
	return (s >= 0) ? val : -val;
}

static XCG_FT_Int ft_trig_prenorm(XCG_FT_Vector *vec)
{
	XCG_FT_Pos x, y;
	XCG_FT_Int shift;

	x = vec->x;
	y = vec->y;

	shift = XCG_FT_MSB(XCG_FT_ABS(x) | XCG_FT_ABS(y));

	if(shift <= XCG_FT_TRIG_SAFE_MSB)
	{
		shift = XCG_FT_TRIG_SAFE_MSB - shift;
		vec->x = (XCG_FT_Pos)((XCG_FT_ULong)x << shift);
		vec->y = (XCG_FT_Pos)((XCG_FT_ULong)y << shift);
	}
	else
	{
		shift -= XCG_FT_TRIG_SAFE_MSB;
		vec->x = x >> shift;
		vec->y = y >> shift;
		shift = -shift;
	}
	return shift;
}

static void ft_trig_pseudo_rotate(XCG_FT_Vector *vec, XCG_FT_Angle theta)
{
	XCG_FT_Int i;
	XCG_FT_Fixed x, y, xtemp, b;
	const XCG_FT_Fixed *arctanptr;

	x = vec->x;
	y = vec->y;
	while(theta < -XCG_FT_ANGLE_PI4)
	{
		xtemp = y;
		y = -x;
		x = xtemp;
		theta += XCG_FT_ANGLE_PI2;
	}
	while(theta > XCG_FT_ANGLE_PI4)
	{
		xtemp = -y;
		y = x;
		x = xtemp;
		theta -= XCG_FT_ANGLE_PI2;
	}
	arctanptr = ft_trig_arctan_table;
	for(i = 1, b = 1; i < XCG_FT_TRIG_MAX_ITERS; b <<= 1, i++)
	{
		XCG_FT_Fixed v1 = ((y + b) >> i);
		XCG_FT_Fixed v2 = ((x + b) >> i);
		if(theta < 0)
		{
			xtemp = x + v1;
			y = y - v2;
			x = xtemp;
			theta += *arctanptr++;
		}
		else
		{
			xtemp = x - v1;
			y = y + v2;
			x = xtemp;
			theta -= *arctanptr++;
		}
	}
	vec->x = x;
	vec->y = y;
}

static void ft_trig_pseudo_polarize(XCG_FT_Vector *vec)
{
	XCG_FT_Angle theta;
	XCG_FT_Int i;
	XCG_FT_Fixed x, y, xtemp, b;
	const XCG_FT_Fixed *arctanptr;

	x = vec->x;
	y = vec->y;
	if(y > x)
	{
		if(y > -x)
		{
			theta = XCG_FT_ANGLE_PI2;
			xtemp = y;
			y = -x;
			x = xtemp;
		}
		else
		{
			theta = y > 0 ? XCG_FT_ANGLE_PI : -XCG_FT_ANGLE_PI;
			x = -x;
			y = -y;
		}
	}
	else
	{
		if(y < -x)
		{
			theta = -XCG_FT_ANGLE_PI2;
			xtemp = -y;
			y = x;
			x = xtemp;
		}
		else
		{
			theta = 0;
		}
	}
	arctanptr = ft_trig_arctan_table;
	for(i = 1, b = 1; i < XCG_FT_TRIG_MAX_ITERS; b <<= 1, i++)
	{
		XCG_FT_Fixed v1 = ((y + b) >> i);
		XCG_FT_Fixed v2 = ((x + b) >> i);
		if(y > 0)
		{
			xtemp = x + v1;
			y = y - v2;
			x = xtemp;
			theta += *arctanptr++;
		}
		else
		{
			xtemp = x - v1;
			y = y + v2;
			x = xtemp;
			theta -= *arctanptr++;
		}
	}
	if(theta >= 0)
		theta = XCG_FT_PAD_ROUND(theta, 32);
	else
		theta = -XCG_FT_PAD_ROUND(-theta, 32);
	vec->x = x;
	vec->y = theta;
}

XCG_FT_Fixed XCG_FT_Cos(XCG_FT_Angle angle)
{
	XCG_FT_Vector v;

	v.x = XCG_FT_TRIG_SCALE >> 8;
	v.y = 0;
	ft_trig_pseudo_rotate(&v, angle);
	return (v.x + 0x80L) >> 8;
}

XCG_FT_Fixed XCG_FT_Sin(XCG_FT_Angle angle)
{
	return XCG_FT_Cos(XCG_FT_ANGLE_PI2 - angle);
}

XCG_FT_Fixed XCG_FT_Tan(XCG_FT_Angle angle)
{
	XCG_FT_Vector v;

	v.x = XCG_FT_TRIG_SCALE >> 8;
	v.y = 0;
	ft_trig_pseudo_rotate(&v, angle);
	return XCG_FT_DivFix(v.y, v.x);
}

XCG_FT_Angle XCG_FT_Atan2(XCG_FT_Fixed dx, XCG_FT_Fixed dy)
{
	XCG_FT_Vector v;

	if(dx == 0 && dy == 0)
		return 0;
	v.x = dx;
	v.y = dy;
	ft_trig_prenorm(&v);
	ft_trig_pseudo_polarize(&v);
	return v.y;
}

void XCG_FT_Vector_Unit(XCG_FT_Vector *vec, XCG_FT_Angle angle)
{
	vec->x = XCG_FT_TRIG_SCALE >> 8;
	vec->y = 0;
	ft_trig_pseudo_rotate(vec, angle);
	vec->x = (vec->x + 0x80L) >> 8;
	vec->y = (vec->y + 0x80L) >> 8;
}

void XCG_FT_Vector_Rotate(XCG_FT_Vector *vec, XCG_FT_Angle angle)
{
	XCG_FT_Int shift;
	XCG_FT_Vector v = *vec;

	if(v.x == 0 && v.y == 0)
		return;
	shift = ft_trig_prenorm(&v);
	ft_trig_pseudo_rotate(&v, angle);
	v.x = ft_trig_downscale(v.x);
	v.y = ft_trig_downscale(v.y);

	if(shift > 0)
	{
		XCG_FT_Int32 half = (XCG_FT_Int32)1L << (shift - 1);

		vec->x = (v.x + half - (v.x < 0)) >> shift;
		vec->y = (v.y + half - (v.y < 0)) >> shift;
	}
	else
	{
		shift = -shift;
		vec->x = (XCG_FT_Pos)((XCG_FT_ULong)v.x << shift);
		vec->y = (XCG_FT_Pos)((XCG_FT_ULong)v.y << shift);
	}
}

XCG_FT_Fixed XCG_FT_Vector_Length(XCG_FT_Vector *vec)
{
	XCG_FT_Int shift;
	XCG_FT_Vector v;

	v = *vec;
	if(v.x == 0)
	{
		return XCG_FT_ABS(v.y);
	}
	else if(v.y == 0)
	{
		return XCG_FT_ABS(v.x);
	}

	shift = ft_trig_prenorm(&v);
	ft_trig_pseudo_polarize(&v);
	v.x = ft_trig_downscale(v.x);
	if(shift > 0)
		return (v.x + (1 << (shift - 1))) >> shift;

	return (XCG_FT_Fixed)((XCG_FT_UInt32)v.x << -shift);
}

void XCG_FT_Vector_Polarize(XCG_FT_Vector *vec, XCG_FT_Fixed *length, XCG_FT_Angle *angle)
{
	XCG_FT_Int shift;
	XCG_FT_Vector v;

	v = *vec;
	if(v.x == 0 && v.y == 0)
		return;
	shift = ft_trig_prenorm(&v);
	ft_trig_pseudo_polarize(&v);
	v.x = ft_trig_downscale(v.x);

	*length = (shift >= 0) ? (v.x >> shift) : (XCG_FT_Fixed)((XCG_FT_UInt32)v.x << -shift);
	*angle = v.y;
}

void XCG_FT_Vector_From_Polar(XCG_FT_Vector *vec, XCG_FT_Fixed length, XCG_FT_Angle angle)
{
	vec->x = length;
	vec->y = 0;

	XCG_FT_Vector_Rotate(vec, angle);
}

XCG_FT_Angle XCG_FT_Angle_Diff(XCG_FT_Angle angle1, XCG_FT_Angle angle2)
{
	XCG_FT_Angle delta = angle2 - angle1;

	while(delta <= -XCG_FT_ANGLE_PI)
		delta += XCG_FT_ANGLE_2PI;

	while(delta > XCG_FT_ANGLE_PI)
		delta -= XCG_FT_ANGLE_2PI;

	return delta;
}

/*
 * raster
 */
typedef long			TCoord;
typedef long			TPos;
typedef long			TArea;
typedef ptrdiff_t		XCG_FT_PtrDist;

#define xcg_ft_setjmp	setjmp
#define xcg_ft_longjmp 	longjmp
#define xcg_ft_jmp_buf	jmp_buf

#define ErrRaster_Invalid_Mode      -2
#define ErrRaster_Invalid_Outline   -1
#define ErrRaster_Invalid_Argument  -3
#define ErrRaster_Memory_Overflow   -4
#define ErrRaster_OutOfMemory       -6

#define XCG_FT_MINIMUM_POOL_SIZE 	8192
#define XCG_FT_MAX_GRAY_SPANS		256

#define RAS_ARG   					PWorker worker
#define RAS_ARG_					PWorker worker,
#define RAS_VAR 					worker
#define RAS_VAR_					worker,
#define ras							(*worker)

#define PIXEL_BITS		8
#define ONE_PIXEL		(1L << PIXEL_BITS)
#define TRUNC(x)		(TCoord)((x) >> PIXEL_BITS)
#define FRACT(x)		(TCoord)((x) & (ONE_PIXEL - 1))
#if PIXEL_BITS >= 6
#define UPSCALE(x)		((x) * (ONE_PIXEL >> 6))
#define DOWNSCALE(x)	((x) >> (PIXEL_BITS - 6))
#else
#define UPSCALE(x)		((x) >> (6 - PIXEL_BITS))
#define DOWNSCALE(x)	((x) * (64 >> PIXEL_BITS))
#endif

#define XCG_FT_DIV_MOD(type, dividend, divisor, quotient, remainder) \
	do { \
		(quotient) = (type)((dividend) / (divisor)); \
		(remainder) = (type)((dividend) % (divisor)); \
		if((remainder) < 0) \
		{ \
			(quotient)--; \
			(remainder) += (type)(divisor); \
		} \
	} while(0)

typedef struct TCell_ * PCell;
typedef struct TCell_ {
	int x;
	int cover;
	TArea area;
	PCell next;
} TCell;

typedef struct TWorker_ {
	TCoord ex, ey;
	TPos min_ex, max_ex;
	TPos min_ey, max_ey;
	TPos count_ex, count_ey;
	TArea area;
	int cover;
	int invalid;
	PCell cells;
	XCG_FT_PtrDist max_cells;
	XCG_FT_PtrDist num_cells;
	TPos x, y;
	XCG_FT_Outline outline;
	XCG_FT_BBox clip_box;
	XCG_FT_Span gray_spans[XCG_FT_MAX_GRAY_SPANS];
	int num_gray_spans;
	int skip_spans;
	XCG_FT_Raster_Span_Func render_span;
	void *render_span_data;
	int band_size;
	int band_shoot;
	xcg_ft_jmp_buf jump_buffer;
	void *buffer;
	long buffer_size;
	PCell *ycells;
	TPos ycount;
} TWorker, *PWorker;

static void gray_init_cells(RAS_ARG_ void* buffer, long byte_size)
{
	ras.buffer = buffer;
	ras.buffer_size = byte_size;
	ras.ycells = (PCell*)buffer;
	ras.cells = NULL;
	ras.max_cells = 0;
	ras.num_cells = 0;
	ras.area = 0;
	ras.cover = 0;
	ras.invalid = 1;
}

static void gray_compute_cbox( RAS_ARG)
{
	XCG_FT_Outline *outline = &ras.outline;
	XCG_FT_Vector *vec = outline->points;
	XCG_FT_Vector *limit = vec + outline->n_points;

	if(outline->n_points <= 0)
	{
		ras.min_ex = ras.max_ex = 0;
		ras.min_ey = ras.max_ey = 0;
		return;
	}
	ras.min_ex = ras.max_ex = vec->x;
	ras.min_ey = ras.max_ey = vec->y;
	vec++;
	for(; vec < limit; vec++)
	{
		TPos x = vec->x;
		TPos y = vec->y;
		if(x < ras.min_ex)
			ras.min_ex = x;
		if(x > ras.max_ex)
			ras.max_ex = x;
		if(y < ras.min_ey)
			ras.min_ey = y;
		if(y > ras.max_ey)
			ras.max_ey = y;
	}
	ras.min_ex = ras.min_ex >> 6;
	ras.min_ey = ras.min_ey >> 6;
	ras.max_ex = (ras.max_ex + 63) >> 6;
	ras.max_ey = (ras.max_ey + 63) >> 6;
}

static PCell gray_find_cell(RAS_ARG)
{
	PCell *pcell, cell;
	TPos x = ras.ex;

	if(x > ras.count_ex)
		x = ras.count_ex;
	pcell = &ras.ycells[ras.ey];
	for(;;)
	{
		cell = *pcell;
		if(cell == NULL || cell->x > x)
			break;
		if(cell->x == x)
			goto Exit;
		pcell = &cell->next;
	}
	if(ras.num_cells >= ras.max_cells)
		xcg_ft_longjmp(ras.jump_buffer, 1);
	cell = ras.cells + ras.num_cells++;
	cell->x = x;
	cell->area = 0;
	cell->cover = 0;
	cell->next = *pcell;
	*pcell = cell;
Exit:
	return cell;
}

static void gray_record_cell( RAS_ARG)
{
	if( ras.area | ras.cover)
	{
		PCell cell = gray_find_cell( RAS_VAR);
		cell->area += ras.area;
		cell->cover += ras.cover;
	}
}

static void gray_set_cell( RAS_ARG_ TCoord ex, TCoord ey)
{
	ey -= ras.min_ey;
	if(ex > ras.max_ex)
		ex = ras.max_ex;
	ex -= ras.min_ex;
	if(ex < 0)
		ex = -1;
	if(ex != ras.ex || ey != ras.ey)
	{
		if(!ras.invalid)
			gray_record_cell( RAS_VAR);
		ras.area = 0;
		ras.cover = 0;
		ras.ex = ex;
		ras.ey = ey;
	}
	ras.invalid = ((unsigned int)ey >= (unsigned int)ras.count_ey || ex >= ras.count_ex);
}

static void gray_start_cell( RAS_ARG_ TCoord ex, TCoord ey)
{
	if(ex > ras.max_ex)
		ex = (TCoord)( ras.max_ex);
	if(ex < ras.min_ex)
		ex = (TCoord)( ras.min_ex - 1);
	ras.area = 0;
	ras.cover = 0;
	ras.ex = ex - ras.min_ex;
	ras.ey = ey - ras.min_ey;
	ras.invalid = 0;
	gray_set_cell( RAS_VAR_ ex, ey);
}

static void gray_render_scanline( RAS_ARG_ TCoord ey, TPos x1, TCoord y1, TPos x2, TCoord y2)
{
	TCoord ex1, ex2, fx1, fx2, first, dy, delta, mod;
	TPos p, dx;
	int incr;

	ex1 = TRUNC(x1);
	ex2 = TRUNC(x2);
	if(y1 == y2)
	{
		gray_set_cell( RAS_VAR_ ex2, ey);
		return;
	}
	fx1 = FRACT(x1);
	fx2 = FRACT(x2);
	if(ex1 == ex2)
		goto End;
	dx = x2 - x1;
	dy = y2 - y1;
	if(dx > 0)
	{
		p = ( ONE_PIXEL - fx1) * dy;
		first = ONE_PIXEL;
		incr = 1;
	}
	else
	{
		p = fx1 * dy;
		first = 0;
		incr = -1;
		dx = -dx;
	}
	XCG_FT_DIV_MOD(TCoord, p, dx, delta, mod);
	ras.area += (TArea)(fx1 + first) * delta;
	ras.cover += delta;
	y1 += delta;
	ex1 += incr;
	gray_set_cell( RAS_VAR_ ex1, ey);
	if(ex1 != ex2)
	{
		TCoord lift, rem;
		p = ONE_PIXEL * dy;
		XCG_FT_DIV_MOD(TCoord, p, dx, lift, rem);
		do {
			delta = lift;
			mod += rem;
			if(mod >= (TCoord)dx)
			{
				mod -= (TCoord)dx;
				delta++;
			}
			ras.area += (TArea)( ONE_PIXEL * delta);
			ras.cover += delta;
			y1 += delta;
			ex1 += incr;
			gray_set_cell( RAS_VAR_ ex1, ey);
		} while(ex1 != ex2);
	}
	fx1 = ONE_PIXEL - first;
End:
	dy = y2 - y1;
	ras.area += (TArea)((fx1 + fx2) * dy);
	ras.cover += dy;
}

static void gray_render_line( RAS_ARG_ TPos to_x, TPos to_y)
{
	TCoord ey1, ey2, fy1, fy2, first, delta, mod;
	TPos p, dx, dy, x, x2;
	int incr;

	ey1 = TRUNC(ras.y);
	ey2 = TRUNC(to_y);
	if((ey1 >= ras.max_ey && ey2 >= ras.max_ey) || (ey1 < ras.min_ey && ey2 < ras.min_ey))
		goto End;
	fy1 = FRACT(ras.y);
	fy2 = FRACT(to_y);
	if(ey1 == ey2)
	{
		gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, to_x, fy2);
		goto End;
	}
	dx = to_x - ras.x;
	dy = to_y - ras.y;
	if(dx == 0)
	{
		TCoord ex = TRUNC(ras.x);
		TCoord two_fx = FRACT( ras.x ) << 1;
		TPos area, max_ey1;

		if(dy > 0)
		{
			first = ONE_PIXEL;
		}
		else
		{
			first = 0;
		}
		delta = first - fy1;
		ras.area += (TArea)two_fx * delta;
		ras.cover += delta;
		delta = first + first - ONE_PIXEL;
		area = (TArea)two_fx * delta;
		max_ey1 = ras.count_ey + ras.min_ey;
		if(dy < 0)
		{
			if(ey1 > max_ey1)
			{
				ey1 = (max_ey1 > ey2) ? max_ey1 : ey2;
				gray_set_cell(&ras, ex, ey1);
			}
			else
			{
				ey1--;
				gray_set_cell(&ras, ex, ey1);
			}
			while(ey1 > ey2 && ey1 >= ras.min_ey)
			{
				ras.area += area;
				ras.cover += delta;
				ey1--;

				gray_set_cell(&ras, ex, ey1);
			}
			if(ey1 != ey2)
			{
				ey1 = ey2;
				gray_set_cell(&ras, ex, ey1);
			}
		}
		else
		{
			if(ey1 < ras.min_ey)
			{
				ey1 = (ras.min_ey < ey2) ? ras.min_ey : ey2;
				gray_set_cell(&ras, ex, ey1);
			}
			else
			{
				ey1++;
				gray_set_cell(&ras, ex, ey1);
			}
			while(ey1 < ey2 && ey1 < max_ey1)
			{
				ras.area += area;
				ras.cover += delta;
				ey1++;

				gray_set_cell(&ras, ex, ey1);
			}
			if(ey1 != ey2)
			{
				ey1 = ey2;
				gray_set_cell(&ras, ex, ey1);
			}
		}
		delta = (int)(fy2 - ONE_PIXEL + first);
		ras.area += (TArea)two_fx * delta;
		ras.cover += delta;
		goto End;
	}
	if(dy > 0)
	{
		p = ( ONE_PIXEL - fy1) * dx;
		first = ONE_PIXEL;
		incr = 1;
	}
	else
	{
		p = fy1 * dx;
		first = 0;
		incr = -1;
		dy = -dy;
	}
	XCG_FT_DIV_MOD(TCoord, p, dy, delta, mod);
	x = ras.x + delta;
	gray_render_scanline( RAS_VAR_ ey1, ras.x, fy1, x, (TCoord)first);
	ey1 += incr;
	gray_set_cell( RAS_VAR_ TRUNC( x ), ey1);
	if(ey1 != ey2)
	{
		TCoord lift, rem;
		p = ONE_PIXEL * dx;
		XCG_FT_DIV_MOD(TCoord, p, dy, lift, rem);
		do {
			delta = lift;
			mod += rem;
			if(mod >= (TCoord)dy)
			{
				mod -= (TCoord)dy;
				delta++;
			}
			x2 = x + delta;
			gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, x2, first);
			x = x2;
			ey1 += incr;
			gray_set_cell( RAS_VAR_ TRUNC( x ), ey1);
		} while(ey1 != ey2);
	}
	gray_render_scanline( RAS_VAR_ ey1, x, ONE_PIXEL - first, to_x, fy2);
End:
	ras.x = to_x;
	ras.y = to_y;
}

static void gray_split_conic(XCG_FT_Vector * base)
{
	TPos a, b;

	base[4].x = base[2].x;
	b = base[1].x;
	a = base[3].x = (base[2].x + b) / 2;
	b = base[1].x = (base[0].x + b) / 2;
	base[2].x = (a + b) / 2;

	base[4].y = base[2].y;
	b = base[1].y;
	a = base[3].y = (base[2].y + b) / 2;
	b = base[1].y = (base[0].y + b) / 2;
	base[2].y = (a + b) / 2;
}

static void gray_render_conic( RAS_ARG_ const XCG_FT_Vector * control, const XCG_FT_Vector * to)
{
	XCG_FT_Vector bez_stack[16 * 2 + 1];
	XCG_FT_Vector *arc = bez_stack;
	TPos dx, dy;
	int draw, split;

	arc[0].x = UPSCALE(to->x);
	arc[0].y = UPSCALE(to->y);
	arc[1].x = UPSCALE(control->x);
	arc[1].y = UPSCALE(control->y);
	arc[2].x = ras.x;
	arc[2].y = ras.y;

	if(( TRUNC( arc[0].y ) >= ras.max_ey &&
		TRUNC( arc[1].y ) >= ras.max_ey &&
		TRUNC( arc[2].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))
	{
		ras.x = arc[0].x;
		ras.y = arc[0].y;
		return;
	}
	dx = XCG_FT_ABS(arc[2].x + arc[0].x - 2 * arc[1].x);
	dy = XCG_FT_ABS(arc[2].y + arc[0].y - 2 * arc[1].y);
	if(dx < dy)
		dx = dy;
	draw = 1;
	while(dx > ONE_PIXEL / 4)
	{
		dx >>= 2;
		draw <<= 1;
	}
	do {
		split = 1;
		while((draw & split) == 0)
		{
			gray_split_conic(arc);
			arc += 2;
			split <<= 1;
		}
		gray_render_line( RAS_VAR_ arc[0].x, arc[0].y);
		arc -= 2;
	} while(--draw);
}

static void gray_split_cubic(XCG_FT_Vector * base)
{
	TPos a, b, c, d;

	base[6].x = base[3].x;
	c = base[1].x;
	d = base[2].x;
	base[1].x = a = (base[0].x + c) / 2;
	base[5].x = b = (base[3].x + d) / 2;
	c = (c + d) / 2;
	base[2].x = a = (a + c) / 2;
	base[4].x = b = (b + c) / 2;
	base[3].x = (a + b) / 2;

	base[6].y = base[3].y;
	c = base[1].y;
	d = base[2].y;
	base[1].y = a = (base[0].y + c) / 2;
	base[5].y = b = (base[3].y + d) / 2;
	c = (c + d) / 2;
	base[2].y = a = (a + c) / 2;
	base[4].y = b = (b + c) / 2;
	base[3].y = (a + b) / 2;
}

static void gray_render_cubic(RAS_ARG_ const XCG_FT_Vector * control1, const XCG_FT_Vector * control2, const XCG_FT_Vector * to)
{
	XCG_FT_Vector bez_stack[16 * 3 + 1];
	XCG_FT_Vector * arc = bez_stack;
	TPos dx, dy, dx_, dy_;
	TPos dx1, dy1, dx2, dy2;
	TPos L, s, s_limit;

	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;

	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;
	}
	for(;;)
	{
		dx = dx_ = arc[3].x - arc[0].x;
		dy = dy_ = arc[3].y - arc[0].y;
		L = XCG_FT_HYPOT(dx_, dy_);
		if(L >= (1 << 23))
			goto Split;
		s_limit = L * (TPos)( ONE_PIXEL / 6);
		dx1 = arc[1].x - arc[0].x;
		dy1 = arc[1].y - arc[0].y;
		s = XCG_FT_ABS(dy * dx1 - dx * dy1);
		if(s > s_limit)
			goto Split;
		dx2 = arc[2].x - arc[0].x;
		dy2 = arc[2].y - arc[0].y;
		s = XCG_FT_ABS(dy * dx2 - dx * dy2);
		if(s > s_limit)
			goto Split;
		if(dx1 * (dx1 - dx) + dy1 * (dy1 - dy) > 0 || dx2 * (dx2 - dx) + dy2 * (dy2 - dy) > 0)
			goto Split;
		gray_render_line( RAS_VAR_ arc[0].x, arc[0].y);
		if(arc == bez_stack)
			return;
		arc -= 3;
		continue;
Split:
		gray_split_cubic(arc);
		arc += 3;
	}
}

static int gray_move_to(const XCG_FT_Vector *to, PWorker worker)
{
	TPos x, y;

	if(!ras.invalid)
		gray_record_cell(worker);
	x = UPSCALE(to->x);
	y = UPSCALE(to->y);
	gray_start_cell(worker, TRUNC(x), TRUNC(y));
	ras.x = x;
	ras.y = y;
	return 0;
}

static void gray_hline( RAS_ARG_ TCoord x, TCoord y, TPos area, int acount)
{
	int coverage;

	coverage = (int)(area >> ( PIXEL_BITS * 2 + 1 - 8));
	if(coverage < 0)
		coverage = -coverage;
	if( ras.outline.flags & XCG_FT_OUTLINE_EVEN_ODD_FILL)
	{
		coverage &= 511;
		if(coverage > 256)
			coverage = 512 - coverage;
		else if(coverage == 256)
			coverage = 255;
	}
	else
	{
		if(coverage >= 256)
			coverage = 255;
	}
	y += (TCoord)ras.min_ey;
	x += (TCoord)ras.min_ex;
	if(x >= (1 << 23))
		x = (1 << 23) - 1;
	if(y >= (1 << 23))
		y = (1 << 23) - 1;
	if(coverage)
	{
		XCG_FT_Span *span;
		int count;
		int skip;
		count = ras.num_gray_spans;
		span = ras.gray_spans + count - 1;
		if(count > 0 && span->y == y && span->x + span->len == x && span->coverage == coverage)
		{
			span->len = span->len + acount;
			return;
		}
		if(count >= XCG_FT_MAX_GRAY_SPANS)
		{
			if( ras.render_span && count > ras.skip_spans)
			{
				skip = ras.skip_spans > 0 ? ras.skip_spans : 0;
				ras.render_span( ras.num_gray_spans - skip,
				ras.gray_spans + skip,
				ras.render_span_data);
			}
			ras.skip_spans -= ras.num_gray_spans;
			ras.num_gray_spans = 0;
			span = ras.gray_spans;
		}
		else
			span++;
		span->x = x;
		span->len = acount;
		span->y = y;
		span->coverage = (unsigned char)coverage;
		ras.num_gray_spans++;
	}
}

static void gray_sweep(RAS_ARG)
{
	int yindex;

	if( ras.num_cells == 0)
		return;
	for(yindex = 0; yindex < ras.ycount; yindex++)
	{
		PCell cell = ras.ycells[yindex];
		TCoord cover = 0;
		TCoord x = 0;

		for(; cell != NULL; cell = cell->next)
		{
			TArea area;
			if(cell->x > x && cover != 0)
				gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2), cell->x - x);
			cover += cell->cover;
			area = cover * ( ONE_PIXEL * 2) - cell->area;
			if(area != 0 && cell->x >= 0)
				gray_hline( RAS_VAR_ cell->x, yindex, area, 1);
			x = cell->x + 1;
		}
		if( ras.count_ex > x && cover != 0)
			gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2), ras.count_ex - x);
	}
}

static int XCG_FT_Outline_Decompose(const XCG_FT_Outline * outline, void * user)
{
#undef SCALED
#define SCALED( x )  (x)
	XCG_FT_Vector v_last;
	XCG_FT_Vector v_control;
	XCG_FT_Vector v_start;
	XCG_FT_Vector * point;
	XCG_FT_Vector * limit;
	char * tags;
	int n;
	int first;
	int error;
	char tag;

	if(!outline)
		return ErrRaster_Invalid_Outline;
	first = 0;
	for(n = 0; n < outline->n_contours; n++)
	{
		int last;
		last = outline->contours[n];
		if(last < 0)
			goto Invalid_Outline;
		limit = outline->points + last;
		v_start = outline->points[first];
		v_start.x = SCALED(v_start.x);
		v_start.y = SCALED(v_start.y);
		v_last = outline->points[last];
		v_last.x = SCALED(v_last.x);
		v_last.y = SCALED(v_last.y);
		v_control = v_start;
		point = outline->points + first;
		tags = outline->tags + first;
		tag = XCG_FT_CURVE_TAG(tags[0]);
		if(tag == XCG_FT_CURVE_TAG_CUBIC)
			goto Invalid_Outline;
		if(tag == XCG_FT_CURVE_TAG_CONIC)
		{
			if( XCG_FT_CURVE_TAG( outline->tags[last] ) == XCG_FT_CURVE_TAG_ON)
			{
				v_start = v_last;
				limit--;
			}
			else
			{
				v_start.x = (v_start.x + v_last.x) / 2;
				v_start.y = (v_start.y + v_last.y) / 2;
				v_last = v_start;
			}
			point--;
			tags--;
		}
		error = gray_move_to(&v_start, user);
		if(error)
			goto Exit;
		while(point < limit)
		{
			point++;
			tags++;
			tag = XCG_FT_CURVE_TAG(tags[0]);
			switch(tag)
			{
			case XCG_FT_CURVE_TAG_ON:
			{
				XCG_FT_Vector vec;
				vec.x = SCALED(point->x);
				vec.y = SCALED(point->y);
				gray_render_line(user, UPSCALE(vec.x), UPSCALE(vec.y));
				continue;
			}
			case XCG_FT_CURVE_TAG_CONIC:
			{
				v_control.x = SCALED(point->x);
				v_control.y = SCALED(point->y);
Do_Conic:
				if(point < limit)
				{
					XCG_FT_Vector vec;
					XCG_FT_Vector v_middle;
					point++;
					tags++;
					tag = XCG_FT_CURVE_TAG(tags[0]);
					vec.x = SCALED(point->x);
					vec.y = SCALED(point->y);
					if(tag == XCG_FT_CURVE_TAG_ON)
					{
						gray_render_conic(user, &v_control, &vec);
						continue;
					}
					if(tag != XCG_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;
					gray_render_conic(user, &v_control, &v_middle);
					v_control = vec;
					goto Do_Conic;
				}
				gray_render_conic(user, &v_control, &v_start);
				goto Close;
			}
			default:
			{
				XCG_FT_Vector vec1, vec2;
				if(point + 1 > limit ||
				XCG_FT_CURVE_TAG( tags[1] ) != XCG_FT_CURVE_TAG_CUBIC)
					goto Invalid_Outline;
				point += 2;
				tags += 2;
				vec1.x = SCALED(point[-2].x);
				vec1.y = SCALED(point[-2].y);
				vec2.x = SCALED(point[-1].x);
				vec2.y = SCALED(point[-1].y);
				if(point <= limit)
				{
					XCG_FT_Vector vec;
					vec.x = SCALED(point->x);
					vec.y = SCALED(point->y);
					gray_render_cubic(user, &vec1, &vec2, &vec);
					continue;
				}
				gray_render_cubic(user, &vec1, &vec2, &v_start);
				goto Close;
			}
			}
		}
		gray_render_line(user, UPSCALE(v_start.x), UPSCALE(v_start.y));
Close:
		first = last + 1;
	}
	return 0;
Exit:
	return error;
Invalid_Outline:
	return ErrRaster_Invalid_Outline;
}

typedef struct TBand_ {
	TPos min, max;
} TBand;

static int gray_convert_glyph_inner(RAS_ARG)
{
	volatile int error = 0;

	if( xcg_ft_setjmp( ras.jump_buffer) == 0)
	{
		error = XCG_FT_Outline_Decompose(&ras.outline, &ras);
		if(!ras.invalid)
			gray_record_cell( RAS_VAR);
	}
	else
	{
		error = ErrRaster_Memory_Overflow;
	}
	return error;
}

static int gray_convert_glyph(RAS_ARG)
{
	TBand bands[40];
	TBand *volatile band;
	int volatile n, num_bands;
	TPos volatile min, max, max_y;
	XCG_FT_BBox *clip;
	int skip;

	ras.num_gray_spans = 0;
	gray_compute_cbox( RAS_VAR);
	clip = &ras.clip_box;
	if( ras.max_ex <= clip->xMin || ras.min_ex >= clip->xMax || ras.max_ey <= clip->yMin || ras.min_ey >= clip->yMax)
		return 0;
	if( ras.min_ex < clip->xMin)
		ras.min_ex = clip->xMin;
	if( ras.min_ey < clip->yMin)
		ras.min_ey = clip->yMin;
	if( ras.max_ex > clip->xMax)
		ras.max_ex = clip->xMax;
	if( ras.max_ey > clip->yMax)
		ras.max_ey = clip->yMax;
	ras.count_ex = ras.max_ex - ras.min_ex;
	ras.count_ey = ras.max_ey - ras.min_ey;
	num_bands = (int)(( ras.max_ey - ras.min_ey) / ras.band_size);
	if(num_bands == 0)
		num_bands = 1;
	if(num_bands >= 39)
		num_bands = 39;
	ras.band_shoot = 0;
	min = ras.min_ey;
	max_y = ras.max_ey;
	for(n = 0; n < num_bands; n++, min = max)
	{
		max = min + ras.band_size;
		if(n == num_bands - 1 || max > max_y)
			max = max_y;
		bands[0].min = min;
		bands[0].max = max;
		band = bands;
		while(band >= bands)
		{
			TPos bottom, top, middle;
			int error;
			{
				PCell cells_max;
				int yindex;
				int cell_start, cell_end, cell_mod;
				ras.ycells = (PCell*)ras.buffer;
				ras.ycount = band->max - band->min;
				cell_start = sizeof(PCell) * ras.ycount;
				cell_mod = cell_start % sizeof(TCell);
				if(cell_mod > 0)
					cell_start += sizeof(TCell) - cell_mod;
				cell_end = ras.buffer_size;
				cell_end -= cell_end % sizeof(TCell);
				cells_max = (PCell)((char*)ras.buffer + cell_end);
				ras.cells = (PCell)((char*)ras.buffer + cell_start);
				if( ras.cells >= cells_max)
					goto ReduceBands;
				ras.max_cells = (int)(cells_max - ras.cells);
				if( ras.max_cells < 2)
					goto ReduceBands;
				for(yindex = 0; yindex < ras.ycount; yindex++)
					ras.ycells[yindex] = NULL;
			}
			ras.num_cells = 0;
			ras.invalid = 1;
			ras.min_ey = band->min;
			ras.max_ey = band->max;
			ras.count_ey = band->max - band->min;
			error = gray_convert_glyph_inner( RAS_VAR);
			if(!error)
			{
				gray_sweep( RAS_VAR);
				band--;
				continue;
			}
			else if(error != ErrRaster_Memory_Overflow)
				return 1;
ReduceBands:
			bottom = band->min;
			top = band->max;
			middle = bottom + ((top - bottom) >> 1);
			if(middle == bottom)
			{
				return ErrRaster_OutOfMemory;
			}
			if(bottom - top >= ras.band_size)
				ras.band_shoot++;
			band[1].min = bottom;
			band[1].max = middle;
			band[0].min = middle;
			band[0].max = top;
			band++;
		}
	}
	if( ras.render_span && ras.num_gray_spans > ras.skip_spans)
	{
		skip = ras.skip_spans > 0 ? ras.skip_spans : 0;
		ras.render_span( ras.num_gray_spans - skip,
		ras.gray_spans + skip,
		ras.render_span_data);
	}
	ras.skip_spans -= ras.num_gray_spans;
	if( ras.band_shoot > 8 && ras.band_size > 16)
		ras.band_size = ras.band_size / 2;
	return 0;
}

static int gray_raster_render(RAS_ARG_ void * buffer, long buffer_size, const XCG_FT_Raster_Params * params)
{
	const XCG_FT_Outline *outline = (const XCG_FT_Outline*)params->source;
	if(outline == NULL)
		return ErrRaster_Invalid_Outline;
	if(outline->n_points == 0 || outline->n_contours <= 0)
		return 0;
	if(!outline->contours || !outline->points)
		return ErrRaster_Invalid_Outline;
	if(outline->n_points != outline->contours[outline->n_contours - 1] + 1)
		return ErrRaster_Invalid_Outline;
	if(!(params->flags & XCG_FT_RASTER_FLAG_AA))
		return ErrRaster_Invalid_Mode;
	if(!(params->flags & XCG_FT_RASTER_FLAG_DIRECT))
		return ErrRaster_Invalid_Mode;
	if(params->flags & XCG_FT_RASTER_FLAG_CLIP)
	{
		ras.clip_box = params->clip_box;
	}
	else
	{
		ras.clip_box.xMin = -(1 << 23);
		ras.clip_box.yMin = -(1 << 23);
		ras.clip_box.xMax = (1 << 23) - 1;
		ras.clip_box.yMax = (1 << 23) - 1;
	}
	gray_init_cells( RAS_VAR_ buffer, buffer_size);
	ras.outline = *outline;
	ras.num_cells = 0;
	ras.invalid = 1;
	ras.band_size = (int)(buffer_size / (long)(sizeof(TCell) * 8));
	ras.render_span = (XCG_FT_Raster_Span_Func)params->gray_spans;
	ras.render_span_data = params->user;
	return gray_convert_glyph( RAS_VAR);
}

void XCG_FT_Raster_Render(const XCG_FT_Raster_Params * params)
{
	char stack[XCG_FT_MINIMUM_POOL_SIZE];
	size_t length = XCG_FT_MINIMUM_POOL_SIZE;

	TWorker worker;
	worker.skip_spans = 0;
	int rendered_spans = 0;
	int error = gray_raster_render(&worker, stack, length, params);
	while(error == ErrRaster_OutOfMemory)
	{
		if(worker.skip_spans < 0)
			rendered_spans += -worker.skip_spans;
		worker.skip_spans = rendered_spans;
		length *= 2;
		void *heap = malloc(length);
		error = gray_raster_render(&worker, heap, length, params);
		free(heap);
	}
}

/*
 * stroker
 */
#define XCG_FT_SMALL_CONIC_THRESHOLD	(XCG_FT_ANGLE_PI / 6)
#define XCG_FT_SMALL_CUBIC_THRESHOLD	(XCG_FT_ANGLE_PI / 8)
#define XCG_FT_IS_SMALL(x)				((x) > -2 && (x) < 2)

static XCG_FT_Pos ft_pos_abs(XCG_FT_Pos x)
{
	return x >= 0 ? x : -x;
}

static void ft_conic_split(XCG_FT_Vector *base)
{
	XCG_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 XCG_FT_Bool ft_conic_is_small_enough(XCG_FT_Vector *base, XCG_FT_Angle *angle_in, XCG_FT_Angle *angle_out)
{
	XCG_FT_Vector d1, d2;
	XCG_FT_Angle theta;
	XCG_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 = XCG_FT_IS_SMALL(d1.x) && XCG_FT_IS_SMALL(d1.y);
	close2 = XCG_FT_IS_SMALL(d2.x) && XCG_FT_IS_SMALL(d2.y);
	if(close1)
	{
		if(close2)
		{
		}
		else
		{
			*angle_in = *angle_out = XCG_FT_Atan2(d2.x, d2.y);
		}
	}
	else
	{
		if(close2)
		{
			*angle_in = *angle_out = XCG_FT_Atan2(d1.x, d1.y);
		}
		else
		{
			*angle_in = XCG_FT_Atan2(d1.x, d1.y);
			*angle_out = XCG_FT_Atan2(d2.x, d2.y);
		}
	}
	theta = ft_pos_abs(XCG_FT_Angle_Diff(*angle_in, *angle_out));
	return XCG_FT_BOOL(theta < XCG_FT_SMALL_CONIC_THRESHOLD);
}

static void ft_cubic_split(XCG_FT_Vector * base)
{
	XCG_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;
}

static XCG_FT_Angle ft_angle_mean(XCG_FT_Angle angle1, XCG_FT_Angle angle2)
{
	return angle1 + XCG_FT_Angle_Diff(angle1, angle2) / 2;
}

static XCG_FT_Bool ft_cubic_is_small_enough(XCG_FT_Vector *base, XCG_FT_Angle *angle_in, XCG_FT_Angle *angle_mid, XCG_FT_Angle *angle_out)
{
	XCG_FT_Vector d1, d2, d3;
	XCG_FT_Angle theta1, theta2;
	XCG_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 = XCG_FT_IS_SMALL(d1.x) && XCG_FT_IS_SMALL(d1.y);
	close2 = XCG_FT_IS_SMALL(d2.x) && XCG_FT_IS_SMALL(d2.y);
	close3 = XCG_FT_IS_SMALL(d3.x) && XCG_FT_IS_SMALL(d3.y);

	if(close1)
	{
		if(close2)
		{
			if(close3)
			{
			}
			else
			{
				*angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d3.x, d3.y);
			}
		}
		else
		{
			if(close3)
			{
				*angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d2.x, d2.y);
			}
			else
			{
				*angle_in = *angle_mid = XCG_FT_Atan2(d2.x, d2.y);
				*angle_out = XCG_FT_Atan2(d3.x, d3.y);
			}
		}
	}
	else
	{
		if(close2)
		{
			if(close3)
			{
				*angle_in = *angle_mid = *angle_out = XCG_FT_Atan2(d1.x, d1.y);
			}
			else
			{
				*angle_in = XCG_FT_Atan2(d1.x, d1.y);
				*angle_out = XCG_FT_Atan2(d3.x, d3.y);
				*angle_mid = ft_angle_mean(*angle_in, *angle_out);
			}
		}
		else
		{
			if(close3)
			{
				*angle_in = XCG_FT_Atan2(d1.x, d1.y);
				*angle_mid = *angle_out = XCG_FT_Atan2(d2.x, d2.y);
			}
			else
			{
				*angle_in = XCG_FT_Atan2(d1.x, d1.y);
				*angle_mid = XCG_FT_Atan2(d2.x, d2.y);
				*angle_out = XCG_FT_Atan2(d3.x, d3.y);
			}
		}
	}
	theta1 = ft_pos_abs(XCG_FT_Angle_Diff(*angle_in, *angle_mid));
	theta2 = ft_pos_abs(XCG_FT_Angle_Diff(*angle_mid, *angle_out));
	return XCG_FT_BOOL(theta1 < XCG_FT_SMALL_CUBIC_THRESHOLD && theta2 < XCG_FT_SMALL_CUBIC_THRESHOLD);
}

typedef enum XCG_FT_StrokeTags_ {
	XCG_FT_STROKE_TAG_ON = 1,
	XCG_FT_STROKE_TAG_CUBIC = 2,
	XCG_FT_STROKE_TAG_BEGIN = 4,
	XCG_FT_STROKE_TAG_END = 8,
} XCG_FT_StrokeTags;

#define XCG_FT_STROKE_TAG_BEGIN_END		(XCG_FT_STROKE_TAG_BEGIN | XCG_FT_STROKE_TAG_END)

typedef struct XCG_FT_StrokeBorderRec_ {
	XCG_FT_UInt num_points;
	XCG_FT_UInt max_points;
	XCG_FT_Vector * points;
	XCG_FT_Byte * tags;
	XCG_FT_Bool movable;
	XCG_FT_Int start;
	XCG_FT_Bool valid;
} XCG_FT_StrokeBorderRec, *XCG_FT_StrokeBorder;

XCG_FT_Error XCG_FT_Outline_Check(XCG_FT_Outline * outline)
{
	if(outline)
	{
		XCG_FT_Int n_points = outline->n_points;
		XCG_FT_Int n_contours = outline->n_contours;
		XCG_FT_Int end0, end;
		XCG_FT_Int n;

		if(n_points == 0 && n_contours == 0)
			return 0;
		if(n_points <= 0 || n_contours <= 0)
			goto Bad;
		end0 = end = -1;
		for(n = 0; n < n_contours; n++)
		{
			end = outline->contours[n];
			if(end <= end0 || end >= n_points)
				goto Bad;
			end0 = end;
		}
		if(end != n_points - 1)
			goto Bad;
		return 0;
	}
Bad:
	return -1;
}

void XCG_FT_Outline_Get_CBox(const XCG_FT_Outline * outline, XCG_FT_BBox * acbox)
{
	XCG_FT_Pos xMin, yMin, xMax, yMax;

	if(outline && acbox)
	{
		if(outline->n_points == 0)
		{
			xMin = 0;
			yMin = 0;
			xMax = 0;
			yMax = 0;
		}
		else
		{
			XCG_FT_Vector *vec = outline->points;
			XCG_FT_Vector *limit = vec + outline->n_points;
			xMin = xMax = vec->x;
			yMin = yMax = vec->y;
			vec++;
			for(; vec < limit; vec++)
			{
				XCG_FT_Pos x, y;
				x = vec->x;
				if(x < xMin)
					xMin = x;
				if(x > xMax)
					xMax = x;
				y = vec->y;
				if(y < yMin)
					yMin = y;
				if(y > yMax)
					yMax = y;
			}
		}
		acbox->xMin = xMin;
		acbox->xMax = xMax;
		acbox->yMin = yMin;
		acbox->yMax = yMax;
	}
}

static XCG_FT_Error ft_stroke_border_grow(XCG_FT_StrokeBorder border, XCG_FT_UInt new_points)
{
	XCG_FT_UInt old_max = border->max_points;
	XCG_FT_UInt new_max = border->num_points + new_points;
	XCG_FT_Error error = 0;

	if(new_max > old_max)
	{
		XCG_FT_UInt cur_max = old_max;
		while(cur_max < new_max)
			cur_max += (cur_max >> 1) + 16;
		border->points = (XCG_FT_Vector*)realloc(border->points, cur_max * sizeof(XCG_FT_Vector));
		border->tags = (XCG_FT_Byte*)realloc(border->tags, cur_max * sizeof(XCG_FT_Byte));
		if(!border->points || !border->tags)
			goto Exit;
		border->max_points = cur_max;
	}
Exit:
	return error;
}

static void ft_stroke_border_close(XCG_FT_StrokeBorder border, XCG_FT_Bool reverse)
{
	XCG_FT_UInt start = border->start;
	XCG_FT_UInt count = border->num_points;

	if(count <= start + 1U)
		border->num_points = start;
	else
	{
		border->num_points = --count;
		border->points[start] = border->points[count];
		border->tags[start] = border->tags[count];
		if(reverse)
		{
			{
				XCG_FT_Vector *vec1 = border->points + start + 1;
				XCG_FT_Vector *vec2 = border->points + count - 1;
				for(; vec1 < vec2; vec1++, vec2--)
				{
					XCG_FT_Vector tmp;
					tmp = *vec1;
					*vec1 = *vec2;
					*vec2 = tmp;
				}
			}
			{
				XCG_FT_Byte *tag1 = border->tags + start + 1;
				XCG_FT_Byte *tag2 = border->tags + count - 1;

				for(; tag1 < tag2; tag1++, tag2--)
				{
					XCG_FT_Byte tmp;
					tmp = *tag1;
					*tag1 = *tag2;
					*tag2 = tmp;
				}
			}
		}
		border->tags[start] |= XCG_FT_STROKE_TAG_BEGIN;
		border->tags[count - 1] |= XCG_FT_STROKE_TAG_END;
	}
	border->start = -1;
	border->movable = FALSE;
}

static XCG_FT_Error ft_stroke_border_lineto(XCG_FT_StrokeBorder border, XCG_FT_Vector * to, XCG_FT_Bool movable)
{
	XCG_FT_Error error = 0;

	if(border->movable)
	{
		border->points[border->num_points - 1] = *to;
	}
	else
	{
		if(border->num_points > 0&&
		XCG_FT_IS_SMALL(border->points[border->num_points - 1].x - to->x) &&
		XCG_FT_IS_SMALL(border->points[border->num_points - 1].y - to->y))
			return error;
		error = ft_stroke_border_grow(border, 1);
		if(!error)
		{
			XCG_FT_Vector *vec = border->points + border->num_points;
			XCG_FT_Byte *tag = border->tags + border->num_points;
			vec[0] = *to;
			tag[0] = XCG_FT_STROKE_TAG_ON;
			border->num_points += 1;
		}
	}
	border->movable = movable;
	return error;
}

static XCG_FT_Error ft_stroke_border_conicto(XCG_FT_StrokeBorder border, XCG_FT_Vector * control, XCG_FT_Vector * to)
{
	XCG_FT_Error error;

	error = ft_stroke_border_grow(border, 2);
	if(!error)
	{
		XCG_FT_Vector *vec = border->points + border->num_points;
		XCG_FT_Byte *tag = border->tags + border->num_points;

		vec[0] = *control;
		vec[1] = *to;
		tag[0] = 0;
		tag[1] = XCG_FT_STROKE_TAG_ON;
		border->num_points += 2;
	}
	border->movable = FALSE;
	return error;
}

static XCG_FT_Error ft_stroke_border_cubicto(XCG_FT_StrokeBorder border, XCG_FT_Vector * control1, XCG_FT_Vector * control2, XCG_FT_Vector * to)
{
	XCG_FT_Error error;

	error = ft_stroke_border_grow(border, 3);
	if(!error)
	{
		XCG_FT_Vector *vec = border->points + border->num_points;
		XCG_FT_Byte *tag = border->tags + border->num_points;

		vec[0] = *control1;
		vec[1] = *control2;
		vec[2] = *to;
		tag[0] = XCG_FT_STROKE_TAG_CUBIC;
		tag[1] = XCG_FT_STROKE_TAG_CUBIC;
		tag[2] = XCG_FT_STROKE_TAG_ON;
		border->num_points += 3;
	}
	border->movable = FALSE;
	return error;
}

#define XCG_FT_ARC_CUBIC_ANGLE		(XCG_FT_ANGLE_PI / 2)

static XCG_FT_Error ft_stroke_border_arcto(XCG_FT_StrokeBorder border, XCG_FT_Vector *center, XCG_FT_Fixed radius, XCG_FT_Angle angle_start, XCG_FT_Angle angle_diff)
{
	XCG_FT_Fixed coef;
	XCG_FT_Vector a0, a1, a2, a3;
	XCG_FT_Int i, arcs = 1;
	XCG_FT_Error error = 0;

	while(angle_diff > XCG_FT_ARC_CUBIC_ANGLE * arcs || -angle_diff > XCG_FT_ARC_CUBIC_ANGLE * arcs)
		arcs++;
	coef = XCG_FT_Tan(angle_diff / (4 * arcs));
	coef += coef / 3;
	XCG_FT_Vector_From_Polar(&a0, radius, angle_start);
	a1.x = XCG_FT_MulFix(-a0.y, coef);
	a1.y = XCG_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++)
	{
		XCG_FT_Vector_From_Polar(&a3, radius, angle_start + i * angle_diff / arcs);
		a2.x = XCG_FT_MulFix(a3.y, coef);
		a2.y = XCG_FT_MulFix(-a3.x, coef);
		a3.x += center->x;
		a3.y += center->y;
		a2.x += a3.x;
		a2.y += a3.y;
		error = ft_stroke_border_cubicto(border, &a1, &a2, &a3);
		if(error)
			break;
		a1.x = a3.x - a2.x + a3.x;
		a1.y = a3.y - a2.y + a3.y;
	}
	return error;
}

static XCG_FT_Error ft_stroke_border_moveto(XCG_FT_StrokeBorder border, XCG_FT_Vector * to)
{
	if(border->start >= 0)
		ft_stroke_border_close(border, FALSE);
	border->start = border->num_points;
	border->movable = FALSE;
	return ft_stroke_border_lineto(border, to, FALSE);
}

static void ft_stroke_border_init(XCG_FT_StrokeBorder border)
{
	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(XCG_FT_StrokeBorder border)
{
	border->num_points = 0;
	border->start = -1;
	border->valid = FALSE;
}

static void ft_stroke_border_done(XCG_FT_StrokeBorder border)
{
	free(border->points);
	free(border->tags);

	border->num_points = 0;
	border->max_points = 0;
	border->start = -1;
	border->valid = FALSE;
}

static XCG_FT_Error ft_stroke_border_get_counts(XCG_FT_StrokeBorder border, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours)
{
	XCG_FT_Error error = 0;
	XCG_FT_UInt num_points = 0;
	XCG_FT_UInt num_contours = 0;
	XCG_FT_UInt count = border->num_points;
	XCG_FT_Vector *point = border->points;
	XCG_FT_Byte *tags = border->tags;
	XCG_FT_Int in_contour = 0;

	for(; count > 0; count--, num_points++, point++, tags++)
	{
		if(tags[0] & XCG_FT_STROKE_TAG_BEGIN)
		{
			if(in_contour != 0)
				goto Fail;
			in_contour = 1;
		}
		else if(in_contour == 0)
			goto Fail;
		if(tags[0] & XCG_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(XCG_FT_StrokeBorder border, XCG_FT_Outline * outline)
{
	if(outline->points != NULL && border->points != NULL)
		memcpy(outline->points + outline->n_points, border->points, border->num_points * sizeof(XCG_FT_Vector));
	if(outline->tags)
	{
		XCG_FT_UInt count = border->num_points;
		XCG_FT_Byte *read = border->tags;
		XCG_FT_Byte *write = (XCG_FT_Byte*)outline->tags + outline->n_points;
		for(; count > 0; count--, read++, write++)
		{
			if(*read & XCG_FT_STROKE_TAG_ON)
				*write = XCG_FT_CURVE_TAG_ON;
			else if(*read & XCG_FT_STROKE_TAG_CUBIC)
				*write = XCG_FT_CURVE_TAG_CUBIC;
			else
				*write = XCG_FT_CURVE_TAG_CONIC;
		}
	}
	if(outline->contours)
	{
		XCG_FT_UInt count = border->num_points;
		XCG_FT_Byte *tags = border->tags;
		XCG_FT_Int *write = outline->contours + outline->n_contours;
		XCG_FT_Int idx = (XCG_FT_Int)outline->n_points;
		for(; count > 0; count--, tags++, idx++)
		{
			if(*tags & XCG_FT_STROKE_TAG_END)
			{
				*write++ = idx;
				outline->n_contours++;
			}
		}
	}
	outline->n_points = (int)(outline->n_points + border->num_points);
	XCG_FT_Outline_Check(outline);
}

#define XCG_FT_SIDE_TO_ROTATE(s)	(XCG_FT_ANGLE_PI2 - (s) * XCG_FT_ANGLE_PI)

typedef struct XCG_FT_StrokerRec_ {
	XCG_FT_Angle angle_in;
	XCG_FT_Angle angle_out;
	XCG_FT_Vector center;
	XCG_FT_Fixed line_length;
	XCG_FT_Bool first_point;
	XCG_FT_Bool subpath_open;
	XCG_FT_Angle subpath_angle;
	XCG_FT_Vector subpath_start;
	XCG_FT_Fixed subpath_line_length;
	XCG_FT_Bool handle_wide_strokes;
	XCG_FT_Stroker_LineCap line_cap;
	XCG_FT_Stroker_LineJoin line_join;
	XCG_FT_Stroker_LineJoin line_join_saved;
	XCG_FT_Fixed miter_limit;
	XCG_FT_Fixed radius;
	XCG_FT_StrokeBorderRec borders[2];
} XCG_FT_StrokerRec;

XCG_FT_Error XCG_FT_Stroker_New(XCG_FT_Stroker * astroker)
{
	XCG_FT_Error error = 0;
	XCG_FT_Stroker stroker = NULL;
	stroker = (XCG_FT_StrokerRec*)calloc(1, sizeof(XCG_FT_StrokerRec));
	if(stroker)
	{
		ft_stroke_border_init(&stroker->borders[0]);
		ft_stroke_border_init(&stroker->borders[1]);
	}
	*astroker = stroker;
	return error;
}

void XCG_FT_Stroker_Rewind(XCG_FT_Stroker stroker)
{
	if(stroker)
	{
		ft_stroke_border_reset(&stroker->borders[0]);
		ft_stroke_border_reset(&stroker->borders[1]);
	}
}

void XCG_FT_Stroker_Set(XCG_FT_Stroker stroker, XCG_FT_Fixed radius, XCG_FT_Stroker_LineCap line_cap, XCG_FT_Stroker_LineJoin line_join, XCG_FT_Fixed miter_limit)
{
	stroker->radius = radius;
	stroker->line_cap = line_cap;
	stroker->line_join = line_join;
	stroker->miter_limit = miter_limit;
	if(stroker->miter_limit < 0x10000)
		stroker->miter_limit = 0x10000;
	stroker->line_join_saved = line_join;
	XCG_FT_Stroker_Rewind(stroker);
}

void XCG_FT_Stroker_Done(XCG_FT_Stroker stroker)
{
	if(stroker)
	{
		ft_stroke_border_done(&stroker->borders[0]);
		ft_stroke_border_done(&stroker->borders[1]);
		free(stroker);
	}
}

static XCG_FT_Error ft_stroker_arcto(XCG_FT_Stroker stroker, XCG_FT_Int side)
{
	XCG_FT_Angle total, rotate;
	XCG_FT_Fixed radius = stroker->radius;
	XCG_FT_Error error = 0;
	XCG_FT_StrokeBorder border = stroker->borders + side;

	rotate = XCG_FT_SIDE_TO_ROTATE(side);
	total = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out);
	if(total == XCG_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;
}

static XCG_FT_Error ft_stroker_cap(XCG_FT_Stroker stroker, XCG_FT_Angle angle, XCG_FT_Int side)
{
	XCG_FT_Error error = 0;

	if(stroker->line_cap == XCG_FT_STROKER_LINECAP_ROUND)
	{
		stroker->angle_in = angle;
		stroker->angle_out = angle + XCG_FT_ANGLE_PI;
		error = ft_stroker_arcto(stroker, side);
	}
	else
	{
		XCG_FT_Vector middle, delta;
		XCG_FT_Fixed radius = stroker->radius;
		XCG_FT_StrokeBorder border = stroker->borders + side;
		XCG_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 == XCG_FT_STROKER_LINECAP_SQUARE)
		{
			middle.x += stroker->center.x;
			middle.y += stroker->center.y;
		}
		else
		{
			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;
		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;
}

static XCG_FT_Error ft_stroker_inside(XCG_FT_Stroker stroker, XCG_FT_Int side, XCG_FT_Fixed line_length)
{
	XCG_FT_StrokeBorder border = stroker->borders + side;
	XCG_FT_Angle phi, theta, rotate;
	XCG_FT_Fixed length;
	XCG_FT_Vector sigma = { 0, 0 };
	XCG_FT_Vector delta;
	XCG_FT_Error error = 0;
	XCG_FT_Bool intersect;

	rotate = XCG_FT_SIDE_TO_ROTATE(side);
	theta = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2;
	if(!border->movable || line_length == 0 || theta > 0x59C000 || theta < -0x59C000)
		intersect = FALSE;
	else
	{
		XCG_FT_Fixed min_length;
		XCG_FT_Vector_Unit(&sigma, theta);
		min_length = ft_pos_abs(XCG_FT_MulDiv(stroker->radius, sigma.y, sigma.x));
		intersect = XCG_FT_BOOL(min_length && stroker->line_length >= min_length && line_length >= min_length);
	}
	if(!intersect)
	{
		XCG_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
	{
		phi = stroker->angle_in + theta + rotate;
		length = XCG_FT_DivFix(stroker->radius, sigma.x);
		XCG_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;
}

static XCG_FT_Error ft_stroker_outside(XCG_FT_Stroker stroker, XCG_FT_Int side, XCG_FT_Fixed line_length)
{
	XCG_FT_StrokeBorder border = stroker->borders + side;
	XCG_FT_Error error;
	XCG_FT_Angle rotate;

	if(stroker->line_join == XCG_FT_STROKER_LINEJOIN_ROUND)
		error = ft_stroker_arcto(stroker, side);
	else
	{
		XCG_FT_Fixed radius = stroker->radius;
		XCG_FT_Vector sigma = { 0, 0 };
		XCG_FT_Angle theta = 0, phi = 0;
		XCG_FT_Bool bevel, fixed_bevel;

		rotate = XCG_FT_SIDE_TO_ROTATE(side);
		bevel = XCG_FT_BOOL(stroker->line_join == XCG_FT_STROKER_LINEJOIN_BEVEL);
		fixed_bevel = XCG_FT_BOOL(stroker->line_join != XCG_FT_STROKER_LINEJOIN_MITER_VARIABLE);

if(!bevel)
		{
			theta = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out) / 2;
			if(theta == XCG_FT_ANGLE_PI2)
				theta = -rotate;
			phi = stroker->angle_in + theta + rotate;
			XCG_FT_Vector_From_Polar(&sigma, stroker->miter_limit, theta);
			if(sigma.x < 0x10000L)
			{
				if(fixed_bevel || ft_pos_abs(theta) > 57)
					bevel = TRUE;
			}
		}
		if(bevel)
		{
			if(fixed_bevel)
			{
				XCG_FT_Vector delta;
				XCG_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
			{
				XCG_FT_Vector middle, delta;
				XCG_FT_Fixed coef;

				XCG_FT_Vector_From_Polar(&middle, XCG_FT_MulFix(radius, stroker->miter_limit), phi);
				coef = XCG_FT_DivFix(0x10000L - sigma.x, sigma.y);
				delta.x = XCG_FT_MulFix(middle.y, coef);
				delta.y = XCG_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;
				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;
				if(line_length == 0)
				{
					XCG_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
		{
			XCG_FT_Fixed length;
			XCG_FT_Vector delta;
			length = XCG_FT_MulDiv(stroker->radius, stroker->miter_limit, sigma.x);
			XCG_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;
			if(line_length == 0)
			{
				XCG_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 XCG_FT_Error ft_stroker_process_corner(XCG_FT_Stroker stroker, XCG_FT_Fixed line_length)
{
	XCG_FT_Error error = 0;
	XCG_FT_Angle turn;
	XCG_FT_Int inside_side;

	turn = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out);
	if(turn == 0)
		goto Exit;
	inside_side = 0;
	if(turn < 0)
		inside_side = 1;
	error = ft_stroker_inside(stroker, inside_side, line_length);
	if(error)
		goto Exit;
	error = ft_stroker_outside(stroker, 1 - inside_side, line_length);
Exit:
	return error;
}

static XCG_FT_Error ft_stroker_subpath_start(XCG_FT_Stroker stroker, XCG_FT_Angle start_angle, XCG_FT_Fixed line_length)
{
	XCG_FT_Vector delta;
	XCG_FT_Vector point;
	XCG_FT_Error error;
	XCG_FT_StrokeBorder border;

	XCG_FT_Vector_From_Polar(&delta, stroker->radius, start_angle + XCG_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);
	stroker->subpath_angle = start_angle;
	stroker->first_point = FALSE;
	stroker->subpath_line_length = line_length;
Exit:
	return error;
}

XCG_FT_Error XCG_FT_Stroker_LineTo(XCG_FT_Stroker stroker, XCG_FT_Vector * to)
{
	XCG_FT_Error error = 0;
	XCG_FT_StrokeBorder border;
	XCG_FT_Vector delta;
	XCG_FT_Angle angle;
	XCG_FT_Int side;
	XCG_FT_Fixed line_length;

	delta.x = to->x - stroker->center.x;
	delta.y = to->y - stroker->center.y;
	if(delta.x == 0 && delta.y == 0)
		goto Exit;
	line_length = XCG_FT_Vector_Length(&delta);
	angle = XCG_FT_Atan2(delta.x, delta.y);
	XCG_FT_Vector_From_Polar(&delta, stroker->radius, angle + XCG_FT_ANGLE_PI2);
	if(stroker->first_point)
	{
		error = ft_stroker_subpath_start(stroker, angle, line_length);
		if(error)
			goto Exit;
	}
	else
	{
		stroker->angle_out = angle;
		error = ft_stroker_process_corner(stroker, line_length);
		if(error)
			goto Exit;
	}
	for(border = stroker->borders, side = 1; side >= 0; side--, border++)
	{
		XCG_FT_Vector point;
		point.x = to->x + delta.x;
		point.y = to->y + delta.y;
		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;
}

XCG_FT_Error XCG_FT_Stroker_ConicTo(XCG_FT_Stroker stroker, XCG_FT_Vector * control, XCG_FT_Vector * to)
{
	XCG_FT_Error error = 0;
	XCG_FT_Vector bez_stack[34];
	XCG_FT_Vector *arc;
	XCG_FT_Vector *limit = bez_stack + 30;
	XCG_FT_Bool first_arc = TRUE;

	if(XCG_FT_IS_SMALL(stroker->center.x - control->x) &&
		XCG_FT_IS_SMALL(stroker->center.y - control->y) &&
		XCG_FT_IS_SMALL(control->x - to->x) &&
		XCG_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;
	while(arc >= bez_stack)
	{
		XCG_FT_Angle angle_in, angle_out;
		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;
			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(XCG_FT_Angle_Diff(stroker->angle_in, angle_in)) > XCG_FT_SMALL_CONIC_THRESHOLD / 4)
		{
			stroker->center = arc[2];
			stroker->angle_out = angle_in;
			stroker->line_join = XCG_FT_STROKER_LINEJOIN_ROUND;
			error = ft_stroker_process_corner(stroker, 0);
			stroker->line_join = stroker->line_join_saved;
		}
		if(error)
			goto Exit;
		{
			XCG_FT_Vector ctrl, end;
			XCG_FT_Angle theta, phi, rotate, alpha0 = 0;
			XCG_FT_Fixed length;
			XCG_FT_StrokeBorder border;
			XCG_FT_Int side;

			theta = XCG_FT_Angle_Diff(angle_in, angle_out) / 2;
			phi = angle_in + theta;
			length = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta));
			if(stroker->handle_wide_strokes)
				alpha0 = XCG_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 = XCG_FT_SIDE_TO_ROTATE(side);
				XCG_FT_Vector_From_Polar(&ctrl, length, phi + rotate);
				ctrl.x += arc[1].x;
				ctrl.y += arc[1].y;
				XCG_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)
				{
					XCG_FT_Vector start;
					XCG_FT_Angle alpha1;

					start = border->points[border->num_points - 1];
					alpha1 = XCG_FT_Atan2(end.x - start.x, end.y - start.y);
					if(ft_pos_abs(XCG_FT_Angle_Diff(alpha0, alpha1)) >
					XCG_FT_ANGLE_PI / 2)
					{
						XCG_FT_Angle beta, gamma;
						XCG_FT_Vector bvec, delta;
						XCG_FT_Fixed blen, sinA, sinB, alen;

						beta = XCG_FT_Atan2(arc[2].x - start.x, arc[2].y - start.y);
						gamma = XCG_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 = XCG_FT_Vector_Length(&bvec);
						sinA = ft_pos_abs(XCG_FT_Sin(alpha1 - gamma));
						sinB = ft_pos_abs(XCG_FT_Sin(beta - gamma));
						alen = XCG_FT_MulDiv(blen, sinA, sinB);
						XCG_FT_Vector_From_Polar(&delta, alen, beta);
						delta.x += start.x;
						delta.y += start.y;
						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;
						error = ft_stroke_border_lineto(border, &end, FALSE);
						if(error)
							goto Exit;
						continue;
					}
				}
				error = ft_stroke_border_conicto(border, &ctrl, &end);
				if(error)
					goto Exit;
			}
		}
		arc -= 2;
		stroker->angle_in = angle_out;
	}
	stroker->center = *to;
	stroker->line_length = 0;
Exit:
	return error;
}

XCG_FT_Error XCG_FT_Stroker_CubicTo(XCG_FT_Stroker stroker, XCG_FT_Vector *control1, XCG_FT_Vector *control2, XCG_FT_Vector *to)
{
	XCG_FT_Error error = 0;
	XCG_FT_Vector bez_stack[37];
	XCG_FT_Vector *arc;
	XCG_FT_Vector *limit = bez_stack + 32;
	XCG_FT_Bool first_arc = TRUE;

	if(XCG_FT_IS_SMALL(stroker->center.x - control1->x) &&
		XCG_FT_IS_SMALL(stroker->center.y - control1->y) &&
		XCG_FT_IS_SMALL(control1->x - control2->x) &&
		XCG_FT_IS_SMALL(control1->y - control2->y) &&
		XCG_FT_IS_SMALL(control2->x - to->x) &&
		XCG_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;
	while(arc >= bez_stack)
	{
		XCG_FT_Angle angle_in, angle_mid, angle_out;
		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;
			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(XCG_FT_Angle_Diff(stroker->angle_in, angle_in)) > XCG_FT_SMALL_CUBIC_THRESHOLD / 4)
		{
			stroker->center = arc[3];
			stroker->angle_out = angle_in;
			stroker->line_join = XCG_FT_STROKER_LINEJOIN_ROUND;
			error = ft_stroker_process_corner(stroker, 0);
			stroker->line_join = stroker->line_join_saved;
		}
		if(error)
			goto Exit;
		{
			XCG_FT_Vector ctrl1, ctrl2, end;
			XCG_FT_Angle theta1, phi1, theta2, phi2, rotate, alpha0 = 0;
			XCG_FT_Fixed length1, length2;
			XCG_FT_StrokeBorder border;
			XCG_FT_Int side;

			theta1 = XCG_FT_Angle_Diff(angle_in, angle_mid) / 2;
			theta2 = XCG_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 = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta1));
			length2 = XCG_FT_DivFix(stroker->radius, XCG_FT_Cos(theta2));
			if(stroker->handle_wide_strokes)
				alpha0 = XCG_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 = XCG_FT_SIDE_TO_ROTATE(side);
				XCG_FT_Vector_From_Polar(&ctrl1, length1, phi1 + rotate);
				ctrl1.x += arc[2].x;
				ctrl1.y += arc[2].y;
				XCG_FT_Vector_From_Polar(&ctrl2, length2, phi2 + rotate);
				ctrl2.x += arc[1].x;
				ctrl2.y += arc[1].y;
				XCG_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)
				{
					XCG_FT_Vector start;
					XCG_FT_Angle alpha1;
					start = border->points[border->num_points - 1];
					alpha1 = XCG_FT_Atan2(end.x - start.x, end.y - start.y);
					if(ft_pos_abs(XCG_FT_Angle_Diff(alpha0, alpha1)) >
					XCG_FT_ANGLE_PI / 2)
					{
						XCG_FT_Angle beta, gamma;
						XCG_FT_Vector bvec, delta;
						XCG_FT_Fixed blen, sinA, sinB, alen;
						beta = XCG_FT_Atan2(arc[3].x - start.x, arc[3].y - start.y);
						gamma = XCG_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 = XCG_FT_Vector_Length(&bvec);
						sinA = ft_pos_abs(XCG_FT_Sin(alpha1 - gamma));
						sinB = ft_pos_abs(XCG_FT_Sin(beta - gamma));
						alen = XCG_FT_MulDiv(blen, sinA, sinB);
						XCG_FT_Vector_From_Polar(&delta, alen, beta);
						delta.x += start.x;
						delta.y += start.y;
						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;
						error = ft_stroke_border_lineto(border, &end, FALSE);
						if(error)
							goto Exit;
						continue;
					}
				}
				error = ft_stroke_border_cubicto(border, &ctrl1, &ctrl2, &end);
				if(error)
					goto Exit;
			}
		}
		arc -= 3;
		stroker->angle_in = angle_out;
	}
	stroker->center = *to;
	stroker->line_length = 0;
Exit:
	return error;
}

XCG_FT_Error XCG_FT_Stroker_BeginSubPath(XCG_FT_Stroker stroker, XCG_FT_Vector * to, XCG_FT_Bool open)
{
	stroker->first_point = TRUE;
	stroker->center = *to;
	stroker->subpath_open = open;
	stroker->handle_wide_strokes = XCG_FT_BOOL(stroker->line_join != XCG_FT_STROKER_LINEJOIN_ROUND || (stroker->subpath_open && stroker->line_cap == XCG_FT_STROKER_LINECAP_BUTT));
	stroker->subpath_start = *to;
	stroker->angle_in = 0;
	return 0;
}

static XCG_FT_Error ft_stroker_add_reverse_left(XCG_FT_Stroker stroker, XCG_FT_Bool open)
{
	XCG_FT_StrokeBorder right = stroker->borders + 0;
	XCG_FT_StrokeBorder left = stroker->borders + 1;
	XCG_FT_Int new_points;
	XCG_FT_Error error = 0;

	new_points = left->num_points - left->start;
	if(new_points > 0)
	{
		error = ft_stroke_border_grow(right, (XCG_FT_UInt)new_points);
		if(error)
			goto Exit;
		{
			XCG_FT_Vector *dst_point = right->points + right->num_points;
			XCG_FT_Byte *dst_tag = right->tags + right->num_points;
			XCG_FT_Vector *src_point = left->points + left->num_points - 1;
			XCG_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] &= ~XCG_FT_STROKE_TAG_BEGIN_END;
				else
				{
					XCG_FT_Byte ttag = (XCG_FT_Byte)(dst_tag[0] & XCG_FT_STROKE_TAG_BEGIN_END);
					if(ttag == XCG_FT_STROKE_TAG_BEGIN || ttag == XCG_FT_STROKE_TAG_END)
						dst_tag[0] ^= XCG_FT_STROKE_TAG_BEGIN_END;
				}
				src_point--;
				src_tag--;
				dst_point++;
				dst_tag++;
			}
		}
		left->num_points = left->start;
		right->num_points += new_points;
		right->movable = FALSE;
		left->movable = FALSE;
	}
Exit:
	return error;
}

XCG_FT_Error XCG_FT_Stroker_EndSubPath(XCG_FT_Stroker stroker)
{
	XCG_FT_Error error = 0;

	if(stroker->subpath_open)
	{
		XCG_FT_StrokeBorder right = stroker->borders;
		error = ft_stroker_cap(stroker, stroker->angle_in, 0);
		if(error)
			goto Exit;
		error = ft_stroker_add_reverse_left(stroker, TRUE);
		if(error)
			goto Exit;
		stroker->center = stroker->subpath_start;
		error = ft_stroker_cap(stroker, stroker->subpath_angle + XCG_FT_ANGLE_PI, 0);
		if(error)
			goto Exit;
		ft_stroke_border_close(right, FALSE);
	}
	else
	{
		XCG_FT_Angle turn;
		XCG_FT_Int inside_side;
		if(stroker->center.x != stroker->subpath_start.x || stroker->center.y != stroker->subpath_start.y)
		{
			error = XCG_FT_Stroker_LineTo(stroker, &stroker->subpath_start);
			if(error)
				goto Exit;
		}
		stroker->angle_out = stroker->subpath_angle;
		turn = XCG_FT_Angle_Diff(stroker->angle_in, stroker->angle_out);
		if(turn != 0)
		{
			inside_side = 0;
			if(turn < 0)
				inside_side = 1;
			error = ft_stroker_inside(stroker, inside_side, stroker->subpath_line_length);
			if(error)
				goto Exit;
			error = ft_stroker_outside(stroker, 1 - inside_side, stroker->subpath_line_length);
			if(error)
				goto Exit;
		}
		ft_stroke_border_close(stroker->borders + 0, FALSE);
		ft_stroke_border_close(stroker->borders + 1, TRUE);
	}
Exit:
	return error;
}

XCG_FT_Error XCG_FT_Stroker_GetBorderCounts(XCG_FT_Stroker stroker, XCG_FT_StrokerBorder border, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours)
{
	XCG_FT_UInt num_points = 0, num_contours = 0;
	XCG_FT_Error error;

	if(!stroker || border > 1)
	{
		error = -1;
		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;
}

XCG_FT_Error XCG_FT_Stroker_GetCounts(XCG_FT_Stroker stroker, XCG_FT_UInt * anum_points, XCG_FT_UInt * anum_contours)
{
	XCG_FT_UInt count1, count2, num_points = 0;
	XCG_FT_UInt count3, count4, num_contours = 0;
	XCG_FT_Error error;

	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:
	*anum_points = num_points;
	*anum_contours = num_contours;
	return error;
}

void XCG_FT_Stroker_ExportBorder(XCG_FT_Stroker stroker, XCG_FT_StrokerBorder border, XCG_FT_Outline * outline)
{
	if(border == XCG_FT_STROKER_BORDER_LEFT || border == XCG_FT_STROKER_BORDER_RIGHT)
	{
		XCG_FT_StrokeBorder sborder = &stroker->borders[border];
		if(sborder->valid)
			ft_stroke_border_export(sborder, outline);
	}
}

void XCG_FT_Stroker_Export(XCG_FT_Stroker stroker, XCG_FT_Outline * outline)
{
	XCG_FT_Stroker_ExportBorder(stroker, XCG_FT_STROKER_BORDER_LEFT, outline);
	XCG_FT_Stroker_ExportBorder(stroker, XCG_FT_STROKER_BORDER_RIGHT, outline);
}

XCG_FT_Error XCG_FT_Stroker_ParseOutline(XCG_FT_Stroker stroker, const XCG_FT_Outline *outline)
{
	XCG_FT_Vector v_last;
	XCG_FT_Vector v_control;
	XCG_FT_Vector v_start;
	XCG_FT_Vector * point;
	XCG_FT_Vector * limit;
	char * tags;
	XCG_FT_Error error;
	XCG_FT_Int n;
	XCG_FT_UInt first;
	XCG_FT_Int tag;

	if(!outline || !stroker)
		return -1;
	XCG_FT_Stroker_Rewind(stroker);
	first = 0;
	for(n = 0; n < outline->n_contours; n++)
	{
		XCG_FT_UInt last;
		last = outline->contours[n];
		limit = outline->points + last;
		if(last <= first)
		{
			first = last + 1;
			continue;
		}
		v_start = outline->points[first];
		v_last = outline->points[last];
		v_control = v_start;
		point = outline->points + first;
		tags = outline->tags + first;
		tag = XCG_FT_CURVE_TAG(tags[0]);
		if(tag == XCG_FT_CURVE_TAG_CUBIC)
			goto Invalid_Outline;
		if(tag == XCG_FT_CURVE_TAG_CONIC)
		{
			if(XCG_FT_CURVE_TAG(outline->tags[last]) == XCG_FT_CURVE_TAG_ON)
			{
				v_start = v_last;
				limit--;
			}
			else
			{
				v_start.x = (v_start.x + v_last.x) / 2;
				v_start.y = (v_start.y + v_last.y) / 2;
			}
			point--;
			tags--;
		}
		error = XCG_FT_Stroker_BeginSubPath(stroker, &v_start, outline->contours_flag[n]);
		if(error)
			goto Exit;
		while(point < limit)
		{
			point++;
			tags++;
			tag = XCG_FT_CURVE_TAG(tags[0]);
			switch(tag)
			{
			case XCG_FT_CURVE_TAG_ON:
			{
				XCG_FT_Vector vec;
				vec.x = point->x;
				vec.y = point->y;
				error = XCG_FT_Stroker_LineTo(stroker, &vec);
				if(error)
					goto Exit;
				continue;
			}

			case XCG_FT_CURVE_TAG_CONIC:
				v_control.x = point->x;
				v_control.y = point->y;
Do_Conic:
				if(point < limit)
				{
					XCG_FT_Vector vec;
					XCG_FT_Vector v_middle;
					point++;
					tags++;
					tag = XCG_FT_CURVE_TAG(tags[0]);
					vec = point[0];
					if(tag == XCG_FT_CURVE_TAG_ON)
					{
						error = XCG_FT_Stroker_ConicTo(stroker, &v_control, &vec);
						if(error)
							goto Exit;
						continue;
					}
					if(tag != XCG_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 = XCG_FT_Stroker_ConicTo(stroker, &v_control, &v_middle);
					if(error)
						goto Exit;
					v_control = vec;
					goto Do_Conic;
				}
				error = XCG_FT_Stroker_ConicTo(stroker, &v_control, &v_start);
				goto Close;

			default:
			{
				XCG_FT_Vector vec1, vec2;
				if(point + 1 > limit ||
				XCG_FT_CURVE_TAG(tags[1]) != XCG_FT_CURVE_TAG_CUBIC)
					goto Invalid_Outline;
				point += 2;
				tags += 2;
				vec1 = point[-2];
				vec2 = point[-1];
				if(point <= limit)
				{
					XCG_FT_Vector vec;
					vec = point[0];
					error = XCG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &vec);
					if(error)
						goto Exit;
					continue;
				}
				error = XCG_FT_Stroker_CubicTo(stroker, &vec1, &vec2, &v_start);
				goto Close;
			}
			}
		}
Close:
		if(error)
			goto Exit;
		if(stroker->first_point)
		{
			stroker->subpath_open = TRUE;
			error = ft_stroker_subpath_start(stroker, 0, 0);
			if(error)
				goto Exit;
		}
		error = XCG_FT_Stroker_EndSubPath(stroker);
		if(error)
			goto Exit;
		first = last + 1;
	}
	return 0;
Exit:
	return error;
Invalid_Outline:
	return -2;
}
