/* Returns the length of path. path must be flattened * (i.e. only consist of line segments) */ static double _rsvg_path_length(cairo_path_t *path) { int i; double distance = 0.0; cairo_path_data_t *last_point, *cur_point, *last_move_to; for (i=0; i < path->num_data; i += path->data[i].header.length) { cairo_path_data_t *data = &path->data[i]; switch (data->header.type) { case CAIRO_PATH_MOVE_TO: last_move_to = data; last_point = &data[1]; break; case CAIRO_PATH_CLOSE_PATH: // Make it look like it's a line_to to last_move_to data = last_move_to; // fall through case CAIRO_PATH_LINE_TO: cur_point = &data[1]; distance += two_points_distance(last_point, cur_point); last_point = cur_point; break; default: g_assert_not_reached (); } } return distance; }
/* Returns length of a Bezier curve. Seems like computing that analytically is not easy. The * code just flattens the curve using cairo and adds the length of segments. */ static double curve_length( double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3 ) { cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_A8, 0, 0); cairo_t* cr = cairo_create(surface); double length = 0; cairo_path_t* path; cairo_path_data_t* data; cairo_path_data_t current_point; int i; current_point.point.x = 0.0; current_point.point.y = 0.0; cairo_surface_destroy(surface); cairo_move_to(cr, x0, y0); cairo_curve_to(cr, x1, y1, x2, y2, x3, y3); path = cairo_copy_path_flat(cr); for(i = 0; i < path->num_data; i += path->data[i].header.length) { data = &path->data[i]; switch (data->header.type) { case CAIRO_PATH_MOVE_TO: current_point = data[1]; break; case CAIRO_PATH_LINE_TO: length += two_points_distance(¤t_point, &data[1]); current_point = data[1]; break; default: break; } } cairo_path_destroy(path); cairo_destroy(cr); return length; }
/* Compute parametrization info. That is, for each part of the cairo path, tags it with * its length. */ static parametrization_t* parametrize_path(cairo_path_t* path) { parametrization_t* parametrization = 0; cairo_path_data_t* data = 0; cairo_path_data_t last_move_to; cairo_path_data_t current_point; int i; current_point.point.x = 0.0; current_point.point.y = 0.0; parametrization = (parametrization_t*)malloc(path->num_data * sizeof(parametrization[0])); for(i = 0; i < path->num_data; i += path->data[i].header.length) { data = &path->data[i]; parametrization[i] = 0.0; switch(data->header.type) { case CAIRO_PATH_MOVE_TO: last_move_to = data[1]; current_point = data[1]; break; case CAIRO_PATH_CLOSE_PATH: /* Make it look like it's a line_to to last_move_to. */ data = (&last_move_to) - 1; case CAIRO_PATH_LINE_TO: parametrization[i] = two_points_distance(¤t_point, &data[1]); current_point = data[1]; break; case CAIRO_PATH_CURVE_TO: parametrization[i] = curve_length( current_point.point.x, current_point.point.x, data[1].point.x, data[1].point.y, data[2].point.x, data[2].point.y, data[3].point.x, data[3].point.y ); current_point = data[3]; break; } } return parametrization; }
/* Compute parametrization info. That is, for each part of the * cairo path, tags it with its length. * * Free returned value with g_free(). */ static parametrization_t * parametrize_path (cairo_path_t *path) { int i; cairo_path_data_t *data, last_move_to, current_point; parametrization_t *parametrization; parametrization = g_malloc (path->num_data * sizeof (parametrization[0])); for (i=0; i < path->num_data; i += path->data[i].header.length) { data = &path->data[i]; parametrization[i] = 0.0; switch (data->header.type) { case CAIRO_PATH_MOVE_TO: last_move_to = data[1]; current_point = data[1]; break; case CAIRO_PATH_CLOSE_PATH: /* Make it look like it's a line_to to last_move_to */ data = (&last_move_to) - 1; /* fall through */ case CAIRO_PATH_LINE_TO: parametrization[i] = two_points_distance (¤t_point, &data[1]); current_point = data[1]; break; case CAIRO_PATH_CURVE_TO: /* naive curve-length, treating bezier as three line segments: parametrization[i] = two_points_distance (¤t_point, &data[1]) + two_points_distance (&data[1], &data[2]) + two_points_distance (&data[2], &data[3]); */ parametrization[i] = curve_length (current_point.point.x, current_point.point.x, data[1].point.x, data[1].point.y, data[2].point.x, data[2].point.y, data[3].point.x, data[3].point.y); current_point = data[3]; break; default: g_assert_not_reached (); } } return parametrization; }
/* Returns the x, y, and tangent vector angle of a point distance * d down a path. path must be flattened. */ static void _rsvg_path_point_on_path(cairo_path_t *path, double d, double *x, double *y, double *theta) { int i; double dist_so_far = 0.0; cairo_path_data_t *last_point, *cur_point, *last_move_to; for (i=0; i < path->num_data; i += path->data[i].header.length) { cairo_path_data_t *data = &path->data[i]; switch (data->header.type) { case CAIRO_PATH_MOVE_TO: last_move_to = data; last_point = &data[1]; break; case CAIRO_PATH_CLOSE_PATH: // Make it look like it's a line_to to last_move_to data = last_move_to; // fall through case CAIRO_PATH_LINE_TO: cur_point = &data[1]; dist_so_far += two_points_distance(last_point, cur_point); // Reached distance d along the path if (dist_so_far > d) { double dlength = dist_so_far - d; double angle = atan2(cur_point->point.y - last_point->point.y, cur_point->point.x - last_point->point.x); *x = dlength * cos(angle) + last_point->point.x; *y = dlength * sin(angle) + last_point->point.y; // Find the normal vector and normalize to [-pi,pi] angle -= G_PI/2.0; if (angle < -G_PI) angle += 2*G_PI; *theta = angle; } else { last_point = cur_point; } break; default: g_assert_not_reached (); } } }