Commit 901e96e1 authored by Caleb Michael Moore's avatar Caleb Michael Moore

seperation and fixes

parent cf83fe99
2005-02-23 Caleb Moore <c.moore@student.unsw.edu.au>
* rsvg-art-mask.c: new file with art specific masking stuff
* rsvg-art-mask.h: see above
* rsvg-paint-server.c: bug 155752
2005-02-18 Dom Lachowicz <cinamod@hotmail.com>
* rsvg-structure.c: bug 167813
......
......@@ -66,6 +66,8 @@ librsvg_2_la_SOURCES = \
rsvg-art-composite.h \
rsvg-art-render.c \
rsvg-art-render.h \
rsvg-art-mask.c \
rsvg-art-mask.h \
librsvg-enum-types.c
librsvg_2_la_LDFLAGS = -version-info @VERSION_INFO@ -no-undefined -export-dynamic
......
......@@ -34,7 +34,7 @@
#include "rsvg-styles.h"
#include "rsvg-structure.h"
#include "rsvg-filter.h"
#include "rsvg-mask.h"
#include "rsvg-art-mask.h"
#include <libart_lgpl/art_rgba.h>
#include <libart_lgpl/art_affine.h>
......@@ -115,10 +115,10 @@ rsvg_art_push_discrete_layer (RsvgDrawingCtx *ctx)
ArtSVP * tmppath;
rsvg_state_push(ctx);
tmppath = rsvg_clip_path_render (state->clip_path_ref, ctx);
tmppath = rsvg_art_clip_path_render (state->clip_path_ref, ctx);
rsvg_state_pop(ctx);
render->clippath = rsvg_clip_path_merge(render->clippath, tmppath, 'i');
render->clippath = rsvg_art_clip_path_merge(render->clippath, tmppath, 'i');
layer->clippath_loaded = TRUE;
layer->clippath_save = render->clippath;
}
......@@ -314,7 +314,7 @@ rsvg_composite_layer(RsvgDrawingCtx *ctx, RsvgState *state, GdkPixbuf *tos, GdkP
if (mask != NULL)
{
out = get_next_out(&operationsleft, in, tos, nos, intermediate);
rsvg_mask_render ((RsvgMask *)mask, in, out, ctx);
rsvg_art_mask_render ((RsvgMask *)mask, in, out, ctx);
in = out;
}
if (adobe_blend)
......@@ -621,8 +621,8 @@ rsvg_art_add_clipping_rect(RsvgDrawingCtx *ctx, double x, double y, double w, do
ArtSVP * temppath;
RsvgArtRender * render = (RsvgArtRender *)ctx->render;
RsvgArtDiscreteLayer * data = g_slist_nth(render->layers, 0)->data;
temppath = rsvg_rect_clip_path(x, y, w, h, ctx);
temppath = rsvg_art_rect_clip_path(x, y, w, h, ctx);
data->clippath_loaded = TRUE;
render->clippath = rsvg_clip_path_merge(render->clippath, temppath, 'i');
render->clippath = rsvg_art_clip_path_merge(render->clippath, temppath, 'i');
}
/* vim: set sw=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
rsvg-filter.c: Provides filters
Copyright (C) 2004 Caleb Moore
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.
Author: Caleb Moore <calebmm@tpg.com.au>
*/
#include "rsvg-private.h"
#include "rsvg-art-mask.h"
#include "rsvg-styles.h"
#include "rsvg-art-draw.h"
#include "rsvg-art-composite.h"
#include "rsvg-art-render.h"
#include "rsvg-css.h"
#include <libart_lgpl/art_rgba.h>
#include <libart_lgpl/art_svp_ops.h>
#include <string.h>
ArtSVP *
rsvg_art_rect_clip_path(double x, double y, double w, double h, RsvgDrawingCtx * ctx)
{
RsvgArtSVPRender * asvpr;
RsvgRender * save;
GString * d = NULL;
ArtSVP * output = NULL;
char buf [G_ASCII_DTOSTR_BUF_SIZE];
/* emulate a rect using a path */
d = g_string_new ("M ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x));
g_string_append_c (d, ' ');
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y));
g_string_append (d, " H ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x + w));
g_string_append (d, " V ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y+h));
g_string_append (d, " H ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x));
g_string_append (d, " V ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y));
g_string_append (d, " Z");
asvpr = rsvg_art_svp_render_new();
save = ctx->render;
ctx->render = (RsvgRender *)asvpr;
asvpr->super.render_path(ctx, d->str);
ctx->render = save;
output = asvpr->outline;
/* todo, free the render */
g_string_free (d, TRUE);
return output;
}
ArtSVP *
rsvg_art_clip_path_merge(ArtSVP * first, ArtSVP * second, char operation)
{
ArtSVP * tmppath;
if (first != NULL && second != NULL)
{
if (operation == 'i')
tmppath = art_svp_intersect(first, second);
else
tmppath = art_svp_union(first, second);
art_free(second);
return tmppath;
}
else if (first != NULL)
return first;
else
return second;
}
ArtSVP *
rsvg_art_clip_path_render (RsvgClipPath * self, RsvgDrawingCtx *ctx)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawableGroup *group = (RsvgDefsDrawableGroup*)self;
guint i;
ArtSVP *svp, *svpx;
svpx = NULL;
rsvg_state_reinherit_top(ctx, &self->super.super.state, 0);
if (self->units == objectBoundingBox)
{
state->affine[0] = ((RsvgArtRender *)ctx->render)->bbox.x1
- ((RsvgArtRender *)ctx->render)->bbox.x0;
state->affine[1] = 0;
state->affine[2] = 0;
state->affine[3] = ((RsvgArtRender *)ctx->render)->bbox.y1 -
((RsvgArtRender *)ctx->render)->bbox.y0;
state->affine[4] = ((RsvgArtRender *)ctx->render)->bbox.x0;
state->affine[5] = ((RsvgArtRender *)ctx->render)->bbox.y0;
}
for (i = 0; i < group->children->len; i++)
{
RsvgArtSVPRender * asvpr;
RsvgRender * save;
rsvg_state_push(ctx);
asvpr = rsvg_art_svp_render_new();
save = ctx->render;
ctx->render = (RsvgRender *)asvpr;
rsvg_defs_drawable_draw (g_ptr_array_index(group->children, i),
ctx, 0);
svp = asvpr->outline;
/*todo, free the render*/
ctx->render = save;
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;
}
rsvg_state_pop(ctx);
}
return svpx;
}
void
rsvg_art_mask_render (RsvgMask *self, GdkPixbuf *tos, GdkPixbuf *nos, RsvgDrawingCtx *ctx)
{
art_u8 *tos_pixels, *nos_pixels, *mask_pixels;
int width;
int height;
int rowstride;
int x, y;
GdkPixbuf *save, *mask;
RsvgDefsDrawable *drawable;
drawable = (RsvgDefsDrawable*)self;
mask = _rsvg_pixbuf_new_cleared(GDK_COLORSPACE_RGB, 1, 8,
gdk_pixbuf_get_width(tos),
gdk_pixbuf_get_height(tos));
save = ((RsvgArtRender *)ctx->render)->pixbuf;
((RsvgArtRender *)ctx->render)->pixbuf = mask;
rsvg_state_push(ctx);
rsvg_defs_drawable_draw (drawable, ctx, 0);
rsvg_state_pop(ctx);
((RsvgArtRender *)ctx->render)->pixbuf = save;
if (tos == NULL || nos == NULL)
{
/* FIXME: What warning/GError here? */
return;
}
if (!gdk_pixbuf_get_has_alpha (nos))
{
g_warning (_("push/pop transparency group on non-alpha buffer nyi"));
return;
}
width = gdk_pixbuf_get_width (tos);
height = gdk_pixbuf_get_height (tos);
rowstride = gdk_pixbuf_get_rowstride (tos);
tos_pixels = gdk_pixbuf_get_pixels (tos);
nos_pixels = gdk_pixbuf_get_pixels (nos);
mask_pixels = gdk_pixbuf_get_pixels (mask);
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
guchar r, g, b, rm, gm, bm, am;
guint a;
guint luminance;
a = tos_pixels[4 * x + 3];
if (a)
{
r = tos_pixels[4 * x];
g = tos_pixels[4 * x + 1];
b = tos_pixels[4 * x + 2];
rm = mask_pixels[4 * x];
gm = mask_pixels[4 * x + 1];
bm = mask_pixels[4 * x + 2];
am = mask_pixels[4 * x + 3];
luminance = (rm * 2125 + gm * 7154 + bm * 721) / 10000;
a = a * luminance / 255 * am / 255;
art_rgba_run_alpha (nos_pixels + 4 * x, r, g, b, a, 1);
}
}
tos_pixels += rowstride;
nos_pixels += rowstride;
mask_pixels += rowstride;
}
g_object_unref (G_OBJECT (mask));
}
/* vim: set sw=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
/*
rsvg-mask.h : Provides Masks
Copyright (C) 2004 Caleb Moore
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.
Author: Caleb Moore <calebmm@tpg.com.au>
*/
#ifndef RSVG_ART_MASK_H
#define RSVG_ART_MASK_H
#include "rsvg-mask.h"
#include <libart_lgpl/art_svp.h>
G_BEGIN_DECLS
void
rsvg_art_mask_render (RsvgMask *self, GdkPixbuf *source, GdkPixbuf *output, RsvgDrawingCtx *ctx);
ArtSVP *
rsvg_art_clip_path_render (RsvgClipPath *s, RsvgDrawingCtx *ctx);
ArtSVP *
rsvg_art_rect_clip_path(double x, double y, double w, double h, RsvgDrawingCtx * ctx);
ArtSVP *
rsvg_art_clip_path_merge(ArtSVP * first, ArtSVP * second, char operation);
G_END_DECLS
#endif
......@@ -25,12 +25,7 @@
#include "rsvg-private.h"
#include "rsvg-mask.h"
#include "rsvg-styles.h"
#include "rsvg-art-draw.h"
#include "rsvg-art-composite.h"
#include "rsvg-art-render.h"
#include "rsvg-css.h"
#include <libart_lgpl/art_rgba.h>
#include <libart_lgpl/art_svp_ops.h>
#include <string.h>
static void
......@@ -42,98 +37,16 @@ rsvg_mask_free (RsvgDefVal * self)
g_free (z);
}
void
rsvg_mask_render (RsvgMask *self, GdkPixbuf *tos, GdkPixbuf *nos, RsvgDrawingCtx *ctx)
{
art_u8 *tos_pixels, *nos_pixels, *mask_pixels;
int width;
int height;
int rowstride;
int x, y;
GdkPixbuf *save, *mask;
RsvgDefsDrawable *drawable;
drawable = (RsvgDefsDrawable*)self;
mask = _rsvg_pixbuf_new_cleared(GDK_COLORSPACE_RGB, 1, 8,
gdk_pixbuf_get_width(tos),
gdk_pixbuf_get_height(tos));
save = ((RsvgArtRender *)ctx->render)->pixbuf;
((RsvgArtRender *)ctx->render)->pixbuf = mask;
rsvg_state_push(ctx);
rsvg_defs_drawable_draw (drawable, ctx, 0);
rsvg_state_pop(ctx);
((RsvgArtRender *)ctx->render)->pixbuf = save;
if (tos == NULL || nos == NULL)
{
/* FIXME: What warning/GError here? */
return;
}
if (!gdk_pixbuf_get_has_alpha (nos))
{
g_warning (_("push/pop transparency group on non-alpha buffer nyi"));
return;
}
width = gdk_pixbuf_get_width (tos);
height = gdk_pixbuf_get_height (tos);
rowstride = gdk_pixbuf_get_rowstride (tos);
tos_pixels = gdk_pixbuf_get_pixels (tos);
nos_pixels = gdk_pixbuf_get_pixels (nos);
mask_pixels = gdk_pixbuf_get_pixels (mask);
for (y = 0; y < height; y++)
{
for (x = 0; x < width; x++)
{
guchar r, g, b, rm, gm, bm, am;
guint a;
guint luminance;
a = tos_pixels[4 * x + 3];
if (a)
{
r = tos_pixels[4 * x];
g = tos_pixels[4 * x + 1];
b = tos_pixels[4 * x + 2];
rm = mask_pixels[4 * x];
gm = mask_pixels[4 * x + 1];
bm = mask_pixels[4 * x + 2];
am = mask_pixels[4 * x + 3];
luminance = (rm * 2125 + gm * 7154 + bm * 0721) / 10000;
a = a * luminance / 255 * am / 255;
art_rgba_run_alpha (nos_pixels + 4 * x, r, g, b, a, 1);
}
}
tos_pixels += rowstride;
nos_pixels += rowstride;
mask_pixels += rowstride;
}
g_object_unref (G_OBJECT (mask));
}
static void
rsvg_defs_drawable_mask_draw (RsvgDefsDrawable * self, RsvgDrawingCtx *ctx,
int dominate)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawableGroup *group = (RsvgDefsDrawableGroup*)self;
guint i;
rsvg_state_reinherit_top(ctx, &self->state, 0);
if (state->opacity != 0xff || state->filter)
rsvg_push_discrete_layer (ctx);
rsvg_push_discrete_layer (ctx);
for (i = 0; i < group->children->len; i++)
{
......@@ -145,8 +58,7 @@ rsvg_defs_drawable_mask_draw (RsvgDefsDrawable * self, RsvgDrawingCtx *ctx,
rsvg_state_pop(ctx);
}
if (state->opacity != 0xff || state->filter)
rsvg_pop_discrete_layer (ctx);
rsvg_pop_discrete_layer (ctx);
}
......@@ -284,68 +196,6 @@ rsvg_clip_path_free (RsvgDefVal * self)
g_free (z);
}
ArtSVP *
rsvg_clip_path_render (RsvgClipPath * self, RsvgDrawingCtx *ctx)
{
RsvgState *state = rsvg_state_current (ctx);
RsvgDefsDrawableGroup *group = (RsvgDefsDrawableGroup*)self;
guint i;
ArtSVP *svp, *svpx;
svpx = NULL;
rsvg_state_reinherit_top(ctx, &self->super.super.state, 0);
if (self->units == objectBoundingBox)
{
state->affine[0] = ((RsvgArtRender *)ctx->render)->bbox.x1
- ((RsvgArtRender *)ctx->render)->bbox.x0;
state->affine[1] = 0;
state->affine[2] = 0;
state->affine[3] = ((RsvgArtRender *)ctx->render)->bbox.y1 -
((RsvgArtRender *)ctx->render)->bbox.y0;
state->affine[4] = ((RsvgArtRender *)ctx->render)->bbox.x0;
state->affine[5] = ((RsvgArtRender *)ctx->render)->bbox.y0;
}
for (i = 0; i < group->children->len; i++)
{
RsvgArtSVPRender * asvpr;
RsvgRender * save;
rsvg_state_push(ctx);
asvpr = rsvg_art_svp_render_new();
save = ctx->render;
ctx->render = (RsvgRender *)asvpr;
rsvg_defs_drawable_draw (g_ptr_array_index(group->children, i),
ctx, 0);
svp = asvpr->outline;
/*todo, free the render*/
ctx->render = save;
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;
}
rsvg_state_pop(ctx);
}
return svpx;
}
static RsvgClipPath *
rsvg_new_clip_path (void)
{
......@@ -431,67 +281,3 @@ rsvg_clip_path_parse (const RsvgDefs * defs, const char *str)
}
return NULL;
}
ArtSVP *
rsvg_rect_clip_path(double x, double y, double w, double h, RsvgDrawingCtx * ctx)
{
RsvgArtSVPRender * asvpr;
RsvgRender * save;
GString * d = NULL;
ArtSVP * output = NULL;
char buf [G_ASCII_DTOSTR_BUF_SIZE];
/* emulate a rect using a path */
d = g_string_new ("M ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x));
g_string_append_c (d, ' ');
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y));
g_string_append (d, " H ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x + w));
g_string_append (d, " V ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y+h));
g_string_append (d, " H ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), x));
g_string_append (d, " V ");
g_string_append (d, g_ascii_dtostr (buf, sizeof (buf), y));
g_string_append (d, " Z");
asvpr = rsvg_art_svp_render_new();
save = ctx->render;
ctx->render = (RsvgRender *)asvpr;
asvpr->super.render_path(ctx, d->str);
ctx->render = save;
output = asvpr->outline;
/* todo, free the render */
g_string_free (d, TRUE);
return output;
}
ArtSVP *
rsvg_clip_path_merge(ArtSVP * first, ArtSVP * second, char operation)
{
ArtSVP * tmppath;
if (first != NULL && second != NULL)
{
if (operation == 'i')
tmppath = art_svp_intersect(first, second);
else
tmppath = art_svp_union(first, second);
art_free(second);
return tmppath;
}
else if (first != NULL)
return first;
else
return second;
}
......@@ -30,7 +30,6 @@
#include "rsvg-styles.h"
#include "rsvg-shapes.h"
#include <libxml/SAX.h>
#include <libart_lgpl/art_svp.h>
G_BEGIN_DECLS
......@@ -45,9 +44,6 @@ struct _RsvgMask {
RsvgMaskUnits contentunits;
};
void
rsvg_mask_render (RsvgMask *self, GdkPixbuf *source, GdkPixbuf *output, RsvgDrawingCtx *ctx);
void
rsvg_start_mask (RsvgHandle *ctx, RsvgPropertyBag *atts);
......@@ -64,9 +60,6 @@ struct _RsvgClipPath {
RsvgCoordUnits units;
};
ArtSVP *
rsvg_clip_path_render (RsvgClipPath *s, RsvgDrawingCtx *ctx);
void
rsvg_start_clip_path (RsvgHandle *ctx, RsvgPropertyBag *atts);
......@@ -76,11 +69,6 @@ rsvg_end_clip_path (RsvgHandle *ctx);
RsvgDefsDrawable *
rsvg_clip_path_parse (const RsvgDefs * defs, const char *str);
ArtSVP *
rsvg_rect_clip_path(double x, double y, double w, double h, RsvgDrawingCtx * ctx);
ArtSVP *
rsvg_clip_path_merge(ArtSVP * first, ArtSVP * second, char operation);
G_END_DECLS
......
......@@ -185,8 +185,9 @@ rsvg_paint_server_lin_grad_render (RsvgPaintServer *self, ArtRender *ar,
guint32 current_color;
int i;
double xchange, ychange, pointlen,unitlen;
double nx2, ny2;
double x0, y0;
double cx, cy, cxt, cyt;
double px, py, pxt, pyt;
double x2t, y2t;
agl = z->agl;
if (agl == NULL)
......@@ -219,7 +220,7 @@ rsvg_paint_server_lin_grad_render (RsvgPaintServer *self, ArtRender *ar,
affine[i] = ctx->affine[i];
}
art_affine_multiply(affine, affine, rlg->affine);
art_affine_multiply(affine, rlg->affine, affine);
/*
in case I am hit by a bus, here is how the following code works:
......@@ -242,31 +243,36 @@ rsvg_paint_server_lin_grad_render (RsvgPaintServer *self, ArtRender *ar,
***Start explained section***/
/*calculate (nx2, ny2), the point perpendicular to the gradient*/
xchange = rlg->x2 - rlg->x1;
ychange = rlg->y2 - rlg->y1;
nx2 = rlg->x1 - ychange;
ny2 = rlg->y1 + xchange;
cx = (rlg->x2 + rlg->x1) / 2;
cy = (rlg->y2 + rlg->y1) / 2;
xchange = cx - rlg->x1;
ychange = cy - rlg->y1;
px = cx - ychange;
py = cy + xchange;
/* compute [xy][12] in pixel space */
x1 = rlg->x1 * affine[0] + rlg->y1 * affine[2] + affine[4];
y1 = rlg->x1 * affine[1] + rlg->y1 * affine[3] + affine[5];
x0 = rlg->x2 * affine[0] + rlg->y2 * affine[2] + affine[4];
y0 = rlg->x2 * affine[1] + rlg->y2 * affine[3] + affine[5];
x2 = nx2 * affine[0] + ny2 * affine[2] + affine[4];
y2 = nx2 * affine[1] + ny2 * affine[3] + affine[5];
pointlen = abs((x2 - x1)*(y1 - y0) - (x1 - x0)*(y2 - y1)) /
sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
xchange = x2 - x1;
ychange = y2 - y1;
cxt = cx * affine[0] + cy * affine[2] + affine[4];
cyt = cx * affine[1] + cy * affine[3] + affine[5];
x2t = rlg->x2 * affine[0] + rlg->y2 * affine[2] + affine[4];
y2t = rlg->x2 * affine[1] + rlg->y2 * affine[3] + affine[5];
pxt = px * affine[0] + py * affine[2] + affine[4];
pyt = px * affine[1] + py * affine[3] + affine[5];
pointlen = abs((pxt - cxt)*(cyt - y2t) - (cxt - x2t)*(pyt - cyt)) /
sqrt((pxt - cxt) * (pxt - cxt) + (pyt - cyt) * (pyt - cyt));
xchange = pxt - cxt;
ychange = pyt - cyt;
unitlen = sqrt(xchange*xchange + ychange*ychange);
if (unitlen == 0 || pointlen == 0) {
x2 = y2 = 0;
if (unitlen == 0) {
x2 = x1 = cxt;
y2 = y1 = cyt;
} else {
x2 = x1 + ychange / unitlen * pointlen;
y2 = y1 - xchange / unitlen * pointlen;
x1 = cxt - ychange / unitlen * pointlen;
y1 = cyt + xchange / unitlen * pointlen;
x2 = cxt + ychange / unitlen * pointlen;
y2 = cyt - xchange / unitlen * pointlen;
}
/***end explained section***/
......@@ -277,6 +283,7 @@ rsvg_paint_server_lin_grad_render (RsvgPaintServer *self, ArtRender *ar,
dy = y2 - y1;
/* workaround for an evil devide by 0 bug - not sure if this is sufficient */
if (fabs(dx) + fabs(dy) <= 0.0000001)
scale = 100000000.;
else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment