/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set sw=4 sts=4 ts=4 expandtab: */ /* rsvg-shapes.c: Draw SVG shapes Copyright (C) 2000 Eazel, Inc. Copyright (C) 2002 Dom Lachowicz This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Authors: Raph Levien , Dom Lachowicz , Caleb Moore */ #include #include #include #include #include "rsvg-private.h" #include "rsvg-styles.h" #include "rsvg-shapes.h" #include "rsvg-css.h" #include "rsvg-defs.h" #include "rsvg-path.h" /* 4/3 * (1-cos 45ƒ)/sin 45ƒ = 4/3 * sqrt(2) - 1 */ #define RSVG_ARC_MAGIC ((double) 0.5522847498) static void rsvg_node_path_free (RsvgNode * self) { RsvgNodePath *path = (RsvgNodePath *) self; if (path->path) rsvg_cairo_path_destroy (path->path); _rsvg_node_finalize (&path->super); g_free (path); } static void rsvg_node_path_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { RsvgNodePath *path = (RsvgNodePath *) self; if (!path->path) return; rsvg_state_reinherit_top (ctx, self->state, dominate); rsvg_render_path (ctx, path->path); } static void rsvg_node_path_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { const char *klazz = NULL, *id = NULL, *value; RsvgNodePath *path = (RsvgNodePath *) self; if (rsvg_property_bag_size (atts)) { if ((value = rsvg_property_bag_lookup (atts, "d"))) { if (path->path) rsvg_cairo_path_destroy (path->path); path->path = rsvg_parse_path (value); } if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, "path", klazz, id, atts); } } RsvgNode * rsvg_new_path (void) { RsvgNodePath *path; path = g_new (RsvgNodePath, 1); _rsvg_node_init (&path->super, RSVG_NODE_TYPE_PATH); path->path = NULL; path->super.free = rsvg_node_path_free; path->super.draw = rsvg_node_path_draw; path->super.set_atts = rsvg_node_path_set_atts; return &path->super; } struct _RsvgNodePoly { RsvgNode super; cairo_path_t *path; }; typedef struct _RsvgNodePoly RsvgNodePoly; static cairo_path_t * _rsvg_node_poly_build_path (const char *value, gboolean close_path); static void _rsvg_node_poly_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { RsvgNodePoly *poly = (RsvgNodePoly *) self; const char *klazz = NULL, *id = NULL, *value; if (rsvg_property_bag_size (atts)) { /* support for svg < 1.0 which used verts */ if ((value = rsvg_property_bag_lookup (atts, "verts")) || (value = rsvg_property_bag_lookup (atts, "points"))) { if (poly->path) rsvg_cairo_path_destroy (poly->path); poly->path = _rsvg_node_poly_build_path (value, RSVG_NODE_TYPE (self) == RSVG_NODE_TYPE_POLYGON); } if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, RSVG_NODE_TYPE (self) == RSVG_NODE_TYPE_POLYLINE ? "polyline" : "polygon", klazz, id, atts); } } static cairo_path_t * _rsvg_node_poly_build_path (const char *value, gboolean close_path) { double *pointlist; guint pointlist_len, i; RsvgPathBuilder builder; cairo_path_t *path; pointlist = rsvg_css_parse_number_list (value, &pointlist_len); if (pointlist == NULL) return NULL; if (pointlist_len < 2) { g_free (pointlist); return NULL; } /* Calculate the number of cairo_path_data_t we'll need: * * pointlist_len / 2 -> number of commands * pointlist_len / 2 -> number of points * + 1 -> closepath * --------------------------------------------- * pointlist_len + 1 -> total */ rsvg_path_builder_init (&builder, pointlist_len + 1); rsvg_path_builder_move_to (&builder, pointlist[0], pointlist[1]); for (i = 2; i < pointlist_len; i += 2) { double x, y; x = pointlist[i]; /* We expect points to come in coordinate pairs. But if there is a * missing part of one pair in a corrupt SVG, we'll have an incomplete * list. In that case, we reuse the last-known Y coordinate. */ if (i + 1 < pointlist_len) y = pointlist[i + 1]; else y = pointlist[i - 1]; rsvg_path_builder_line_to (&builder, x, y); } if (close_path) rsvg_path_builder_close_path (&builder); path = rsvg_path_builder_finish (&builder); g_free (pointlist); return path; } static void _rsvg_node_poly_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { RsvgNodePoly *poly = (RsvgNodePoly *) self; if (poly->path == NULL) return; rsvg_state_reinherit_top (ctx, self->state, dominate); rsvg_render_path (ctx, poly->path); } static void _rsvg_node_poly_free (RsvgNode * self) { RsvgNodePoly *poly = (RsvgNodePoly *) self; if (poly->path) rsvg_cairo_path_destroy (poly->path); _rsvg_node_finalize (&poly->super); g_free (poly); } static RsvgNode * rsvg_new_any_poly (RsvgNodeType type) { RsvgNodePoly *poly; poly = g_new (RsvgNodePoly, 1); _rsvg_node_init (&poly->super, type); poly->super.free = _rsvg_node_poly_free; poly->super.draw = _rsvg_node_poly_draw; poly->super.set_atts = _rsvg_node_poly_set_atts; poly->path = NULL; return &poly->super; } RsvgNode * rsvg_new_polygon (void) { return rsvg_new_any_poly (RSVG_NODE_TYPE_POLYGON); } RsvgNode * rsvg_new_polyline (void) { return rsvg_new_any_poly (RSVG_NODE_TYPE_POLYLINE); } struct _RsvgNodeLine { RsvgNode super; RsvgLength x1, x2, y1, y2; }; typedef struct _RsvgNodeLine RsvgNodeLine; static void _rsvg_node_line_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { const char *klazz = NULL, *id = NULL, *value; RsvgNodeLine *line = (RsvgNodeLine *) self; if (rsvg_property_bag_size (atts)) { if ((value = rsvg_property_bag_lookup (atts, "x1"))) line->x1 = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "y1"))) line->y1 = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "x2"))) line->x2 = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "y2"))) line->y2 = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, "line", klazz, id, atts); } } static void _rsvg_node_line_draw (RsvgNode * overself, RsvgDrawingCtx * ctx, int dominate) { cairo_path_t *path; RsvgPathBuilder builder; RsvgNodeLine *self = (RsvgNodeLine *) overself; double x1, y1, x2, y2; rsvg_path_builder_init (&builder, 4); x1 = _rsvg_css_normalize_length (&self->x1, ctx, 'h'); y1 = _rsvg_css_normalize_length (&self->y1, ctx, 'v'); x2 = _rsvg_css_normalize_length (&self->x2, ctx, 'h'); y2 = _rsvg_css_normalize_length (&self->y2, ctx, 'v'); rsvg_path_builder_move_to (&builder, x1, y1); rsvg_path_builder_line_to (&builder, x2, y2); path = rsvg_path_builder_finish (&builder); rsvg_state_reinherit_top (ctx, overself->state, dominate); rsvg_render_path (ctx, path); rsvg_cairo_path_destroy (path); } RsvgNode * rsvg_new_line (void) { RsvgNodeLine *line; line = g_new (RsvgNodeLine, 1); _rsvg_node_init (&line->super, RSVG_NODE_TYPE_LINE); line->super.draw = _rsvg_node_line_draw; line->super.set_atts = _rsvg_node_line_set_atts; line->x1 = line->x2 = line->y1 = line->y2 = _rsvg_css_parse_length ("0"); return &line->super; } struct _RsvgNodeRect { RsvgNode super; RsvgLength x, y, w, h, rx, ry; gboolean got_rx, got_ry; }; typedef struct _RsvgNodeRect RsvgNodeRect; static void _rsvg_node_rect_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { const char *klazz = NULL, *id = NULL, *value; RsvgNodeRect *rect = (RsvgNodeRect *) self; /* FIXME: negative w/h/rx/ry is an error, per http://www.w3.org/TR/SVG11/shapes.html#RectElement */ if (rsvg_property_bag_size (atts)) { if ((value = rsvg_property_bag_lookup (atts, "x"))) rect->x = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "y"))) rect->y = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "width"))) rect->w = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "height"))) rect->h = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "rx"))) { rect->rx = _rsvg_css_parse_length (value); rect->got_rx = TRUE; } if ((value = rsvg_property_bag_lookup (atts, "ry"))) { rect->ry = _rsvg_css_parse_length (value); rect->got_ry = TRUE; } if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, "rect", klazz, id, atts); } } static void _rsvg_node_rect_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { double x, y, w, h, rx, ry; double half_w, half_h; RsvgPathBuilder builder; cairo_path_t *path; RsvgNodeRect *rect = (RsvgNodeRect *) self; x = _rsvg_css_normalize_length (&rect->x, ctx, 'h'); y = _rsvg_css_normalize_length (&rect->y, ctx, 'v'); /* FIXME: negative w/h/rx/ry is an error, per http://www.w3.org/TR/SVG11/shapes.html#RectElement * For now we'll just take the absolute value. */ w = fabs (_rsvg_css_normalize_length (&rect->w, ctx, 'h')); h = fabs (_rsvg_css_normalize_length (&rect->h, ctx, 'v')); rx = fabs (_rsvg_css_normalize_length (&rect->rx, ctx, 'h')); ry = fabs (_rsvg_css_normalize_length (&rect->ry, ctx, 'v')); if (w == 0. || h == 0.) return; if (rect->got_rx) rx = rx; else rx = ry; if (rect->got_ry) ry = ry; else ry = rx; half_w = w / 2; half_h = h / 2; if (rx > half_w) rx = half_w; if (ry > half_h) ry = half_h; if (rx == 0) ry = 0; else if (ry == 0) rx = 0; if (rx == 0) { /* Easy case, no rounded corners */ rsvg_path_builder_init (&builder, 11); rsvg_path_builder_move_to (&builder, x, y); rsvg_path_builder_line_to (&builder, x + w, y); rsvg_path_builder_line_to (&builder, x + w, y + h); rsvg_path_builder_line_to (&builder, x, y + h); rsvg_path_builder_line_to (&builder, x, y); rsvg_path_builder_close_path (&builder); } else { double top_x1, top_x2, top_y; double bottom_x1, bottom_x2, bottom_y; double left_x, left_y1, left_y2; double right_x, right_y1, right_y2; /* Hard case, rounded corners * * (top_x1, top_y) (top_x2, top_y) * *--------------------------------* * / \ * * (left_x, left_y1) * (right_x, right_y1) * | | * | | * | | * | | * | | * | | * | | * | | * | | * * (left_x, left_y2) * (right_x, right_y2) * \ / * *--------------------------------* * (bottom_x1, bottom_y) (bottom_x2, bottom_y) */ top_x1 = x + rx; top_x2 = x + w - rx; top_y = y; bottom_x1 = top_x1; bottom_x2 = top_x2; bottom_y = y + h; left_x = x; left_y1 = y + ry; left_y2 = y + h - ry; right_x = x + w; right_y1 = left_y1; right_y2 = left_y2; rsvg_path_builder_init (&builder, 32); /* an estimate; the arc segments may grow the array anyway */ rsvg_path_builder_move_to (&builder, top_x1, top_y); rsvg_path_builder_line_to (&builder, top_x2, top_y); rsvg_path_builder_arc (&builder, top_x2, top_y, rx, ry, 0, FALSE, TRUE, right_x, right_y1); rsvg_path_builder_line_to (&builder, right_x, right_y2); rsvg_path_builder_arc (&builder, right_x, right_y2, rx, ry, 0, FALSE, TRUE, bottom_x2, bottom_y); rsvg_path_builder_line_to (&builder, bottom_x1, bottom_y); rsvg_path_builder_arc (&builder, bottom_x1, bottom_y, rx, ry, 0, FALSE, TRUE, left_x, left_y2); rsvg_path_builder_line_to (&builder, left_x, left_y1); rsvg_path_builder_arc (&builder, left_x, left_y1, rx, ry, 0, FALSE, TRUE, top_x1, top_y); rsvg_path_builder_close_path (&builder); } path = rsvg_path_builder_finish (&builder); rsvg_state_reinherit_top (ctx, self->state, dominate); rsvg_render_path (ctx, path); rsvg_cairo_path_destroy (path); } RsvgNode * rsvg_new_rect (void) { RsvgNodeRect *rect; rect = g_new (RsvgNodeRect, 1); _rsvg_node_init (&rect->super, RSVG_NODE_TYPE_RECT); rect->super.draw = _rsvg_node_rect_draw; rect->super.set_atts = _rsvg_node_rect_set_atts; rect->x = rect->y = rect->w = rect->h = rect->rx = rect->ry = _rsvg_css_parse_length ("0"); rect->got_rx = rect->got_ry = FALSE; return &rect->super; } struct _RsvgNodeCircle { RsvgNode super; RsvgLength cx, cy, r; }; typedef struct _RsvgNodeCircle RsvgNodeCircle; static void _rsvg_node_circle_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { const char *klazz = NULL, *id = NULL, *value; RsvgNodeCircle *circle = (RsvgNodeCircle *) self; if (rsvg_property_bag_size (atts)) { if ((value = rsvg_property_bag_lookup (atts, "cx"))) circle->cx = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "cy"))) circle->cy = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "r"))) circle->r = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, "circle", klazz, id, atts); } } static void _rsvg_node_circle_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { cairo_path_t *path; RsvgNodeCircle *circle = (RsvgNodeCircle *) self; double cx, cy, r; RsvgPathBuilder builder; cx = _rsvg_css_normalize_length (&circle->cx, ctx, 'h'); cy = _rsvg_css_normalize_length (&circle->cy, ctx, 'v'); r = _rsvg_css_normalize_length (&circle->r, ctx, 'o'); if (r <= 0) return; /* approximate a circle using 4 bezier curves */ rsvg_path_builder_init (&builder, 19); rsvg_path_builder_move_to (&builder, cx + r, cy); rsvg_path_builder_curve_to (&builder, cx + r, cy + r * RSVG_ARC_MAGIC, cx + r * RSVG_ARC_MAGIC, cy + r, cx, cy + r); rsvg_path_builder_curve_to (&builder, cx - r * RSVG_ARC_MAGIC, cy + r, cx - r, cy + r * RSVG_ARC_MAGIC, cx - r, cy); rsvg_path_builder_curve_to (&builder, cx - r, cy - r * RSVG_ARC_MAGIC, cx - r * RSVG_ARC_MAGIC, cy - r, cx, cy - r); rsvg_path_builder_curve_to (&builder, cx + r * RSVG_ARC_MAGIC, cy - r, cx + r, cy - r * RSVG_ARC_MAGIC, cx + r, cy); rsvg_path_builder_close_path (&builder); path = rsvg_path_builder_finish (&builder); rsvg_state_reinherit_top (ctx, self->state, dominate); rsvg_render_path (ctx, path); rsvg_cairo_path_destroy (path); } RsvgNode * rsvg_new_circle (void) { RsvgNodeCircle *circle; circle = g_new (RsvgNodeCircle, 1); _rsvg_node_init (&circle->super, RSVG_NODE_TYPE_CIRCLE); circle->super.draw = _rsvg_node_circle_draw; circle->super.set_atts = _rsvg_node_circle_set_atts; circle->cx = circle->cy = circle->r = _rsvg_css_parse_length ("0"); return &circle->super; } struct _RsvgNodeEllipse { RsvgNode super; RsvgLength cx, cy, rx, ry; }; typedef struct _RsvgNodeEllipse RsvgNodeEllipse; static void _rsvg_node_ellipse_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts) { const char *klazz = NULL, *id = NULL, *value; RsvgNodeEllipse *ellipse = (RsvgNodeEllipse *) self; if (rsvg_property_bag_size (atts)) { if ((value = rsvg_property_bag_lookup (atts, "cx"))) ellipse->cx = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "cy"))) ellipse->cy = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "rx"))) ellipse->rx = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "ry"))) ellipse->ry = _rsvg_css_parse_length (value); if ((value = rsvg_property_bag_lookup (atts, "class"))) klazz = value; if ((value = rsvg_property_bag_lookup (atts, "id"))) { id = value; rsvg_defs_register_name (ctx->priv->defs, value, self); } rsvg_parse_style_attrs (ctx, self->state, "ellipse", klazz, id, atts); } } static void _rsvg_node_ellipse_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate) { RsvgNodeEllipse *ellipse = (RsvgNodeEllipse *) self; cairo_path_t *path; double cx, cy, rx, ry; RsvgPathBuilder builder; cx = _rsvg_css_normalize_length (&ellipse->cx, ctx, 'h'); cy = _rsvg_css_normalize_length (&ellipse->cy, ctx, 'v'); rx = _rsvg_css_normalize_length (&ellipse->rx, ctx, 'h'); ry = _rsvg_css_normalize_length (&ellipse->ry, ctx, 'v'); if (rx <= 0 || ry <= 0) return; /* approximate an ellipse using 4 bezier curves */ rsvg_path_builder_init (&builder, 19); rsvg_path_builder_move_to (&builder, cx + rx, cy); rsvg_path_builder_curve_to (&builder, cx + rx, cy - RSVG_ARC_MAGIC * ry, cx + RSVG_ARC_MAGIC * rx, cy - ry, cx, cy - ry); rsvg_path_builder_curve_to (&builder, cx - RSVG_ARC_MAGIC * rx, cy - ry, cx - rx, cy - RSVG_ARC_MAGIC * ry, cx - rx, cy); rsvg_path_builder_curve_to (&builder, cx - rx, cy + RSVG_ARC_MAGIC * ry, cx - RSVG_ARC_MAGIC * rx, cy + ry, cx, cy + ry); rsvg_path_builder_curve_to (&builder, cx + RSVG_ARC_MAGIC * rx, cy + ry, cx + rx, cy + RSVG_ARC_MAGIC * ry, cx + rx, cy); rsvg_path_builder_close_path (&builder); path = rsvg_path_builder_finish (&builder); rsvg_state_reinherit_top (ctx, self->state, dominate); rsvg_render_path (ctx, path); rsvg_cairo_path_destroy (path); } RsvgNode * rsvg_new_ellipse (void) { RsvgNodeEllipse *ellipse; ellipse = g_new (RsvgNodeEllipse, 1); _rsvg_node_init (&ellipse->super, RSVG_NODE_TYPE_ELLIPSE); ellipse->super.draw = _rsvg_node_ellipse_draw; ellipse->super.set_atts = _rsvg_node_ellipse_set_atts; ellipse->cx = ellipse->cy = ellipse->rx = ellipse->ry = _rsvg_css_parse_length ("0"); return &ellipse->super; }