Commit ebc963ad authored by Caleb Michael Moore's avatar Caleb Michael Moore

clipping paths

parent 0a48a8b6
......@@ -43,7 +43,8 @@ typedef enum {
RSVG_DEF_PATH,
RSVG_DEF_FILTER,
RSVG_DEF_MASK,
RSVG_DEF_MARKER
RSVG_DEF_MARKER,
RSVG_DEF_CLIP_PATH
} RsvgDefType;
struct _RsvgDefVal {
......
......@@ -28,6 +28,7 @@
#include "rsvg-shapes.h"
#include "rsvg-css.h"
#include <libart_lgpl/art_rgba.h>
#include <libart_lgpl/art_svp_ops.h>
#include <string.h>
static void
......@@ -299,3 +300,156 @@ rsvg_mask_parse (const RsvgDefs * defs, const char *str)
}
return NULL;
}
static void
rsvg_clip_path_free (RsvgDefVal * self)
{
RsvgClipPath *z = (RsvgClipPath *)self;
g_ptr_array_free(z->super.children, FALSE);
g_free (z);
}
ArtSVP *
rsvg_clip_path_render (RsvgClipPath * self, RsvgHandle *ctx)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawableGroup *group = (RsvgDefsDrawableGroup*)self;
guint i;
ArtSVP *svp, *svpx;
svpx = NULL;
/* combine state definitions */
if (ctx->n_state > 1)
rsvg_state_dominate(state, &ctx->state[ctx->n_state - 2]);
if (self->units == objectBoundingBox)
{
state->affine[0] = ctx->bbox.x1 - ctx->bbox.x0;
state->affine[1] = 0;
state->affine[2] = 0;
state->affine[3] = ctx->bbox.y1 - ctx->bbox.y0;
state->affine[4] = ctx->bbox.x0;
state->affine[5] = ctx->bbox.y0;
}
for (i = 0; i < group->children->len; i++)
{
/* push the state stack */
if (ctx->n_state == ctx->n_state_max)
ctx->state = g_renew (RsvgState, ctx->state,
ctx->n_state_max <<= 1);
if (ctx->n_state)
rsvg_state_inherit (&ctx->state[ctx->n_state],
&ctx->state[ctx->n_state - 1]);
else
rsvg_state_init (ctx->state);
ctx->n_state++;
svp = rsvg_defs_drawable_draw_as_svp (g_ptr_array_index(group->children, i),
ctx, 0);
if (svp != NULL)
{
if (svpx != NULL)
{
ArtSVP * svpn;
svpn = art_svp_union(svpx, svp);
art_free(svpx);
art_free(svp);
svpx = svpn;
}
else
svpx = svp;
}
/* pop the state stack */
ctx->n_state--;
rsvg_state_finalize (&ctx->state[ctx->n_state]);
}
return svpx;
}
static RsvgClipPath *
rsvg_new_clip_path (void)
{
RsvgClipPath *clip_path;
clip_path = g_new (RsvgClipPath, 1);
clip_path->super.children = g_ptr_array_new ();
clip_path->units = userSpaceOnUse;
return clip_path;
}
void
rsvg_start_clip_path (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
const char *id = NULL, *value = NULL;
RsvgClipPath *clip_path;
double font_size;
font_size = rsvg_state_current_font_size (ctx);
clip_path = rsvg_new_clip_path ();
if (rsvg_property_bag_size (atts))
{
if ((value = rsvg_property_bag_lookup (atts, "clipPathUnits")))
{
if (!strcmp (value, "objectBoundingBox"))
clip_path->units = objectBoundingBox;
else
clip_path->units = userSpaceOnUse;
}
if ((value = rsvg_property_bag_lookup (atts, "id")))
id = value;
}
ctx->current_defs_group = &clip_path->super;
/* set up the defval stuff */
clip_path->super.super.super.type = RSVG_DEF_CLIP_PATH;
clip_path->super.super.super.free = &rsvg_clip_path_free;
rsvg_defs_set (ctx->defs, id, &clip_path->super.super.super);
}
void
rsvg_end_clip_path (RsvgHandle *ctx)
{
ctx->current_defs_group = NULL;
}
RsvgDefsDrawable *
rsvg_clip_path_parse (const RsvgDefs * defs, const char *str)
{
if (!strncmp (str, "url(", 4))
{
const char *p = str + 4;
int ix;
char *name;
RsvgDefVal *val;
while (g_ascii_isspace (*p))
p++;
if (*p == '#')
{
p++;
for (ix = 0; p[ix]; ix++)
if (p[ix] == ')')
break;
if (p[ix] == ')')
{
name = g_strndup (p, ix);
val = rsvg_defs_lookup (defs, name);
g_free (name);
if (val && val->type == RSVG_DEF_CLIP_PATH)
return (RsvgDefsDrawable *) val;
}
}
}
return NULL;
}
......@@ -30,6 +30,7 @@
#include "rsvg-styles.h"
#include "rsvg-shapes.h"
#include <libxml/SAX.h>
#include <libart_lgpl/art_svp.h>
G_BEGIN_DECLS
......@@ -56,6 +57,25 @@ rsvg_end_mask (RsvgHandle *ctx);
RsvgDefsDrawable *
rsvg_mask_parse (const RsvgDefs * defs, const char *str);
typedef struct _RsvgClipPath RsvgClipPath;
struct _RsvgClipPath {
RsvgDefsDrawableGroup super;
RsvgCoordUnits units;
};
ArtSVP *
rsvg_clip_path_render (RsvgClipPath *s, RsvgHandle *ctx);
void
rsvg_start_clip_path (RsvgHandle *ctx, RsvgPropertyBag *atts);
void
rsvg_end_clip_path (RsvgHandle *ctx);
RsvgDefsDrawable *
rsvg_clip_path_parse (const RsvgDefs * defs, const char *str);
G_END_DECLS
#endif
......@@ -41,9 +41,11 @@
#include <libart_lgpl/art_render_svp.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_svp_intersect.h>
#include <libart_lgpl/art_svp_ops.h>
#include <libart_lgpl/art_svp_vpath.h>
#include <libart_lgpl/art_rgb_affine.h>
#include <libart_lgpl/art_rgb_rgba_affine.h>
#include <libart_lgpl/art_rgb_svp.h>
/* 4/3 * (1-cos 45ƒ)/sin 45ƒ = 4/3 * sqrt(2) - 1 */
#define RSVG_ARC_MAGIC ((double) 0.5522847498)
......@@ -157,7 +159,7 @@ rsvg_calculate_svp_bounds (const ArtSVP *svp)
* Renders the SVP over the pixbuf in @ctx.
**/
static void
rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
rsvg_render_svp (RsvgHandle *ctx, ArtSVP *svp,
RsvgPaintServer *ps, int opacity)
{
GdkPixbuf *pixbuf;
......@@ -175,6 +177,8 @@ rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
return;
}
state = rsvg_state_current(ctx);
has_alpha = gdk_pixbuf_get_has_alpha (pixbuf);
render = art_render_new (0, 0,
......@@ -188,10 +192,19 @@ rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
has_alpha ? ART_ALPHA_SEPARATE : ART_ALPHA_NONE,
NULL);
temprect = rsvg_calculate_svp_bounds(svp);
if (state->clippath != NULL)
{
ArtSVP * svpx;
svpx = art_svp_intersect(svp, state->clippath);
svp = svpx;
}
art_render_svp (render, svp);
art_render_mask_solid (render, (opacity << 8) + opacity + (opacity >> 7));
temprect = rsvg_calculate_svp_bounds(svp);
art_irect_union(&ctx->bbox, &ctx->bbox, &temprect);
......@@ -201,13 +214,65 @@ rsvg_render_svp (RsvgHandle *ctx, const ArtSVP *svp,
gradctx.y1 = temprect.y1;
gradctx.ctx = ctx;
state = rsvg_state_current(ctx);
for (i = 0; i < 6; i++)
gradctx.affine[i] = state->affine[i];
gradctx.color = state->current_color;
rsvg_render_paint_server (render, ps, &gradctx);
art_render_invoke (render);
if (state->clippath != NULL) /*we don't need svpx any more*/
art_free(svp);
}
static ArtSVP *
rsvg_render_filling (RsvgState *state, const ArtVpath *vpath)
{
ArtVpath *closed_vpath;
ArtSVP *svp2, *svp;
ArtSvpWriter *swr;
closed_vpath = rsvg_close_vpath (vpath);
svp = art_svp_from_vpath (closed_vpath);
g_free (closed_vpath);
if (state->fill_rule == FILL_RULE_EVENODD)
swr = art_svp_writer_rewind_new (ART_WIND_RULE_ODDEVEN);
else /* state->fill_rule == FILL_RULE_NONZERO */
swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
art_svp_intersector (svp, swr);
svp2 = art_svp_writer_rewind_reap (swr);
art_svp_free (svp);
return svp2;
}
static ArtSVP *
rsvg_render_outline (RsvgState *state, ArtVpath *vpath)
{
ArtSVP * output;
/* todo: libart doesn't yet implement anamorphic scaling of strokes */
double stroke_width = state->stroke_width *
art_affine_expansion (state->affine);
if (stroke_width < 0.25)
stroke_width = 0.25;
/* if the path is dashed, stroke it */
if (state->dash.n_dash > 0)
{
ArtVpath * dashed_vpath = art_vpath_dash (vpath, &state->dash);
vpath = dashed_vpath;
}
output = art_svp_vpath_stroke (vpath, state->join, state->cap,
stroke_width, state->miter_limit, 0.25);
if (state->dash.n_dash > 0)
art_free (vpath);
return output;
}
static void
......@@ -251,59 +316,27 @@ rsvg_render_bpath (RsvgHandle *ctx, const ArtBpath *bpath)
if (state->fill != NULL)
{
ArtVpath *closed_vpath;
ArtSVP *svp2;
ArtSvpWriter *swr;
closed_vpath = rsvg_close_vpath (vpath);
svp = art_svp_from_vpath (closed_vpath);
g_free (closed_vpath);
if (state->fill_rule == FILL_RULE_EVENODD)
swr = art_svp_writer_rewind_new (ART_WIND_RULE_ODDEVEN);
else /* state->fill_rule == FILL_RULE_NONZERO */
swr = art_svp_writer_rewind_new (ART_WIND_RULE_NONZERO);
art_svp_intersector (svp, swr);
svp2 = art_svp_writer_rewind_reap (swr);
art_svp_free (svp);
opacity = state->fill_opacity;
if (!need_tmpbuf && state->opacity != 0xff)
{
tmp = opacity * state->opacity + 0x80;
opacity = (tmp + (tmp >> 8)) >> 8;
}
rsvg_render_svp (ctx, svp2, state->fill, opacity);
art_svp_free (svp2);
svp = rsvg_render_filling(state, vpath);
rsvg_render_svp (ctx, svp, state->fill, opacity);
art_svp_free (svp);
}
if (state->stroke != NULL)
{
/* todo: libart doesn't yet implement anamorphic scaling of strokes */
double stroke_width = state->stroke_width *
art_affine_expansion (state->affine);
if (stroke_width < 0.25)
stroke_width = 0.25;
/* if the path is dashed, stroke it */
if (state->dash.n_dash > 0)
{
ArtVpath * dashed_vpath = art_vpath_dash (vpath, &state->dash);
art_free (vpath);
vpath = dashed_vpath;
}
svp = art_svp_vpath_stroke (vpath, state->join, state->cap,
stroke_width, state->miter_limit, 0.25);
opacity = state->stroke_opacity;
if (!need_tmpbuf && state->opacity != 0xff)
{
tmp = opacity * state->opacity + 0x80;
opacity = (tmp + (tmp >> 8)) >> 8;
}
svp = rsvg_render_outline(state, vpath);
rsvg_render_svp (ctx, svp, state->stroke, opacity);
art_svp_free (svp);
}
......@@ -314,6 +347,51 @@ rsvg_render_bpath (RsvgHandle *ctx, const ArtBpath *bpath)
art_free (vpath);
}
static ArtSVP *
rsvg_render_bpath_into_svp (RsvgHandle *ctx, const ArtBpath *bpath)
{
RsvgState *state;
ArtBpath *affine_bpath;
ArtVpath *vpath;
ArtSVP *svpi;
ArtSVP *svpo;
ArtSVP *svpx;
state = rsvg_state_current (ctx);
affine_bpath = art_bpath_affine_transform (bpath,
state->affine);
svpi = svpo = svpx = NULL;
vpath = art_bez_path_to_vec (affine_bpath, 0.25);
art_free (affine_bpath);
state->fill_rule = state->clip_rule;
if (state->fill != NULL)
{
svpi = rsvg_render_filling(state, vpath);
svpx = svpi;
}
if (state->stroke != NULL)
{
svpo = rsvg_render_outline(state, vpath);
if (svpx != NULL)
{
svpx = art_svp_union(svpi, svpo);
art_free(svpi);
art_free(svpo);
}
else
{
svpx = svpo;
}
}
art_free (vpath);
return svpx;
}
static void
rsvg_render_markers(RsvgBpathDef * bpath_def, RsvgHandle *ctx)
{
......@@ -411,6 +489,21 @@ rsvg_render_path(RsvgHandle *ctx, const char *d)
rsvg_bpath_def_free (bpath_def);
}
static ArtSVP *
rsvg_render_path_as_svp(RsvgHandle *ctx, const char *d)
{
RsvgBpathDef *bpath_def;
ArtSVP * output;
bpath_def = rsvg_parse_path (d);
rsvg_bpath_def_art_finish (bpath_def);
output = rsvg_render_bpath_into_svp (ctx, bpath_def->bpath);
rsvg_bpath_def_free (bpath_def);
return output;
}
void
rsvg_defs_drawable_draw (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate)
......@@ -418,6 +511,13 @@ rsvg_defs_drawable_draw (RsvgDefsDrawable * self, RsvgHandle *ctx,
self->draw(self, ctx, dominate);
}
ArtSVP *
rsvg_defs_drawable_draw_as_svp (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate)
{
return self->draw_as_svp(self, ctx, dominate);
}
static void
rsvg_defs_drawable_path_free (RsvgDefVal *self)
{
......@@ -449,6 +549,29 @@ rsvg_defs_drawable_path_draw (RsvgDefsDrawable * self, RsvgHandle *ctx,
}
static ArtSVP *
rsvg_defs_drawable_path_draw_as_svp (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawablePath *path = (RsvgDefsDrawablePath*)self;
/* combine state definitions */
rsvg_state_clone (state, &self->state);
if (ctx->n_state > 1)
{
if (dominate)
rsvg_state_dominate(state, &ctx->state[ctx->n_state - 2]);
else
rsvg_state_reinherit(state, &ctx->state[ctx->n_state - 2]);
}
/* always want to render inside of a <use/> */
return rsvg_render_path_as_svp (ctx, path->d);
}
static void
rsvg_defs_drawable_group_free (RsvgDefVal *self)
{
......@@ -517,6 +640,70 @@ rsvg_defs_drawable_group_draw (RsvgDefsDrawable * self, RsvgHandle *ctx,
rsvg_pop_discrete_layer (ctx);
}
static ArtSVP *
rsvg_defs_drawable_group_draw_as_svp (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawableGroup *group = (RsvgDefsDrawableGroup*)self;
guint i;
double tempaffine[6];
ArtSVP *svp1, *svp2, *svp3;
svp1 = NULL;
for (i = 0; i < 6; i++)
{
tempaffine[i] = ctx->state[ctx->n_state - 1].affine[i];
}
/* combine state definitions */
rsvg_state_clone (state, &self->state);
if (ctx->n_state > 1)
{
/*This is a special domination mode for patterns, the style
is simply reinherited, wheras the transform is totally overridden*/
if (dominate == 2)
{
for (i = 0; i < 6; i++)
{
state->affine[i] = tempaffine[i];
}
}
else if (dominate)
rsvg_state_dominate(state, &ctx->state[ctx->n_state - 2]);
else
rsvg_state_reinherit(state, &ctx->state[ctx->n_state - 2]);
}
for (i = 0; i < group->children->len; i++)
{
/* push the state stack */
if (ctx->n_state == ctx->n_state_max)
ctx->state = g_renew (RsvgState, ctx->state,
ctx->n_state_max <<= 1);
if (ctx->n_state)
rsvg_state_inherit (&ctx->state[ctx->n_state],
&ctx->state[ctx->n_state - 1]);
else
rsvg_state_init (ctx->state);
ctx->n_state++;
svp2 = rsvg_defs_drawable_draw_as_svp (g_ptr_array_index(group->children, i),
ctx, 0);
if (svp1 != NULL)
{
svp3 = art_svp_union(svp2, svp1);
art_free(svp1);
svp1 = svp3;
}
/* pop the state stack */
ctx->n_state--;
rsvg_state_finalize (&ctx->state[ctx->n_state]);
}
return svp1;
}
static void
rsvg_defs_drawable_use_free (RsvgDefVal *self)
{
......@@ -590,6 +777,7 @@ rsvg_push_def_group (RsvgHandle *ctx, const char * id)
group->super.super.type = RSVG_DEF_PATH;
group->super.super.free = rsvg_defs_drawable_group_free;
group->super.draw = rsvg_defs_drawable_group_draw;
group->super.draw_as_svp = rsvg_defs_drawable_group_draw_as_svp;
rsvg_defs_set (ctx->defs, id, &group->super.super);
......@@ -628,6 +816,7 @@ rsvg_handle_path (RsvgHandle *ctx, const char * d, const char * id)
path->super.super.type = RSVG_DEF_PATH;
path->super.super.free = rsvg_defs_drawable_path_free;
path->super.draw = rsvg_defs_drawable_path_draw;
path->super.draw_as_svp = rsvg_defs_drawable_path_draw_as_svp;
rsvg_defs_set (ctx->defs, id, &path->super.super);
path->super.parent = (RsvgDefsDrawable *)ctx->current_defs_group;
......@@ -1648,6 +1837,38 @@ rsvg_affine_image(GdkPixbuf *img, GdkPixbuf *intermediate,
}
}
static void
rsvg_clip_image(GdkPixbuf *intermediate, ArtSVP *path)
{
gint intstride;
gint basestride;
guchar * intpix;
guchar * basepix;
gint i, j;
gint width, height;
GdkPixbuf * base;
width = gdk_pixbuf_get_width (intermediate);
height = gdk_pixbuf_get_height (intermediate);
intstride = gdk_pixbuf_get_rowstride (intermediate);
intpix = gdk_pixbuf_get_pixels (intermediate);
base = gdk_pixbuf_new (GDK_COLORSPACE_RGB, 0, 8,
width, height);
basestride = gdk_pixbuf_get_rowstride (base);
basepix = gdk_pixbuf_get_pixels (base);
art_rgb_svp_aa(path, 0, 0, width, height, 0xFFFFFF, 0x000000, basepix, basestride, NULL);
for (i = 0; i < width; i++)
for (j = 0; j < height; j++)
{
intpix[i * 4 + j * intstride + 3] = intpix[i * 4 + j * intstride + 3] *
basepix[i * 3 + j * basestride] / 255;
}
}
void
rsvg_start_image (RsvgHandle *ctx, RsvgPropertyBag *atts)
{
......@@ -1751,13 +1972,17 @@ rsvg_start_image (RsvgHandle *ctx, RsvgPropertyBag *atts)
return;
}
rsvg_affine_image(img, intermediate, tmp_affine, w, h);
g_object_unref (G_OBJECT (img));
rsvg_push_discrete_layer(ctx);
if (state->clippath)
{
rsvg_clip_image(intermediate, state->clippath);
}
/*slap it down*/
rsvg_alpha_blt (intermediate, 0, 0,
gdk_pixbuf_get_width (intermediate),
......
......@@ -57,6 +57,7 @@ struct _RsvgDefsDrawable {
RsvgState state;
RsvgDefsDrawable * parent;
void (*draw) (RsvgDefsDrawable * self, RsvgHandle *ctx, int dominate);
ArtSVP * (*draw_as_svp) (RsvgDefsDrawable * self, RsvgHandle *ctx, int dominate);
};
struct _RsvgDefsDrawablePath {
......@@ -102,6 +103,8 @@ rsvg_pixbuf_new_from_href (const char *href,
void rsvg_defs_drawable_draw (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate);
ArtSVP * rsvg_defs_drawable_draw_as_svp (RsvgDefsDrawable * self, RsvgHandle *ctx,
int dominate);
void
rsvg_affine_image(GdkPixbuf *img, GdkPixbuf *intermediate,
......
......@@ -83,6 +83,7 @@ rsvg_state_init (RsvgState *state)
state->join = ART_PATH_STROKE_JOIN_MITER;
state->stop_opacity = 0xff;
state->fill_rule = FILL_RULE_NONZERO;
state->clip_rule = FILL_RULE_NONZERO;
state->backgroundnew = FALSE;
state->save_pixbuf = NULL;
......@@ -96,16 +97,18 @@ rsvg_state_init (RsvgState *state)
state->unicode_bidi = UNICODE_BIDI_NORMAL;
state->text_anchor = TEXT_ANCHOR_START;
state->visible = TRUE;
state->cond_true = TRUE;
state->cond_true = TRUE;
state->filter = NULL;
state->startMarker = NULL;
state->clip_path_ref= NULL;
state->startMarker = NULL;
state->middleMarker = NULL;
state->endMarker = NULL;
state->endMarker = NULL;
state->has_current_color = FALSE;
state->has_fill_server = FALSE;
state->has_fill_opacity = FALSE;
state->has_fill_rule = FALSE;
state->has_clip_rule = FALSE;
state->has_stroke_server = FALSE;
state->has_stroke_opacity = FALSE;
state->has_stroke_width = FALSE;
......@@ -131,6 +134,8 @@ rsvg_state_init (RsvgState *state)
state->has_startMarker = FALSE;
state->has_middleMarker = FALSE;
state->has_endMarker = FALSE;
state->clippath = NULL;
}
void
......@@ -170,6 +175,8 @@ rsvg_state_reinherit (RsvgState *dst, const RsvgState *src)
dst->fill_opacity = src->fill_opacity;
if (!dst->has_fill_rule)
dst->fill_rule = src->fill_rule;
if (!dst->has_clip_rule)
dst->clip_rule = src->clip_rule;
if (!dst->has_stroke_server)
{
rsvg_paint_server_ref (src->stroke);
......@@ -235,6 +242,26 @@ rsvg_state_reinherit (RsvgState *dst, const RsvgState *src)
dst->dash.dash[i] = src->dash.dash[i];
}
art_affine_multiply (dst->affine, dst->personal_affine, src->affine);
/*
if (src->clippath == NULL)
{
if (dst->clip_path_ref)
dst->clippath = rsvg_clip_path_render (dst->clip_path_ref, ctx);
else
dst->clippath = NULL;
}
else
{
if (dst->clip_path_ref)
{
ArtSVP *svp;
svp = rsvg_clip_path_render (dst->clip_path_ref, ctx);
dst->clippath = art_svp_intersection(src->clippath, svp);
}
else
*/
dst->clippath = src->clippath; /*this is bad!!! we should copy it somehow*/
/*}*/
}
void
......@@ -254,6 +281,8 @@ rsvg_state_dominate (RsvgState *dst, const RsvgState *src)
dst->fill_opacity = src->fill_opacity;
if (!dst->has_fill_rule || src->has_fill_rule)
dst->fill_rule = src->fill_rule;
if (!dst->has_clip_rule || src->has_clip_rule)
dst->clip_rule = src->clip_rule;
if (!dst->has_stroke_server || src->has_stroke_server)
{
rsvg_paint_server_ref (src->stroke);
......@@ -322,6 +351,25 @@ rsvg_state_dominate (RsvgState *dst, const RsvgState *src)
dst->dash.dash[i] = src->dash.dash[i];
}
art_affine_multiply (dst->affine, dst->personal_affine, src->affine);
/*
if (src->clippath == NULL)
{
if (dst->clip_path_ref)
dst->clippath = rsvg_clip_path_render (dst->clip_path_ref, ctx);