/* * GooCanvas. Copyright (C) 2005-6 Damon Chaplin. * Released under the GNU LGPL license. See COPYING for details. * * goocanvaspathview.c - view for path item. */ /** * SECTION:goocanvaspathview * @Title: GooCanvasPathView * @Short_Description: a view for a #GooCanvasPath item. * * #GooCanvasPathView represents a view of a #GooCanvasPath item. * * It implements the #GooCanvasItemView interface, so you can use the * #GooCanvasItemView functions such as goo_canvas_item_view_get_item() * and goo_canvas_item_view_get_bounds(). * * Applications do not normally need to create item views themselves, as * they are created automatically by #GooCanvasView when needed. * * To respond to events such as mouse clicks in the ellipse view you can * connect to one of the #GooCanvasItemView signals such as * #GooCanvasItemView::button-press-event. You can connect to these signals * when the view is created. (See goo_canvas_view_get_item_view() and * #GooCanvasView::item-view-created.) */ #include #include #include #include "goocanvaspathview.h" static void goo_canvas_path_view_create_path (GooCanvasItemSimple *simple, cairo_t *cr); G_DEFINE_TYPE_WITH_CODE (GooCanvasPathView, goo_canvas_path_view, GOO_TYPE_CANVAS_ITEM_VIEW_SIMPLE, G_IMPLEMENT_INTERFACE (GOO_TYPE_CANVAS_ITEM_VIEW, NULL)) static void goo_canvas_path_view_class_init (GooCanvasPathViewClass *klass) { GooCanvasItemViewSimpleClass *simple_view_class = (GooCanvasItemViewSimpleClass*) klass; simple_view_class->create_path = goo_canvas_path_view_create_path; } static void goo_canvas_path_view_init (GooCanvasPathView *path_view) { } /** * goo_canvas_path_view_new: * @canvas_view: the canvas view. * @parent_view: the parent view. * @path: the path item. * * Creates a new #GooCanvasPathView for the given #GooCanvasPath item. * * This is not normally used by application code, as the views are created * automatically by #GooCanvasView. * * Returns: a new #GooCanvasPathView. **/ GooCanvasItemView* goo_canvas_path_view_new (GooCanvasView *canvas_view, GooCanvasItemView *parent_view, GooCanvasPath *path) { GooCanvasItemViewSimple *view; view = g_object_new (GOO_TYPE_CANVAS_PATH_VIEW, NULL); view->canvas_view = canvas_view; view->parent_view = parent_view; view->item = g_object_ref (path); goo_canvas_item_view_simple_setup_accessibility (view); g_signal_connect (path, "changed", G_CALLBACK (goo_canvas_item_view_simple_item_changed), view); return (GooCanvasItemView*) view; } static void do_curve_to (GooCanvasPathCommand *cmd, cairo_t *cr, gdouble *x, gdouble *y, gdouble *last_control_point_x, gdouble *last_control_point_y) { if (cmd->curve.relative) { cairo_curve_to (cr, *x + cmd->curve.x1, *y + cmd->curve.y1, *x + cmd->curve.x2, *y + cmd->curve.y2, *x + cmd->curve.x, *y + cmd->curve.y); *last_control_point_x = *x + cmd->curve.x2; *last_control_point_y = *y + cmd->curve.y2; *x += cmd->curve.x; *y += cmd->curve.y; } else { cairo_curve_to (cr, cmd->curve.x1, cmd->curve.y1, cmd->curve.x2, cmd->curve.y2, cmd->curve.x, cmd->curve.y); *last_control_point_x = cmd->curve.x2; *last_control_point_y = cmd->curve.y2; *x = cmd->curve.x; *y = cmd->curve.y; } } static void do_smooth_curve_to (GooCanvasPathCommand *cmd, GooCanvasPathCommandType prev_cmd_type, cairo_t *cr, gdouble *x, gdouble *y, gdouble *last_control_point_x, gdouble *last_control_point_y) { gdouble x1, y1; /* If the last command was a curveto or smooth curveto, we use the reflection of the last control point about the current point as the first control point of this curve. Otherwise we use the current point as the first control point. */ if (prev_cmd_type == GOO_CANVAS_PATH_CURVE_TO || prev_cmd_type == GOO_CANVAS_PATH_SMOOTH_CURVE_TO) { x1 = *x + (*x - *last_control_point_x); y1 = *y + (*y - *last_control_point_y); } else { x1 = *x; y1 = *y; } if (cmd->curve.relative) { cairo_curve_to (cr, x1, y1, *x + cmd->curve.x2, *y + cmd->curve.y2, *x + cmd->curve.x, *y + cmd->curve.y); *last_control_point_x = *x + cmd->curve.x2; *last_control_point_y = *y + cmd->curve.y2; *x += cmd->curve.x; *y += cmd->curve.y; } else { cairo_curve_to (cr, x1, y1, cmd->curve.x2, cmd->curve.y2, cmd->curve.x, cmd->curve.y); *last_control_point_x = cmd->curve.x2; *last_control_point_y = cmd->curve.y2; *x = cmd->curve.x; *y = cmd->curve.y; } } static void do_quadratic_curve_to (GooCanvasPathCommand *cmd, cairo_t *cr, gdouble *x, gdouble *y, gdouble *last_control_point_x, gdouble *last_control_point_y) { gdouble qx1, qy1, qx2, qy2, x1, y1, x2, y2; if (cmd->curve.relative) { qx1 = *x + cmd->curve.x1; qy1 = *y + cmd->curve.y1; qx2 = *x + cmd->curve.x; qy2 = *y + cmd->curve.y; } else { qx1 = cmd->curve.x1; qy1 = cmd->curve.y1; qx2 = cmd->curve.x; qy2 = cmd->curve.y; } /* We need to convert the quadratic into a cubic bezier. */ x1 = *x + (qx1 - *x) * 2.0 / 3.0; y1 = *y + (qy1 - *y) * 2.0 / 3.0; x2 = x1 + (qx2 - *x) / 3.0; y2 = y1 + (qy2 - *y) / 3.0; cairo_curve_to (cr, x1, y1, x2, y2, qx2, qy2); *x = qx2; *y = qy2; *last_control_point_x = qx1; *last_control_point_y = qy1; } static void do_smooth_quadratic_curve_to (GooCanvasPathCommand *cmd, GooCanvasPathCommandType prev_cmd_type, cairo_t *cr, gdouble *x, gdouble *y, gdouble *last_control_point_x, gdouble *last_control_point_y) { gdouble qx1, qy1, qx2, qy2, x1, y1, x2, y2; /* If the last command was a quadratic or smooth quadratic, we use the reflection of the last control point about the current point as the first control point of this curve. Otherwise we use the current point as the first control point. */ if (prev_cmd_type == GOO_CANVAS_PATH_QUADRATIC_CURVE_TO || prev_cmd_type == GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO) { qx1 = *x + (*x - *last_control_point_x); qy1 = *y + (*y - *last_control_point_y); } else { qx1 = *x; qy1 = *y; } if (cmd->curve.relative) { qx2 = *x + cmd->curve.x; qy2 = *y + cmd->curve.y; } else { qx2 = cmd->curve.x; qy2 = cmd->curve.y; } /* We need to convert the quadratic into a cubic bezier. */ x1 = *x + (qx1 - *x) * 2.0 / 3.0; y1 = *y + (qy1 - *y) * 2.0 / 3.0; x2 = x1 + (qx2 - *x) / 3.0; y2 = y1 + (qy2 - *y) / 3.0; cairo_curve_to (cr, x1, y1, x2, y2, qx2, qy2); *x = qx2; *y = qy2; *last_control_point_x = qx1; *last_control_point_y = qy1; } static gdouble calc_angle (gdouble ux, gdouble uy, gdouble vx, gdouble vy) { gdouble top, u_magnitude, v_magnitude, angle_cos, angle; top = ux * vx + uy * vy; u_magnitude = sqrt (ux * ux + uy * uy); v_magnitude = sqrt (vx * vx + vy * vy); angle_cos = top / (u_magnitude * v_magnitude); /* We check if the cosine is slightly out-of-bounds. */ if (angle_cos >= 1.0) angle = 0.0; if (angle_cos <= -1.0) angle = M_PI; else angle = acos (angle_cos); if (ux * vy - uy * vx < 0) angle = - angle; return angle; } /* FIXME: Maybe we should do these calculations once when the path data is parsed, and keep the cairo parameters we need in the command instead. */ static void do_elliptical_arc (GooCanvasPathCommand *cmd, cairo_t *cr, gdouble *x, gdouble *y) { gdouble x1 = *x, y1 = *y, x2, y2, rx, ry, lambda; gdouble v1, v2, angle, angle_sin, angle_cos, x11, y11; gdouble rx_squared, ry_squared, x11_squared, y11_squared, top, bottom; gdouble c, cx1, cy1, cx, cy, start_angle, angle_delta; /* Calculate the end point of the arc - x2,y2. */ if (cmd->arc.relative) { x2 = x1 + cmd->arc.x; y2 = y1 + cmd->arc.y; } else { x2 = cmd->arc.x; y2 = cmd->arc.y; } *x = x2; *y = y2; /* If the endpoints are exactly the same, just return (see SVG spec). */ if (x1 == x2 && y1 == y2) return; /* If either rx or ry is 0, do a simple lineto (see SVG spec). */ if (cmd->arc.rx == 0.0 || cmd->arc.ry == 0.0) { cairo_line_to (cr, x2, y2); return; } /* Calculate x1' and y1' (as per SVG implementation notes). */ v1 = (x1 - x2) / 2.0; v2 = (y1 - y2) / 2.0; angle = cmd->arc.x_axis_rotation * (M_PI / 180.0); angle_sin = sin (angle); angle_cos = cos (angle); x11 = (angle_cos * v1) + (angle_sin * v2); y11 = - (angle_sin * v1) + (angle_cos * v2); /* Ensure rx and ry are positive and large enough. */ rx = cmd->arc.rx > 0.0 ? cmd->arc.rx : - cmd->arc.rx; ry = cmd->arc.ry > 0.0 ? cmd->arc.ry : - cmd->arc.ry; lambda = (x11 * x11) / (rx * rx) + (y11 * y11) / (ry * ry); if (lambda > 1.0) { gdouble square_root = sqrt (lambda); rx *= square_root; ry *= square_root; } /* Calculate cx' and cy'. */ rx_squared = rx * rx; ry_squared = ry * ry; x11_squared = x11 * x11; y11_squared = y11 * y11; top = (rx_squared * ry_squared) - (rx_squared * y11_squared) - (ry_squared * x11_squared); if (top < 0.0) { c = 0.0; } else { bottom = (rx_squared * y11_squared) + (ry_squared * x11_squared); c = sqrt (top / bottom); } if (cmd->arc.large_arc_flag == cmd->arc.sweep_flag) c = - c; cx1 = c * ((rx * y11) / ry); cy1 = c * (- (ry * x11) / rx); /* Calculate cx and cy. */ cx = (angle_cos * cx1) - (angle_sin * cy1) + (x1 + x2) / 2; cy = (angle_sin * cx1) + (angle_cos * cy1) + (y1 + y2) / 2; /* Calculate the start and end angles. */ v1 = (x11 - cx1) / rx; v2 = (y11 - cy1) / ry; start_angle = calc_angle (1, 0, v1, v2); angle_delta = calc_angle (v1, v2, (-x11 - cx1) / rx, (-y11 - cy1) / ry); if (cmd->arc.sweep_flag == 0 && angle_delta > 0.0) angle_delta -= 2 * M_PI; else if (cmd->arc.sweep_flag == 1 && angle_delta < 0.0) angle_delta += 2 * M_PI; /* Now draw the arc. */ cairo_save (cr); cairo_translate (cr, cx, cy); cairo_rotate (cr, angle); cairo_scale (cr, rx, ry); if (angle_delta > 0.0) cairo_arc (cr, 0.0, 0.0, 1.0, start_angle, start_angle + angle_delta); else cairo_arc_negative (cr, 0.0, 0.0, 1.0, start_angle, start_angle + angle_delta); cairo_restore (cr); } static void goo_canvas_path_view_create_path (GooCanvasItemSimple *simple, cairo_t *cr) { GooCanvasPath *path = (GooCanvasPath*) simple; GooCanvasPathCommand *cmd; GooCanvasPathCommandType prev_cmd_type = GOO_CANVAS_PATH_CLOSE_PATH; gdouble x = 0, y = 0, path_start_x = 0, path_start_y = 0; gdouble last_control_point_x, last_control_point_y; gint i; cairo_new_path (cr); if (!path->commands || path->commands->len == 0) return; for (i = 0; i < path->commands->len; i++) { cmd = &g_array_index (path->commands, GooCanvasPathCommand, i); switch (cmd->simple.type) { /* Simple commands like moveto and lineto: MmZzLlHhVv. */ case GOO_CANVAS_PATH_MOVE_TO: if (cmd->simple.relative) { x += cmd->simple.x; y += cmd->simple.y; } else { x = cmd->simple.x; y = cmd->simple.y; } path_start_x = x; path_start_y = y; cairo_move_to (cr, x, y); break; case GOO_CANVAS_PATH_CLOSE_PATH: x = path_start_x; y = path_start_y; cairo_close_path (cr); break; case GOO_CANVAS_PATH_LINE_TO: if (cmd->simple.relative) { x += cmd->simple.x; y += cmd->simple.y; } else { x = cmd->simple.x; y = cmd->simple.y; } cairo_line_to (cr, x, y); break; case GOO_CANVAS_PATH_HORIZONTAL_LINE_TO: if (cmd->simple.relative) x += cmd->simple.x; else x = cmd->simple.x; cairo_line_to (cr, x, y); break; case GOO_CANVAS_PATH_VERTICAL_LINE_TO: if (cmd->simple.relative) y += cmd->simple.y; else y = cmd->simple.y; cairo_line_to (cr, x, y); break; /* Bezier curve commands: CcSsQqTt. */ case GOO_CANVAS_PATH_CURVE_TO: do_curve_to (cmd, cr, &x, &y, &last_control_point_x, &last_control_point_y); break; case GOO_CANVAS_PATH_SMOOTH_CURVE_TO: do_smooth_curve_to (cmd, prev_cmd_type, cr, &x, &y, &last_control_point_x, &last_control_point_y); break; case GOO_CANVAS_PATH_QUADRATIC_CURVE_TO: do_quadratic_curve_to (cmd, cr, &x, &y, &last_control_point_x, &last_control_point_y); break; case GOO_CANVAS_PATH_SMOOTH_QUADRATIC_CURVE_TO: do_smooth_quadratic_curve_to (cmd, prev_cmd_type, cr, &x, &y, &last_control_point_x, &last_control_point_y); break; /* The elliptical arc commands: Aa. */ case GOO_CANVAS_PATH_ELLIPTICAL_ARC: do_elliptical_arc (cmd, cr, &x, &y); break; } prev_cmd_type = cmd->simple.type; } }