view src/shape_path.c @ 1532:4a92b639a1cd

Clear selection set when switching current scene. To clear selection set after switching away from current to another scene. It avoids Inkscape select on nodes they are not saw after switching.
author Thinker K.F. Li <thinker@codemud.net>
date Fri, 30 Sep 2011 12:31:33 +0800
parents bae104d8d247
children
line wrap: on
line source

// -*- indent-tabs-mode: t; tab-width: 8; c-basic-offset: 4; -*-
// vim: sw=4:ts=8:sts=4
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>
#include "mb_graph_engine.h"
#include "mb_types.h"
#include "mb_redraw_man.h"

/*! \brief Implement respective objects for SVG path tag.
 *
 * In user_data or dev_data, 0x00 bytes are padding after commands.
 * No commands other than 0x00 can resident after 0x00 itself.
 * It means command processing code can skip commands after a 0x00.
 *
 * Shapes should check if shape_t::geo is assigned.  Once transformation
 * matrics are changed, shape objects should update shape_t::geo if
 * it is assigned.
 */
typedef struct _sh_path {
    shape_t shape;
    int cmd_len;
    int pnt_len;
    int float_arg_len;
    char *user_data;
    char *dev_data;		/* device space data */
    
    redraw_man_t *rdman;	/*!< \brief This is used by sh_path_free() */
} sh_path_t;
#define RESERVED_AIXS sizeof(co_aix[2])

int _sh_path_size = sizeof(sh_path_t);

#define ASSERT(x)
#define SKIP_SPACE(x) while(*(x) && (isspace(*(x)) || *(x) == ',')) { (x)++; }
#define SKIP_NUM(x)					\
    while(*(x) &&					\
	  (isdigit(*(x)) ||				\
	   *(x) == 'e' ||				\
	   *(x) == 'E' ||				\
	   *(x) == '-' ||				\
	   *(x) == '+' ||				\
	   *(x) == '.'))  {				\
	(x)++;						\
    }
#define OK 0
#define ERR -1
#define PI 3.1415926535897931
#define FRAC_PI ((int)(PI * FRACTION_ONE))

#define SWAP(x, y) do { x ^= y; y ^= x; x ^= y; } while(0)
#define MAX(x, y) (((x) > (y))? (x): (y))
#define MIN(x, y) (((x) > (y))? (y): (x))
#define IS_NEGATIVE(x) ((x) < 0)

#ifdef UNITTEST
#undef rdman_man_shape
#define rdman_man_shape(x, y)

#undef elmpool_elm_alloc
#define elmpool_elm_alloc(pool) _elmpool_elm_alloc(pool)
static void *
_elmpool_elm_alloc(void *dummy) {
    return malloc(sizeof(sh_path_t));
}

#undef elmpool_elm_free
#define elmpool_elm_free(pool, elm) _elmpool_elm_free(pool, elm)
static void
_elmpool_elm_free(void *pool, void *elm) {
    free(elm);
}
#endif

/* ============================================================
 * Implement arc in path.
 */
#if 1

#include <stdint.h>
#include "precomputed.h"

#define ABS(x) (((x) > 0)? (x): -(x))
#define FRACTION_ONE (1 << FRACTION_SHIFT)

/*! \brief Compute the small slope of a vector.
 *
 * A small slope is based on absolute value of x-axis and y-axis.
 * And use smaller one of absolute values as divisor.
 */
static int
_small_slope(int x, int y) {
    int _x, _y;
    int r;

    _x = ABS(x);
    _y = ABS(y);
    if(_x > _y)
	r = (_y << FRACTION_SHIFT) / _x;
    else
	r = (_x << FRACTION_SHIFT) / _y;

    return r;
}

/*! \brief Index a given angle in slope table.
 *
 * Binary search.
 */
static int
_find_slope_index(int slope) {
    int left, right, v;

    left = 0;
    right = SLOPE_TAB_SZ - 1;
    while(left <= right) {
	v = (left + right) / 2;
	if(slope < slope_tab[v])
	    right = v - 1;
	else
	    left = v + 1;
    }

    return v;
}

static int
_vector_len(int x, int y) {
    int64_t _x, _y;
    int64_t slope;
    int64_t slope_index;
    int64_t radius;
    
    _x = ABS(x);
    _y = ABS(y);
    
    if(_x > _y) {
	slope = (_y << FRACTION_SHIFT) / _x;
	slope_index = _find_slope_index(slope);
	radius = _x * vector_len_factor_tab[slope_index];
    } else {
	slope = (_x << FRACTION_SHIFT) / _y;
	slope_index = _find_slope_index(slope);
	radius = _y * vector_len_factor_tab[slope_index];
    }
    radius = radius / FRACTION_ONE;
    
    return radius;
}

/*! \brief Find index of an arc-to-radius ratio in arc_radius_ratio_tab.
 *
 * Binary search.
 */
static int
_find_arc_radius(int arc_radius_ratio) {
    int left, right, v;

    left = 0;
    right = ARC_RADIUS_RATIO_TAB_SZ - 1;
    while(left <= right) {
	v = (left + right) / 2;
	if(arc_radius_ratio < arc_radius_ratio_tab[v])
	    right = v - 1;
	else
	    left = v + 1;
    }

    return v;
}

/* Compute shift factor for the ratio of arc to radius */
static int
_get_arc_radius_shift_factor(int arc_x, int arc_y, int radius) {
    int arc_len;
    int radius_len;
    int arc_radius_ratio;
    int arc_radius_index;
    int arc_radius_factor;
    
    arc_len = _vector_len(ABS(arc_x), ABS(arc_y));
    arc_radius_ratio = (arc_len << FRACTION_SHIFT) / radius;
    arc_radius_index = _find_arc_radius(arc_radius_ratio);
    
    arc_radius_factor = arc_radius_factor_tab[arc_radius_index];
    
    return arc_radius_factor;
}

/* Return a unit vector in the extend direction.
 *
 * This function make a decision on the direction of extend to make
 * radius of rx direction equivlant to ry direction.  It extends the
 * direction of short one.
 */
static void
_compute_extend_unit_vector(int rx, int ry, int x_rotate,
			    int64_t *ext_unit_x, int64_t *ext_unit_y) {
    int extend_dir;
    int extend_phase;
    int extend_index;
    int extend_sin, extend_cos;
    /* Change sign of x, y values accroding phase of the vector. */
    static int sin_cos_signs_tab[4][2] = {
	/* 0 for positive, 1 for negative */
	{0, 0}, {1, 0}, {1, 1}, {0, 1}};
    int *signs;
    
    if(rx > ry)
	extend_dir = x_rotate + (FRAC_PI >> 1);
    else
	extend_dir = x_rotate;
    extend_dir %= FRAC_PI * 2;
    extend_phase = extend_dir / (FRAC_PI >> 1);
    
    extend_index = (extend_dir % (FRAC_PI >> 1)) * SIN_TAB_SZ /
	(FRAC_PI >> 1);
    if(extend_phase & 0x1)	/* half-phases 1,3 */
	extend_index = SIN_TAB_SZ - extend_index - 1;
    
    extend_sin = sin_tab[extend_index];
    extend_cos = sin_tab[SIN_TAB_SZ - extend_index - 1];
    
    signs = sin_cos_signs_tab[extend_phase];
    *ext_unit_x = signs[0]? -extend_cos: extend_cos;
    *ext_unit_y = signs[1]? -extend_sin: extend_sin;
}

static void
_get_center_ref_shift(int arc_x, int arc_y, int large, int sweep,
		      int slope_index,
		      int64_t *shift_cx, int64_t *shift_cy) {
    int _shift_cx, _shift_cy;
    int stat = 0;
    /* Change sign of shift-x/y accroding sign of arc_x, arc_y,
     * large and sweep.
     */
    static int shift_signs_tab[16][2] = {
	/* +x,+y   -x,+y   +x,-y   -x,-y */
	{1, 1}, {0, 1}, {1, 0}, {0, 0}, /* small, negative-angle */
	{0, 0}, {1, 0}, {0, 1}, {1, 1}, /* large, negative-angle */
	{0, 0}, {1, 0}, {0, 1}, {1, 1}, /* small, positive-angle */
	{1, 1}, {0, 1}, {1, 0}, {0, 0}  /* large, positive-angle */
    };
    
    _shift_cx = center_shift_tab[slope_index][0];
    _shift_cy = center_shift_tab[slope_index][1];
    if(ABS(arc_x) <= ABS(arc_y)) {
	SWAP(_shift_cx, _shift_cy);
	_shift_cx = -_shift_cx;
	_shift_cy = -_shift_cy;
    }
    
    if(IS_NEGATIVE(arc_x))
	stat |= 0x1;
    if(IS_NEGATIVE(arc_y))
	stat |= 0x2;
    if(large)
	stat |= 0x4;
    if(sweep)
	stat |= 0x8;
    if(shift_signs_tab[stat][0])
	_shift_cx = -_shift_cx;
    if(shift_signs_tab[stat][1])
	_shift_cy = -_shift_cy;

    *shift_cx = _shift_cx;
    *shift_cy = _shift_cy;
}

static int
_calc_center_i(int x0, int y0,
	       int x, int y,
	       int rx, int ry,
	       int x_rotate,
	       int large, int sweep,
	       int *cx, int *cy) {
    int64_t radius;
    int64_t ext_unit_y, ext_unit_x;	/* x and y value of unit vector on
				 * extend direction */
    int64_t arc_x, arc_y;
    int64_t radius_ref_ratio;
    int64_t arc_radius_factor;
    int64_t slope, slope_index;
    int64_t shift_cx, shift_cy;
    int64_t center_shift_factor;
    static int negatives[4] = {0, 1, 1, 0};
    int64_t extend_len;
    int64_t extend_x, extend_y;
    
    ASSERT(rx >= 0 && ry >= 0);
    
    arc_x = x - x0;
    arc_y = y - y0;
    
    if(arc_x == 0 && arc_y == 0) {
	*cx = x0;
	*cy = y0;
	return OK;
    }

    /* Translate arc to the coordinate that extend rx or ry to the
     * equivlant size as another.  It translate the ellipse to a
     * circle.
     */
    radius = MAX(rx, ry);
    _compute_extend_unit_vector(rx, ry, x_rotate, &ext_unit_x, &ext_unit_y);
    
    extend_len = (arc_x * ext_unit_x + arc_y * ext_unit_y) / FRACTION_ONE;
    extend_len = extend_len * (MAX(rx, ry) - MIN(rx, ry)) / MIN(rx, ry);
    extend_x = ext_unit_x * extend_len / FRACTION_ONE;
    extend_y = ext_unit_y * extend_len / FRACTION_ONE;
    
    arc_x += extend_x;
    arc_y += extend_y;
    
    /* Find range index of slope. */
    slope = _small_slope(arc_x, arc_y);
    slope_index = _find_slope_index(slope);

    /* Compute shift factor for the ratio of arc to radius */
    arc_radius_factor = _get_arc_radius_shift_factor(arc_x, arc_y, radius);

    /* Compute ratio of radius to reference radius */
    radius_ref_ratio = radius >> REF_RADIUS_SHIFT;
    
    /* Compute x/y-shift of center range index according
     * slope_index, radius_ref_ratio and arc_radius_factor.
     */
    _get_center_ref_shift(arc_x, arc_y, large, sweep, slope_index,
			  &shift_cx, &shift_cy);
    center_shift_factor = radius_ref_ratio * arc_radius_factor;
    center_shift_factor = center_shift_factor / FRACTION_ONE;
    shift_cx = shift_cx * center_shift_factor / FRACTION_ONE;
    shift_cy = shift_cy * center_shift_factor / FRACTION_ONE;
    
    shift_cx += arc_x / 2;
    shift_cy += arc_y / 2;

    /* translate shift_cx/cy back to original coordinate */
    extend_len = (shift_cx * ext_unit_x + shift_cy * ext_unit_y) /
	FRACTION_ONE;
    extend_len = extend_len * (MAX(rx, ry) - MIN(rx, ry)) / MAX(rx, ry);
    extend_x = ext_unit_x * extend_len / FRACTION_ONE;
    extend_y = ext_unit_y * extend_len / FRACTION_ONE;
    shift_cx = shift_cx - extend_x;
    shift_cy = shift_cy - extend_y;
    
    /* get center */
    *cx = x0 + shift_cx;
    *cy = y0 + shift_cy;

    return OK;
}

static int _calc_center(co_aix x0, co_aix y0,
			co_aix x, co_aix y,
			co_aix rx, co_aix ry,
			co_aix x_rotate,
			int large, int sweep,
			co_aix *cx, co_aix *cy) {
    int cx_i, cy_i;
    int r;
    
    r = _calc_center_i(x0 * FRACTION_ONE, y0 * FRACTION_ONE,
		       x * FRACTION_ONE, y * FRACTION_ONE,
		       rx * FRACTION_ONE, ry * FRACTION_ONE,
		       x_rotate * FRACTION_ONE,
		       large, sweep, &cx_i, &cy_i);
    *cx = (co_aix)cx_i / FRACTION_ONE;
    *cy = (co_aix)cy_i / FRACTION_ONE;
    return r;
}

#else
/*! \brief Calculate center of the ellipse of an arc.
 *
 * Origin of our coordination is left-top corner, and y-axis are grown
 * to down-side.
 *
 * Space of the arc is transformed to space that correspondent
 * ellipse containing the arc is mapped into an unit circle.
 * - ux0 = x0 / rx
 * - uy0 = y0 / ry
 * - ux = x / rx
 * - uy = y / ry
 * ux0, uy0, ux, uy are got by transforming (x0, y0) and (x, y) into points
 * on the unit circle. The center of unit circle are (ucx, ucy):
 * - umx = (ux0 + ux) / 2
 * - umy = (uy0 + uy) / 2
 * - udcx = ucx - umx
 * - udcy = ucy - umy
 * - udx = ux - umx
 * - udy = uy - umy
 *
 * - udx * udcx + udy * udcy = 0
 *
 * - udl2 = udx ** 2 + udy ** 2
 *
 * For drawing small arc in clockwise
 * - udx * udcy - udy * udcx = sqrt((1 - udl2) * udl2)
 *
 * - udcy = -udcx * udx / udy
 * - -udcx * (udx ** 2) / udy - udy * udcx = sqrt((1 - udl2) * udl2)
 * - -udcx * ((udx ** 2) / udy + udy) = sqrt((1 - udl2) * udl2)
 * - udcx = -sqrt((1 - udl2) * udl2) / ((udx ** 2) / udy + udy)
 * or
 * - udcx = -udcy * udy / udx
 * - udx * udcy + udcy * (udy ** 2) / udx = sqrt((1 - udl2) * udl2)
 * - udcy * (udx + (udy ** 2) / udx) = sqrt((1 - udl2) * udl2)
 * - udcy = sqrt((1 - udl2) * udl2) / (udx + (udy ** 2) / udx)
 *
 * - cx = rx * ucx
 * - cx = rx * (udcx + umx)
 * - cy = ry * ucy
 * - cy = ry * (udcy + umy)
 */
static int _calc_center(co_aix x0, co_aix y0,
		       co_aix x, co_aix y,
		       co_aix rx, co_aix ry,
		       co_aix x_rotate,
		       int large, int sweep,
		       co_aix *cx, co_aix *cy) {
    co_aix br_x, br_y, br_x0, br_y0; /* before-rotated x, y, x0, y0 */
    co_aix udx, udy, udx2, udy2;
    co_aix umx, umy;
    co_aix udcx, udcy;
    co_aix br_cx, br_cy;
    co_aix udl2;
    co_aix rev_rx2, rev_ry2;
    float _sin = -sinf(x_rotate); /* rotate to oposite direction */
    float _cos = cosf(x_rotate);
    int reflect;

#define X_AFTER_ROTATE(x, y, sin, cos) (x * cos - y * sin)
#define Y_AFTER_ROTATE(x, y, sin, cos) (x * sin + y * cos)

    /* Restore positions to the value before rotation */
    br_x = X_AFTER_ROTATE(x, y, _sin, _cos);
    br_y = Y_AFTER_ROTATE(x, y, _sin, _cos);
    br_x0 = X_AFTER_ROTATE(x0, y0, _sin, _cos);
    br_y0 = Y_AFTER_ROTATE(x0, y0, _sin, _cos);

    /* Resize to be an unit circle */
    rev_rx2 = 1.0 / (2 * rx);
    rev_ry2 = 1.0 / (2 * ry);
    udx = (br_x - br_x0) * rev_rx2; /* ux - umx */
    udy = (br_y - br_y0) * rev_ry2; /* uy - umy */
    umx = (br_x + br_x0) * rev_rx2;
    umy = (br_y + br_y0) * rev_ry2;

    udx2 = udx * udx;
    udy2 = udy * udy;
    udl2 = udx2 + udy2;

    if(udy != 0) {
	/* center is at left-side of arc */
	udcx = -sqrtf((1 - udl2) * udl2) / (udy + udx2 / udy);
	udcy = -udcx * udx / udy;
    } else {
	/* center is at down-side of arc */
	udcx = 0;
	udcy = sqrtf((1 - udl2) * udl2) / udx;
    }

    reflect = 0;
    if(large)
	reflect ^= 1;
    if(sweep != 1)
	reflect ^= 1;
    if(reflect) {
	udcx = -udcx;
	udcy = -udcy;
    }

    br_cx = rx * (udcx + umx);
    br_cy = ry * (udcy + umy);

    *cx = X_AFTER_ROTATE(br_cx, br_cy, -_sin, _cos);
    *cy = Y_AFTER_ROTATE(br_cx, br_cy, -_sin, _cos);

    return OK;
}
#endif

static co_aix _angle_rotated_ellipse(co_aix x, co_aix y,
				     co_aix rx, co_aix ry,
				     co_aix x_rotate) {
    co_aix nrx, nry;
    co_aix _sin, _cos;
    co_aix xy_tan;
    co_aix angle;

    _sin = sinf(x_rotate);
    _cos = cosf(x_rotate);

    nrx = (x * _cos + y * _sin) / rx;
    nry = (-x * _sin + y * _cos) / ry;
    xy_tan = nry / nrx;

    angle = atan(xy_tan);

    if(nrx < 0)
	angle = PI + angle;

    return angle;
}

static void _rotate(co_aix *x, co_aix *y, co_aix _sin, co_aix _cos) {
    co_aix nx, ny;

    nx = *x * _cos - *y * _sin;
    ny = *x * _sin + *y * _cos;

    *x = nx;
    *y = ny;
}

#define TAKE_NUM(r) do {			\
	SKIP_SPACE(p);				\
	old = p;				\
	SKIP_NUM(p);				\
	if(p == old)				\
	    return ERR;				\
	r = atof(old);				\
    } while(0);

static int _sh_path_arc_cmd_arg_fill(char cmd, char **cmds_p,
				     const char **data_p,
				     co_aix **pnts_p,
				     co_aix **float_args_p) {
    co_aix rx, ry;
    co_aix x_rotate;
    int large, sweep;
    co_aix x, y, x0, y0, cx, cy;
    co_aix corners[4][2];
    co_aix angle_start, angle_stop;
    co_aix *pnts = *pnts_p;
    const char *old;
    const char *p;
    char *cmds;
    co_aix *float_args;
    co_aix _sin, _cos;
    int i;

    p = *data_p;
    cmds = *cmds_p;
    float_args = *float_args_p;
    while(*p) {
	SKIP_SPACE(p);
	old = p;
	SKIP_NUM(p);
	if(p == old)
	    break;
	rx = atof(old);

	TAKE_NUM(ry);
	TAKE_NUM(x_rotate);
	TAKE_NUM(large);
	TAKE_NUM(sweep);
	TAKE_NUM(x);
	TAKE_NUM(y)

	x0 = *(pnts - 2);
	y0 = *(pnts - 1);

	if(islower(cmd)) {
	    x += x0;
	    y += y0;
	}

	_calc_center(x0, y0, x, y, rx, ry, x_rotate, large,
		     sweep, &cx, &cy);

	/* Compute positions for four corners.
	 * These four corners form a bounding box for the arc.
	 */
	_sin = sinf(x_rotate);
	_cos = cosf(x_rotate);
	corners[0][0] = -rx;
	corners[0][1] = -ry;
	corners[1][0] = rx;
	corners[1][1] = -ry;
	corners[2][0] = rx;
	corners[2][1] = ry;
	corners[3][0] = -rx;
	corners[3][1] = ry;
	for(i = 0; i < 4; i++) {
	    _rotate(&corners[i][0], &corners[i][1], _sin, _cos);
	    *pnts++ = corners[i][0] + cx;
	    *pnts++ = corners[i][1] + cy;
	}

	*(pnts++) = x;
	*(pnts++) = y;

	angle_start = _angle_rotated_ellipse(x0 - cx, y0 - cy,
					     rx, ry, x_rotate);
	angle_stop = _angle_rotated_ellipse(x - cx, y - cy,
					    rx, ry, x_rotate);

	if(sweep && angle_start > angle_stop)
	    angle_stop += 2 * PI;
	else if((!sweep) && angle_start < angle_stop)
	    angle_start += 2 * PI;

	*float_args++ = cx;
	*float_args++ = cy;
	*float_args++ = rx;
	*float_args++ = ry;
	*float_args++ = angle_start;
	*float_args++ = angle_stop;
	*float_args++ = x_rotate;

	*cmds++ = toupper(cmd);
    }

    *data_p = p;
    *pnts_p = pnts;
    *cmds_p = cmds;
    *float_args_p = float_args;

    return OK;
}

#define INNER(x1, y1, x2, y2) ((x1) * (x2) + (y1) * (y2))
#define CROSS(x1, y1, x2, y2) ((x1) * (y2) - (y1) * (x2))

static co_aix distance_pow2(co_aix x, co_aix y) {
    return x * x + y * y;
}

static co_aix angle_diff(co_aix sx, co_aix sy, co_aix dx, co_aix dy) {
    co_aix inner, cross;
    co_aix angle;
    co_aix rd2, rd;

    rd2 = distance_pow2(dx, dy);
    rd = sqrtf(rd2);

    inner = INNER(sx, sy, dx, dy);
    cross = CROSS(sx, sy, dx, dy);
    angle = acos(inner / rd);
    if(cross < 0)
	angle = 2 * PI - angle;

    return angle;
}

/*! \brief Make path for arcs in a path.
 */
void _sh_path_arc_path(mbe_t *cr, sh_path_t *path, const co_aix **pnts_p,
		       const co_aix **float_args_p) {
    co_aix cx, cy, x0, y0, x, y;
    co_aix rx, ry;
    co_aix xyratio;
    co_aix angle_start, angle_stop;
    co_aix x_rotate;
    const co_aix *pnts;
    const co_aix *float_args;
    co_aix matrix[6];
    co_aix dev_matrix[6];
    co_aix *aggr;
    co_aix _sin, _cos;

    pnts = *pnts_p;
    float_args = *float_args_p;
    x0 = *(pnts - 2);
    y0 = *(pnts - 1);
    pnts += 8;
    x = *pnts++;
    y = *pnts++;

    cx = *float_args++;
    cy = *float_args++;
    rx = *float_args++;
    ry = *float_args++;
    angle_start = *float_args++;
    angle_stop = *float_args++;
    x_rotate = *float_args++;

    _sin = sinf(x_rotate);
    _cos = cosf(x_rotate);

    xyratio = ry / rx;
    aggr = sh_get_aggr_matrix((shape_t *)path);
    matrix[0] = _cos;
    matrix[1] = -_sin * xyratio;
    matrix[2] = cx;
    matrix[3] = _sin;
    matrix[4] = _cos * xyratio;
    matrix[5] = cy;

    matrix_mul(aggr, matrix, dev_matrix);
    mbe_save(cr);
    mbe_transform(cr, dev_matrix);
    mbe_arc(cr, 0, 0, rx, angle_start, angle_stop);
    mbe_restore(cr);

    *pnts_p = pnts;
    *float_args_p = float_args;
}

/* ============================================================ */

static void sh_path_free(shape_t *shape) {
    sh_path_t *path = (sh_path_t *)shape;

    mb_obj_destroy(path);
    if(path->user_data)
	free(path->user_data);
    elmpool_elm_free(path->rdman->sh_path_pool, path);
}

/*! \brief Count number of arguments.
 *
 * \todo Notify programmers that syntax or value error of path data.
 */
static int sh_path_cmd_arg_cnt(const char *data, int *cmd_cntp, int *pnt_cntp,
			       int *float_arg_cntp) {
    const char *p, *old;
    int cmd_cnt, pnt_cnt, float_arg_cnt;
    int i;

    cmd_cnt = pnt_cnt = float_arg_cnt = 0;
    p = data;
    SKIP_SPACE(p);
    while(*p) {
	switch(*p++) {
	case 'c':
	case 'C':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		cmd_cnt++;
	    }
	    break;
	case 's':
	case 'S':
	case 'q':
	case 'Q':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		cmd_cnt++;
	    }
	    break;
	case 'm':
	case 'M':
	case 'l':
	case 'L':
	case 't':
	case 'T':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		pnt_cnt++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		pnt_cnt++;

		cmd_cnt++;
	    }
	    break;
	case 'h':
	case 'H':
	case 'v':
	case 'V':
	    while(*p) {
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		pnt_cnt += 2;

		cmd_cnt++;
	    }
	    break;
	case 'A':
	case 'a':
	    while(*p) {
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;

		for(i = 0; i < 6; i++) {
		    SKIP_SPACE(p);
		    old = p;
		    SKIP_NUM(p);
		    if(p == old)
			return ERR;
		}

		pnt_cnt += 10;
		float_arg_cnt += 7;

		cmd_cnt++;
	    }
	    break;
	case 'z':
	case 'Z':
	    cmd_cnt++;
	    break;
	default:
	    return ERR;
	}
	/*! \todo cmd_cnt should be increased for each implicit repeating. */
	SKIP_SPACE(p);
    }

    *cmd_cntp = cmd_cnt;
    *pnt_cntp = pnt_cnt;
    *float_arg_cntp = float_arg_cnt;
    return OK;
}

#define TO_ABSX islower(cmd)? x + atof(old): atof(old)
#define TO_ABSY islower(cmd)? y + atof(old): atof(old)

static int sh_path_cmd_arg_fill(const char *data, sh_path_t *path) {
    const char *p, *old;
    char *cmds;
    char cmd;
    co_aix *pnts;
    co_aix *float_args;
    co_aix sx = 0, sy = 0;
    co_aix x, y;
    int r;

    cmds = path->user_data;
    pnts = (co_aix *)(cmds + path->cmd_len);
    float_args = (co_aix *)(cmds + path->cmd_len +
			    path->pnt_len * sizeof(co_aix));
    p = data;
    SKIP_SPACE(p);
    while(*p) {
	/* Transform all relative to absolute, */
	x = *(pnts - 2);
	y = *(pnts - 1);

	switch((cmd = *p++)) {
	case 'c':
	case 'C':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		*pnts = TO_ABSX;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSX;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		pnts++;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSX;
		x = *pnts;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		y = *pnts;
		pnts++;

		*cmds++ = toupper(cmd);
	    }
	    break;
	case 's':
	case 'S':
	case 'q':
	case 'Q':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		*pnts = TO_ABSX;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSX;
		x = *pnts;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		y = *pnts;
		pnts++;

		*cmds++ = toupper(cmd);
	    }
	    break;

	case 'm':
	case 'M':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		*pnts = TO_ABSX;
		x = *pnts;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		y = *pnts;
		pnts++;

		*cmds++ = toupper(cmd);

		/* save initial point of a subpath */
		sx = x;
		sy = y;
	    }
	    break;

	case 'l':
	case 'L':
	case 't':
	case 'T':
	    while(*p) {
		old = p;
		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    break;
		*pnts = TO_ABSX;
		x = *pnts;
		pnts++;

		SKIP_SPACE(p);
		old = p;
		SKIP_NUM(p);
		if(p == old)
		    return ERR;
		*pnts = TO_ABSY;
		y = *pnts;
		pnts++;

		*cmds++ = toupper(cmd);
	    }
	    break;
	case 'h':
	case 'H':
	case 'v':
	case 'V':
	    /*! \todo implement h, H, v, V comamnds for path. */
	    return ERR;

	case 'A':
	case 'a':
	    r = _sh_path_arc_cmd_arg_fill(cmd, &cmds,
					  (const char **)&p, &pnts,
					  &float_args);
	    if(r != OK)
		return ERR;
	    break;

	case 'z':
	case 'Z':
	    *cmds++ = toupper(cmd);
	    /* Go back to initial point of a subpath */
	    x = sx;
	    y = sy;
	    break;
	default:
	    return ERR;
	}
	SKIP_SPACE(p);
    }

    return OK;
}

/*! \brief Create a path from value of 'data' of SVG path.
 */
shape_t *rdman_shape_path_new(redraw_man_t *rdman, const char *data) {
    sh_path_t *path;
    int cmd_cnt, pnt_cnt, float_arg_cnt;
    int msz;
    int r;

    r = sh_path_cmd_arg_cnt(data, &cmd_cnt, &pnt_cnt,
			    &float_arg_cnt);
    if(r == ERR)
	return NULL;

    /* Align at 4's boundary and keep 2 unused co_aix space
     * to make logic of transformation from relative to absolute
     * simple.
     */
    cmd_cnt += RESERVED_AIXS;
    cmd_cnt = (cmd_cnt + 3) & ~0x3;

    /*! \todo Use elmpool to manage sh_path_t objects. */
    path = (sh_path_t *)elmpool_elm_alloc(rdman->sh_path_pool);
    /*! \todo Remove this memset()? */
    memset(&path->shape, 0, sizeof(shape_t));
    mb_obj_init(path, MBO_PATH);
    path->cmd_len = cmd_cnt;
    path->pnt_len = pnt_cnt;
    path->float_arg_len = float_arg_cnt;

    msz = cmd_cnt + sizeof(co_aix) * pnt_cnt +
	sizeof(co_aix) * float_arg_cnt;
    path->user_data = (char *)malloc(msz * 2);
    if(path->user_data == NULL) {
	elmpool_elm_free(rdman->sh_path_pool, path);
	return NULL;
    }

    path->dev_data = path->user_data + msz;

    r = sh_path_cmd_arg_fill(data, path);
    if(r == ERR) {
	free(path->user_data);
	elmpool_elm_free(rdman->sh_path_pool, path);
	return NULL;
    }
    memcpy(path->dev_data, path->user_data, msz);

    path->shape.free = sh_path_free;
    path->rdman = rdman;

    rdman_man_shape(rdman, (shape_t *)path);

    return (shape_t *)path;
}

shape_t *rdman_shape_path_new_from_binary(redraw_man_t *rdman,
					  char *commands,
					  co_aix *pnts, int pnt_cnt,
					  co_aix *float_args,
					  int float_arg_cnt) {
    sh_path_t *path;
    int msz;
    int cmd_cnt = strlen(commands);

    /*! \todo Use elmpool to manage sh_path_t objects. */
    path = (sh_path_t *)elmpool_elm_alloc(rdman->sh_path_pool);
    /*! \todo Remove this memset()? */
    memset(&path->shape, 0, sizeof(shape_t));
    mb_obj_init(path, MBO_PATH);
    cmd_cnt = (cmd_cnt + 3) & ~0x3;
    path->cmd_len = cmd_cnt;
    path->pnt_len = pnt_cnt;
    path->float_arg_len = float_arg_cnt;
    msz = cmd_cnt + sizeof(co_aix) * pnt_cnt +
	sizeof(co_aix) * float_arg_cnt;
    path->user_data = (char *)malloc(msz * 2);
    if(path->user_data == NULL) {
	elmpool_elm_free(rdman->sh_path_pool, path);
	return NULL;
    }

    path->dev_data = path->user_data + msz;
    memcpy(path->user_data, commands, strlen(commands) + 1);
    memcpy(path->user_data + cmd_cnt, pnts, sizeof(co_aix) * pnt_cnt);
    memcpy(path->user_data + cmd_cnt + pnt_cnt * sizeof(co_aix),
	   float_args, sizeof(co_aix) * float_arg_cnt);
    memcpy(path->dev_data, path->user_data, msz);

    path->shape.free = sh_path_free;
    path->rdman = rdman;

    rdman_man_shape(rdman, (shape_t *)path);

    return (shape_t *)path;
}

shape_t *
rdman_shape_path_clone(redraw_man_t *rdman, const shape_t *_src_path) {
    const sh_path_t *src_path = (const sh_path_t *)_src_path;
    sh_path_t *new_path;
    char *udata;
    char *cmds;
    co_aix *pnts, *float_args;

    udata = src_path->user_data;
    cmds = udata;
    pnts = (co_aix *)(udata + src_path->cmd_len);
    float_args = pnts + src_path->pnt_len;
    new_path =
	(sh_path_t *)rdman_shape_path_new_from_binary(rdman,
						      cmds,
						      pnts, src_path->pnt_len,
						      float_args,
						      src_path->float_arg_len);
    if(new_path == NULL)
	return NULL;

    sh_copy_style(rdman, (shape_t *)src_path, (shape_t *)new_path);

    return (shape_t *)new_path;
}


/*! \brief Transform a path from user space to device space.
 *
 */
void sh_path_transform(shape_t *shape) {
    sh_path_t *path;
    co_aix *pnts, *dev_pnts;
    co_aix (*poses)[2];
    area_t *area;
    int pnt_len;
    int i;

    ASSERT(shape->type == SHT_PATH);
    ASSERT((shape->pnt_len & 0x1) == 0);

    path = (sh_path_t *)shape;
    pnts = (co_aix *)(path->user_data + path->cmd_len);
    dev_pnts = (co_aix *)(path->dev_data + path->cmd_len);
    pnt_len = path->pnt_len;
    for(i = 0; i < pnt_len; i += 2) {
	dev_pnts[0] = *pnts++;
	dev_pnts[1] = *pnts++;
	coord_trans_pos(shape->coord, dev_pnts, dev_pnts + 1);
	dev_pnts += 2;
    }

    if(path->shape.geo) {
	poses = (co_aix (*)[2])(path->dev_data + path->cmd_len);
	geo_from_positions(path->shape.geo, pnt_len / 2, poses);
	area = shape->geo->cur_area;
	area->x -= shape->stroke_width / 2 + 0.5;
	area->y -= shape->stroke_width / 2 + 0.5;
	area->w += shape->stroke_width + 1;
	area->h += shape->stroke_width + 1;
    }
}

static void sh_path_path(shape_t *shape, mbe_t *cr) {
    sh_path_t *path;
    int cmd_len;
    char *cmds, cmd;
    const co_aix *pnts;
    const co_aix *float_args;
    co_aix x, y, x1, y1, x2, y2;
    int i;

    ASSERT(shape->type == SHT_PATH);

    path = (sh_path_t *)shape;
    cmd_len = path->cmd_len;
    cmds = path->dev_data;
    pnts = (co_aix *)(cmds + cmd_len);
    float_args = (co_aix *)(cmds + cmd_len + path->pnt_len * sizeof(co_aix));
    x = y = x1 = y1 = x2 = y2 = 0;
    for(i = 0; i < cmd_len; i++) {
	/* All path commands and arguments are transformed
	 * to absoluted form.
	 */
	cmd = *cmds++;
	switch(cmd) {
	case 'M':
	    x = *pnts++;
	    y = *pnts++;
	    mbe_move_to(cr, x, y);
	    break;
	case 'L':
	    x = *pnts++;
	    y = *pnts++;
	    mbe_line_to(cr, x, y);
	    break;
	case 'C':
	    x1 = *pnts++;
	    y1 = *pnts++;
	    x2 = *pnts++;
	    y2 = *pnts++;
	    x = *pnts++;
	    y = *pnts++;
	    mbe_curve_to(cr, x1, y1, x2, y2, x, y);
	    break;
	case 'S':
	    x1 = x + x - x2;
	    y1 = y + y - y2;
	    x2 = *pnts++;
	    y2 = *pnts++;
	    x = *pnts++;
	    y = *pnts++;
	    mbe_curve_to(cr, x1, y1, x2, y2, x, y);
	    break;
	case 'Q':
	    x1 = *pnts++;
	    y1 = *pnts++;
	    x2 = x1;
	    y2 = y1;
	    x = *pnts++;
	    y = *pnts++;
	    mbe_curve_to(cr, x1, y1, x2, y2, x, y);
	    break;
	case 'T':
	    x1 = x + x - x2;
	    y1 = y + y - y2;
	    x2 = x1;
	    y2 = y1;
	    x = *pnts++;
	    y = *pnts++;
	    mbe_curve_to(cr, x1, y1, x2, y2, x, y);
	    break;
	case 'A':
	    _sh_path_arc_path(cr, path, &pnts, &float_args);
	    break;
	case 'Z':
	    mbe_close_path(cr);
	    break;
	case '\x0':
	    i = cmd_len;	/* padding! Skip remain ones. */
	    break;
	}
    }
}

void sh_path_draw(shape_t *shape, mbe_t *cr) {
    sh_path_path(shape, cr);
}

#ifdef UNITTEST

#include <CUnit/Basic.h>

void test_rdman_shape_path_new(void) {
    sh_path_t *path;
    co_aix *pnts;
    redraw_man_t rdman;

    path = (sh_path_t *)rdman_shape_path_new(&rdman, "M 33 25l33 55c 33 87 44 22 55 99L33 77z");
    CU_ASSERT(path != NULL);
    CU_ASSERT(path->cmd_len == ((5 + RESERVED_AIXS + 3) & ~0x3));
    CU_ASSERT(path->pnt_len == 12);
    CU_ASSERT(strncmp(path->user_data, "MLCLZ", 5) == 0);
    CU_ASSERT(strncmp(path->dev_data, "MLCLZ", 5) == 0);

    pnts = (co_aix *)(path->user_data + path->cmd_len);
    CU_ASSERT(pnts[0] == 33);
    CU_ASSERT(pnts[1] == 25);
    CU_ASSERT(pnts[2] == 66);
    CU_ASSERT(pnts[3] == 80);
    CU_ASSERT(pnts[4] == 99);
    CU_ASSERT(pnts[5] == 167);
    CU_ASSERT(pnts[6] == 110);
    CU_ASSERT(pnts[7] == 102);
    CU_ASSERT(pnts[8] == 121);
    CU_ASSERT(pnts[9] == 179);
    CU_ASSERT(pnts[10] == 33);
    CU_ASSERT(pnts[11] == 77);
    sh_path_free((shape_t *)path);
}

void test_path_transform(void) {
    sh_path_t *path;
    co_aix *pnts;
    coord_t coord;
    geo_t geo;
    redraw_man_t rdman;

    path = (sh_path_t *)rdman_shape_path_new(&rdman, "M 33 25l33 55C 33 87 44 22 55 99L33 77z");
    CU_ASSERT(path != NULL);
    CU_ASSERT(path->cmd_len == ((5 + RESERVED_AIXS + 3) & ~0x3));
    CU_ASSERT(path->pnt_len == 12);
    CU_ASSERT(strncmp(path->user_data, "MLCLZ", 5) == 0);
    CU_ASSERT(strncmp(path->dev_data, "MLCLZ", 5) == 0);

    geo_init(&geo);
    path->shape.geo = &geo;
    geo.shape = (shape_t *)path;

    coord.aggr_matrix[0] = 1;
    coord.aggr_matrix[1] = 0;
    coord.aggr_matrix[2] = 1;
    coord.aggr_matrix[3] = 0;
    coord.aggr_matrix[4] = 2;
    coord.aggr_matrix[5] = 0;
    path->shape.coord = &coord;
    sh_path_transform((shape_t *)path);

    pnts = (co_aix *)(path->dev_data + path->cmd_len);
    CU_ASSERT(pnts[0] == 34);
    CU_ASSERT(pnts[1] == 50);
    CU_ASSERT(pnts[2] == 67);
    CU_ASSERT(pnts[3] == 160);
    CU_ASSERT(pnts[4] == 34);
    CU_ASSERT(pnts[5] == 174);
    CU_ASSERT(pnts[6] == 45);
    CU_ASSERT(pnts[7] == 44);
    CU_ASSERT(pnts[8] == 56);
    CU_ASSERT(pnts[9] == 198);
    CU_ASSERT(pnts[10] == 34);
    CU_ASSERT(pnts[11] == 154);

    sh_path_free((shape_t *)path);
}

void test_small_slope(void) {
    co_aix x, y;
    co_aix slope;
    co_aix r;

    x = 135.3;
    y = 149.6;
    r = (co_aix)_small_slope(x * FRACTION_ONE,
			     y * FRACTION_ONE) /
	FRACTION_ONE;

    slope = MIN(x, y) / MAX(x, y);
    CU_ASSERT(((r - slope) / slope) < 0.01 &&
	      ((r - slope) / slope) > -0.01);
}

void test_find_slope_index(void) {
    co_aix slope;
    int idx;
    co_aix r;

    slope = 0.754;
    idx = _find_slope_index(slope * FRACTION_ONE);
    r = (co_aix)slope_tab[idx] / FRACTION_ONE;
    CU_ASSERT((r / slope) < 1.01 &&
	      (r / slope) > 0.99);
}

void test_vector_len(void) {
    co_aix x, y;
    co_aix len;
    co_aix r;
    int rlen;

    x = 397;
    y = 449;
    len = sqrt(x * x + y * y);
    rlen = _vector_len(x * FRACTION_ONE,
		       y * FRACTION_ONE);
    r = (co_aix)rlen / (1 <<FRACTION_SHIFT);
    CU_ASSERT((r / len) < 1.01 &&
	      (r / len) > 0.99);

    x = 357;
    y = 224;
    len = sqrt(x * x + y * y);
    rlen = _vector_len(x * FRACTION_ONE,
		       y * FRACTION_ONE);
    r = (co_aix)rlen / FRACTION_ONE;
    CU_ASSERT((r / len) < 1.01 &&
	      (r / len) > 0.99);
}

void test_find_arc_radius(void) {
    co_aix ratio;
    int idx;
    co_aix r;

    ratio = 0.732;
    idx = _find_arc_radius(ratio * FRACTION_ONE);
    r = (co_aix)arc_radius_ratio_tab[idx] / FRACTION_ONE;
    CU_ASSERT((r / ratio) < 1.01 &&
	      (r / ratio) > 0.99);
}

void test_get_arc_radius_shift_factor(void) {
    co_aix arc_x, arc_y, radius;
    co_aix factor;
    int rfactor;
    co_aix r;

    arc_x = 30.5;
    arc_y = 10.3;
    radius = 90.3;
    factor = sqrt(radius * radius - (arc_x * arc_x + arc_y * arc_y) / 4) /
	radius;
    rfactor = _get_arc_radius_shift_factor(arc_x * FRACTION_ONE,
					   arc_y * FRACTION_ONE,
					   radius * FRACTION_ONE);
    r = (co_aix)rfactor / FRACTION_ONE;
    CU_ASSERT((r / factor) < 1.01 &&
	      (r / factor) > 0.99);

    arc_x = 30.5;
    arc_y = 70.3;
    radius = 190.3;
    factor = sqrt(radius * radius - (arc_x * arc_x + arc_y * arc_y) / 4) /
	radius;
    rfactor = _get_arc_radius_shift_factor(arc_x * FRACTION_ONE,
					   arc_y * FRACTION_ONE,
					   radius * FRACTION_ONE);
    r = (co_aix)rfactor / FRACTION_ONE;
    CU_ASSERT((r / factor) < 1.01 &&
	      (r / factor) > 0.99);
}

void test_compute_extend_unit_vector(void) {
    co_aix rx, ry;
    co_aix x_rotate;
    co_aix unit_x, unit_y;
    co_aix runit_x, runit_y;
    int64_t ext_unit_x, ext_unit_y;

    rx = 200;
    ry = 153;
    x_rotate = PI * 30 / 180;
    unit_x = cos(PI * 90 / 180 + x_rotate);
    unit_y = sin(PI * 90 / 180 + x_rotate);
    _compute_extend_unit_vector(rx * FRACTION_ONE, ry * FRACTION_ONE,
				x_rotate * FRACTION_ONE,
				&ext_unit_x, &ext_unit_y);
    runit_x = (co_aix)ext_unit_x / FRACTION_ONE;
    runit_y = (co_aix)ext_unit_y / FRACTION_ONE;
    CU_ASSERT((runit_x / unit_x) < 1.01 &&
	      (runit_x / unit_x) > 0.99);
    CU_ASSERT((runit_y / unit_y) < 1.01 &&
	      (runit_y / unit_y) > 0.99);

    rx = 200;
    ry = 153;
    x_rotate = PI * 158 / 180;
    unit_x = cos(PI * 90 / 180 + x_rotate);
    unit_y = sin(PI * 90 / 180 + x_rotate);
    _compute_extend_unit_vector(rx * FRACTION_ONE, ry * FRACTION_ONE,
				x_rotate * FRACTION_ONE,
				&ext_unit_x, &ext_unit_y);
    runit_x = (co_aix)ext_unit_x / FRACTION_ONE;
    runit_y = (co_aix)ext_unit_y / FRACTION_ONE;
    CU_ASSERT((runit_x / unit_x) < 1.01 &&
	      (runit_x / unit_x) > 0.99);
    CU_ASSERT((runit_y / unit_y) < 1.01 &&
	      (runit_y / unit_y) > 0.99);

    rx = 100;
    ry = 153;
    x_rotate = PI * 158 / 180;
    unit_x = cos(x_rotate);
    unit_y = sin(x_rotate);
    _compute_extend_unit_vector(rx * FRACTION_ONE, ry * FRACTION_ONE,
				x_rotate * FRACTION_ONE,
				&ext_unit_x, &ext_unit_y);
    runit_x = (co_aix)ext_unit_x / FRACTION_ONE;
    runit_y = (co_aix)ext_unit_y / FRACTION_ONE;
    CU_ASSERT((runit_x / unit_x) < 1.01 &&
	      (runit_x / unit_x) > 0.99);
    CU_ASSERT((runit_y / unit_y) < 1.01 &&
	      (runit_y / unit_y) > 0.99);
}

void test_get_center_ref_shift(void) {
    co_aix slope;
    int slope_index;
    co_aix arc_len;
    co_aix arc_x, arc_y;
    int large, sweep;
    co_aix shift_x, shift_y;
    co_aix r_x, r_y;
    int64_t rshift_x, rshift_y;

    arc_x = 311;
    arc_y = 210;
    large = 0;			/* small arc */
    sweep = 1;			/* positive sweep */
    arc_len = sqrt(arc_x * arc_x + arc_y * arc_y);
    shift_x = arc_y / arc_len * (1 << REF_RADIUS_SHIFT);
    shift_y = arc_x / arc_len * (1 << REF_RADIUS_SHIFT);
    if((arc_x < 0) ^ (arc_y < 0))
	/* exactly one of arc_x and arc_y is negative */
	shift_y = -shift_y;
    else
	shift_x = -shift_x;
    slope = MIN(ABS(arc_x), ABS(arc_y)) /  MAX(ABS(arc_x), ABS(arc_y));
    slope_index = _find_slope_index(slope * FRACTION_ONE);
    _get_center_ref_shift(arc_x * FRACTION_ONE, arc_y * FRACTION_ONE,
			  large, sweep,
			  slope_index,
			  &rshift_x, &rshift_y);
    r_x = (co_aix)rshift_x / FRACTION_ONE;
    r_y = (co_aix)rshift_y / FRACTION_ONE;
    CU_ASSERT((r_x / shift_x) < 1.01 &&
	      (r_x / shift_x) > 0.99);
    CU_ASSERT((r_y / shift_y) < 1.01 &&
	      (r_y / shift_y) > 0.99);

    arc_x = 311;
    arc_y = 210;
    large = 1;			/* small arc */
    sweep = 1;			/* positive sweep */
    arc_len = sqrt(arc_x * arc_x + arc_y * arc_y);
    shift_x = -arc_y / arc_len * (1 << REF_RADIUS_SHIFT);
    shift_y = -arc_x / arc_len * (1 << REF_RADIUS_SHIFT);
    if((arc_x < 0) ^ (arc_y < 0))
	/* exactly one of arc_x and arc_y is negative */
	shift_y = -shift_y;
    else
	shift_x = -shift_x;
    slope = MIN(ABS(arc_x), ABS(arc_y)) /  MAX(ABS(arc_x), ABS(arc_y));
    slope_index = _find_slope_index(slope * FRACTION_ONE);
    _get_center_ref_shift(arc_x * FRACTION_ONE, arc_y * FRACTION_ONE,
			  large, sweep,
			  slope_index,
			  &rshift_x, &rshift_y);
    r_x = (co_aix)rshift_x / FRACTION_ONE;
    r_y = (co_aix)rshift_y / FRACTION_ONE;
    CU_ASSERT((r_x / shift_x) < 1.01 &&
	      (r_x / shift_x) > 0.99);
    CU_ASSERT((r_y / shift_y) < 1.01 &&
	      (r_y / shift_y) > 0.99);
}

void test_calc_center(void) {
    co_aix x0, y0, x, y;
    co_aix rx, ry, x_rotate;
    int large, sweep;
    co_aix cx, cy;
    co_aix angle_start, angle_stop;
    co_aix rcx, rcy;
    co_aix _x, _y;
    
#define ELLIPSE_POINT(angle, point_x, point_y)			\
    do {							\
	_x = rx * cos(angle);					\
	_y = ry * sin(angle);					\
	point_x = _x * cos(x_rotate) - _y * sin(x_rotate) + cx;	\
	point_y = _x * sin(x_rotate) + _y * cos(x_rotate) + cy;	\
    } while(0)
#define CENTER_TEST()						\
    do {							\
	_calc_center(x0, y0, x, y, rx, ry, x_rotate,		\
		     0, 1, &rcx, &rcy);				\
	CU_ASSERT((cx - rcx) <= 2 && (cx - rcx) >= -2);		\
	CU_ASSERT((cy - rcy) <= 2 && (cy - rcy) >= -2);		\
	_calc_center(x0, y0, x, y, rx, ry, x_rotate,		\
		     1, 0, &rcx, &rcy);				\
	CU_ASSERT((cx - rcx) <= 2 && (cx - rcx) >= -2);		\
	CU_ASSERT((cy - rcy) <= 2 && (cy - rcy) >= -2);		\
	_calc_center(x, y, x0, y0, rx, ry, x_rotate,		\
		     0, 0, &rcx, &rcy);				\
	CU_ASSERT((cx - rcx) <= 2 && (cx - rcx) >= -2);		\
	CU_ASSERT((cy - rcy) <= 2 && (cy - rcy) >= -2);		\
	_calc_center(x, y, x0, y0, rx, ry, x_rotate,		\
		     1, 1, &rcx, &rcy);				\
	CU_ASSERT((cx - rcx) <= 2 && (cx - rcx) >= -2);		\
	CU_ASSERT((cy - rcy) <= 2 && (cy - rcy) >= -2);		\
    } while(0)

    cx = 135;
    cy = 254;
    rx = 200;
    ry = 170;
    x_rotate = PI * 20 / 180;
    angle_start = PI * 55 / 180;
    angle_stop = PI * 97 / 180;
    
    ELLIPSE_POINT(angle_start, x0, y0);
    ELLIPSE_POINT(angle_stop, x, y);
    CENTER_TEST();
}

void test_spaces_head_tail(void) {
    sh_path_t *path;
    redraw_man_t rdman;

    path = (sh_path_t *)
	rdman_shape_path_new(&rdman,
			     " M 33 25l33 55C 33 87 44 22 55 99L33 77z ");
    CU_ASSERT(path != NULL);
    sh_path_free((shape_t *)path);
}

CU_pSuite get_shape_path_suite(void) {
    CU_pSuite suite;

    suite = CU_add_suite("Suite_shape_path", NULL, NULL);
    CU_ADD_TEST(suite, test_rdman_shape_path_new);
    CU_ADD_TEST(suite, test_path_transform);
    CU_ADD_TEST(suite, test_small_slope);
    CU_ADD_TEST(suite, test_find_slope_index);
    CU_ADD_TEST(suite, test_vector_len);
    CU_ADD_TEST(suite, test_find_arc_radius);
    CU_ADD_TEST(suite, test_get_arc_radius_shift_factor);
    CU_ADD_TEST(suite, test_compute_extend_unit_vector);
    CU_ADD_TEST(suite, test_get_center_ref_shift);
    CU_ADD_TEST(suite, test_calc_center);

    return suite;
}

#endif /* UNITTEST */