rsvg-paint-server.c 15.6 KB
Newer Older
1 2
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim: set sw=4 sts=4 expandtab: */
3
/*
4
   rsvg-paint-server.c: Implement the SVG paint server abstraction.
5

6
   Copyright (C) 2000 Eazel, Inc.
7

8
   This program is free software; you can redistribute it and/or
9
   modify it under the terms of the GNU Library General Public License as
10 11
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.
12

13 14 15
   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
16
   Library General Public License for more details.
17

18
   You should have received a copy of the GNU Library General Public
19 20 21
   License along with this program; if not, write to the
   Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
22

23 24 25
   Author: Raph Levien <raph@artofcode.com>
*/

26
#include "config.h"
27
#include "rsvg-private.h"
28 29
#include "rsvg-defs.h"
#include "rsvg-paint-server.h"
30
#include "rsvg-styles.h"
31
#include "rsvg-image.h"
32

33
#include <glib.h>
34
#include <string.h>
35 36
#include <math.h>

37 38 39
#include "rsvg-css.h"

static RsvgPaintServer *
40
rsvg_paint_server_solid (guint32 argb)
41
{
42
    RsvgPaintServer *result = g_new0 (RsvgPaintServer, 1);
43 44 45

    result->refcnt = 1;
    result->type = RSVG_PAINT_SERVER_SOLID;
46
    result->core.color = g_new0 (RsvgSolidColor, 1);
47 48
    result->core.color->argb = argb;
    result->core.color->currentcolor = FALSE;
49 50

    return result;
51 52 53
}

static RsvgPaintServer *
54
rsvg_paint_server_solid_current_color (void)
55
{
56
    RsvgPaintServer *result = g_new0 (RsvgPaintServer, 1);
57 58 59

    result->refcnt = 1;
    result->type = RSVG_PAINT_SERVER_SOLID;
60
    result->core.color = g_new0 (RsvgSolidColor, 1);
61
    result->core.color->currentcolor = TRUE;
62 63

    return result;
64 65 66
}

static RsvgPaintServer *
67
rsvg_paint_server_iri (char *iri, gboolean has_alternate, RsvgSolidColor alternate)
68
{
69
    RsvgPaintServer *result = g_new0 (RsvgPaintServer, 1);
70 71

    result->refcnt = 1;
72
    result->type = RSVG_PAINT_SERVER_IRI;
73 74 75 76
    result->core.iri = g_new0 (RsvgPaintServerIri, 1);
    result->core.iri->iri_str = iri;
    result->core.iri->has_alternate = has_alternate;
    result->core.iri->alternate = alternate;
77 78

    return result;
79 80
}

81 82 83
static gboolean
parse_current_color_or_argb (const char *str, RsvgSolidColor *dest)
{
84 85
    if (!strcmp (str, "none")) {
        dest->currentcolor = FALSE;
86
        dest->argb = 0;
87
        return FALSE;
88
    } else {
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
        RsvgCssColorSpec spec;

        spec = rsvg_css_parse_color (str, ALLOW_INHERIT_NO, ALLOW_CURRENT_COLOR_YES);
        switch (spec.kind) {
        case RSVG_CSS_COLOR_SPEC_CURRENT_COLOR:
            dest->currentcolor = TRUE;
            dest->argb = 0;
            return TRUE;

        case RSVG_CSS_COLOR_SPEC_ARGB:
            dest->currentcolor = FALSE;
            dest->argb = spec.argb;
            return TRUE;

        case RSVG_CSS_COLOR_PARSE_ERROR:
            dest->currentcolor = FALSE;
            dest->argb = 0;
            return FALSE;

        default:
            g_assert_not_reached ();
            return FALSE;
        }
112 113 114
    }
}

115
/**
116
 * rsvg_paint_server_parse:
117 118 119 120 121
 * @str: The SVG paint specification string to parse.
 *
 * Parses the paint specification @str, creating a new paint server
 * object.
 *
122 123
 * Return value: (nullable): The newly created paint server, or %NULL
 *   on error.
124 125
 **/
RsvgPaintServer *
126
rsvg_paint_server_parse (gboolean *inherit, const char *str)
127
{
128
    char *name;
129 130
    const char *rest;

131
    if (inherit != NULL)
132 133
        *inherit = TRUE;

134
    if (str == NULL || !strcmp (str, "none"))
135 136
        return NULL;

137
    name = rsvg_get_url_string (str, &rest);
138
    if (name) {
139 140 141 142 143 144 145 146 147 148
        RsvgSolidColor alternate;
        gboolean has_alternate;

        while (*rest && g_ascii_isspace (*rest)) {
            rest++;
        }

        has_alternate = parse_current_color_or_argb (rest, &alternate);

        return rsvg_paint_server_iri (name, has_alternate, alternate);
149
    } else {
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
        RsvgCssColorSpec spec;

        spec = rsvg_css_parse_color (str, ALLOW_INHERIT_YES, ALLOW_CURRENT_COLOR_YES);
        switch (spec.kind) {
        case RSVG_CSS_COLOR_SPEC_INHERIT:
            /* FIXME: this is incorrect; we should inherit the paint server */
            if (inherit != NULL)
                *inherit = FALSE;
            return rsvg_paint_server_solid (0);

        case RSVG_CSS_COLOR_SPEC_CURRENT_COLOR:
            return rsvg_paint_server_solid_current_color ();

        case RSVG_CSS_COLOR_SPEC_ARGB:
            return rsvg_paint_server_solid (spec.argb);

        case RSVG_CSS_COLOR_PARSE_ERROR:
            return NULL;

        default:
            g_assert_not_reached ();
            return NULL;
        }
173
    }
174 175 176
}

/**
177
 * rsvg_paint_server_ref:
178
 * @ps: The paint server object to reference.
179 180
 *
 * Reference a paint server object.
181 182
 **/
void
183
rsvg_paint_server_ref (RsvgPaintServer * ps)
184
{
185 186 187
    if (ps == NULL)
        return;
    ps->refcnt++;
188 189 190
}

/**
191
 * rsvg_paint_server_unref:
192
 * @ps: The paint server object to unreference.
193 194
 *
 * Unreference a paint server object.
195 196
 **/
void
197
rsvg_paint_server_unref (RsvgPaintServer * ps)
198
{
199 200 201 202
    if (ps == NULL)
        return;
    if (--ps->refcnt == 0) {
        if (ps->type == RSVG_PAINT_SERVER_SOLID)
203
            g_free (ps->core.color);
204 205
        else if (ps->type == RSVG_PAINT_SERVER_IRI) {
            g_free (ps->core.iri->iri_str);
206
            g_free (ps->core.iri);
207
        }
208 209
        g_free (ps);
    }
210
}
211

212
static void
213
rsvg_stop_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
214
{
215
    RsvgGradientStop *stop = impl;
216
    const char *value;
217
    RsvgState *state;
218
    RsvgState *inherited_state;
219 220
    int opacity;
    guint32 color;
221

222
    state = rsvg_node_get_state (node);
223

224 225 226
    if ((value = rsvg_property_bag_lookup (atts, "offset"))) {
        /* either a number [0,1] or a percentage */
        RsvgLength length = rsvg_length_parse (value, LENGTH_DIR_BOTH);
227

228 229
        if (length.unit == LENGTH_UNIT_DEFAULT || length.unit == LENGTH_UNIT_PERCENT) {
            double offset;
230

231
            offset = length.length;
232

233 234 235 236 237 238 239 240 241 242
            if (offset < 0.0)
                offset = 0.0;
            else if (offset > 1.0)
                offset = 1.0;

            stop->offset = offset;
            stop->is_valid = TRUE;
        } else {
            /* Only default and percent values are allowed */
            stop->is_valid = FALSE;
243
        }
244 245
    }
    if ((value = rsvg_property_bag_lookup (atts, "style")))
246
        rsvg_parse_style (handle, state, value);
247

248
    rsvg_parse_style_pairs (state, atts);
249

250
    inherited_state = rsvg_state_new ();
251
    rsvg_state_reconstruct (inherited_state, node);
252

253 254 255 256 257 258 259
    if (state->has_stop_color) {
        switch (state->stop_color.kind) {
        case RSVG_CSS_COLOR_SPEC_INHERIT:
            switch (inherited_state->stop_color.kind) {
            case RSVG_CSS_COLOR_SPEC_INHERIT:
                color = 0;
                break;
260

261 262 263
            case RSVG_CSS_COLOR_SPEC_CURRENT_COLOR:
                color = inherited_state->current_color;
                break;
264

265 266 267
            case RSVG_CSS_COLOR_SPEC_ARGB:
                color = inherited_state->stop_color.argb;
                break;
268

269 270 271
            case RSVG_CSS_COLOR_PARSE_ERROR:
                color = 0;
                break;
272

273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295
            default:
                g_assert_not_reached ();
                return;
            }
            break;

        case RSVG_CSS_COLOR_SPEC_CURRENT_COLOR:
            color = inherited_state->current_color;
            break;

        case RSVG_CSS_COLOR_SPEC_ARGB:
            color = state->stop_color.argb & 0x00ffffff;
            break;

        case RSVG_CSS_COLOR_PARSE_ERROR:
            color = 0;
            break;

        default:
            g_assert_not_reached ();
            return;
        }
    } else {
296
        color = 0;
297 298
    }

299 300 301 302 303 304 305 306 307 308 309
    if (state->has_stop_opacity) {
        switch (state->stop_opacity_mode) {
        case STOP_OPACITY_SPECIFIED:
            opacity = state->stop_opacity;
            break;

        case STOP_OPACITY_INHERIT:
            switch (inherited_state->stop_opacity_mode) {
            case STOP_OPACITY_SPECIFIED:
                opacity = inherited_state->stop_opacity;
                break;
310

311 312 313
            case STOP_OPACITY_INHERIT:
                opacity = 0xff;
                break;
314

315 316 317 318 319
            default:
                g_assert_not_reached ();
                return;
            }
            break;
320

321 322 323 324 325 326
        default:
            g_assert_not_reached ();
            return;
        }
    } else {
        opacity = 0xff;
327 328 329
    }

    stop->rgba = (color << 8) | opacity;
330

331
    rsvg_state_free (inherited_state);
332 333
}

334 335
static void
rsvg_paint_server_draw (RsvgNode *node, gpointer impl, RsvgDrawingCtx *ctx, int dominate)
336
{
337 338
    /* nothing; paint servers are handled specially */
}
339

340 341 342 343
RsvgNode *
rsvg_new_stop (const char *element_name, RsvgNode *parent)
{
    RsvgGradientStop *stop = g_new0 (RsvgGradientStop, 1);
344

345
    stop->offset = 0;
346
    stop->rgba = 0xff000000;
347
    stop->is_valid = FALSE;
348 349 350 351 352 353 354 355

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_STOP,
                                parent,
                                rsvg_state_new (),
                                stop,
                                rsvg_stop_set_atts,
                                rsvg_paint_server_draw,
                                g_free);
356 357 358
}

static void
359
rsvg_linear_gradient_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
360
{
361
    RsvgLinearGradient *grad = impl;
362 363
    const char *value;

364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
    if ((value = rsvg_property_bag_lookup (atts, "x1"))) {
        grad->x1 = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
        grad->hasx1 = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "y1"))) {
        grad->y1 = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
        grad->hasy1 = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "x2"))) {
        grad->x2 = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
        grad->hasx2 = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "y2"))) {
        grad->y2 = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
        grad->hasy2 = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "spreadMethod"))) {
        if (!strcmp (value, "pad")) {
            grad->spread = CAIRO_EXTEND_PAD;
        } else if (!strcmp (value, "reflect")) {
            grad->spread = CAIRO_EXTEND_REFLECT;
        } else if (!strcmp (value, "repeat")) {
            grad->spread = CAIRO_EXTEND_REPEAT;
387
        }
388 389 390 391 392
        grad->hasspread = TRUE;
    }
    g_free (grad->fallback);
    grad->fallback = g_strdup (rsvg_property_bag_lookup (atts, "xlink:href"));
    if ((value = rsvg_property_bag_lookup (atts, "gradientTransform"))) {
393 394 395 396 397 398 399
        if (rsvg_parse_transform (&grad->affine, value)) {
            grad->hastransform = TRUE;
        } else {
            rsvg_node_set_attribute_parse_error (node,
                                                 "gradientTransform",
                                                 "Invalid transformation");
        }
400 401 402 403 404 405 406
    }
    if ((value = rsvg_property_bag_lookup (atts, "gradientUnits"))) {
        if (!strcmp (value, "userSpaceOnUse"))
            grad->obj_bbox = FALSE;
        else if (!strcmp (value, "objectBoundingBox"))
            grad->obj_bbox = TRUE;
        grad->hasbbox = TRUE;
407
    }
408 409
}

410
static void
411
rsvg_linear_gradient_free (gpointer impl)
412
{
413 414
    RsvgLinearGradient *self = impl;

415
    g_free (self->fallback);
416
    g_free (self);
417
}
418 419

RsvgNode *
420
rsvg_new_linear_gradient (const char *element_name, RsvgNode *parent)
421
{
422
    RsvgLinearGradient *grad = NULL;
423

424
    grad = g_new0 (RsvgLinearGradient, 1);
Christian Persch's avatar
Christian Persch committed
425
    cairo_matrix_init_identity (&grad->affine);
426 427 428
    grad->x1 = rsvg_length_parse ("0", LENGTH_DIR_HORIZONTAL);
    grad->y1 = grad->y2 = rsvg_length_parse ("0", LENGTH_DIR_VERTICAL);
    grad->x2 = rsvg_length_parse ("1", LENGTH_DIR_HORIZONTAL);
429 430
    grad->fallback = NULL;
    grad->obj_bbox = TRUE;
431
    grad->spread = CAIRO_EXTEND_PAD;
432 433
    grad->hasx1 = grad->hasy1 = grad->hasx2 = grad->hasy2 = grad->hasbbox = grad->hasspread =
        grad->hastransform = FALSE;
434 435 436 437 438 439 440 441

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_LINEAR_GRADIENT,
                                parent,
                                rsvg_state_new (),
                                grad,
                                rsvg_linear_gradient_set_atts,
                                rsvg_paint_server_draw,
                                rsvg_linear_gradient_free);
442 443
}

444
static void
445
rsvg_radial_gradient_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag *atts)
446
{
447
    RsvgRadialGradient *grad = impl;
448 449
    const char *value;

450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
    if ((value = rsvg_property_bag_lookup (atts, "cx"))) {
        grad->cx = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
        grad->hascx = TRUE;
        if (!grad->hasfx)
            grad->fx = grad->cx;
    }
    if ((value = rsvg_property_bag_lookup (atts, "cy"))) {
        grad->cy = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
        grad->hascy = TRUE;
        if (!grad->hasfy)
            grad->fy = grad->cy;
    }
    if ((value = rsvg_property_bag_lookup (atts, "r"))) {
        grad->r = rsvg_length_parse (value, LENGTH_DIR_BOTH);
        grad->hasr = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "fx"))) {
        grad->fx = rsvg_length_parse (value, LENGTH_DIR_HORIZONTAL);
        grad->hasfx = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "fy"))) {
        grad->fy = rsvg_length_parse (value, LENGTH_DIR_VERTICAL);
        grad->hasfy = TRUE;
    }
    g_free (grad->fallback);
    grad->fallback = g_strdup (rsvg_property_bag_lookup (atts, "xlink:href"));
    if ((value = rsvg_property_bag_lookup (atts, "gradientTransform"))) {
477 478 479 480 481 482 483
        if (rsvg_parse_transform (&grad->affine, value)) {
            grad->hastransform = TRUE;
        } else {
            rsvg_node_set_attribute_parse_error (node,
                                                 "gradientTransform",
                                                 "Invalid transformation");
        }
484
    }
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
    if ((value = rsvg_property_bag_lookup (atts, "spreadMethod"))) {
        if (!strcmp (value, "pad"))
            grad->spread = CAIRO_EXTEND_PAD;
        else if (!strcmp (value, "reflect"))
            grad->spread = CAIRO_EXTEND_REFLECT;
        else if (!strcmp (value, "repeat"))
            grad->spread = CAIRO_EXTEND_REPEAT;
        grad->hasspread = TRUE;
    }
    if ((value = rsvg_property_bag_lookup (atts, "gradientUnits"))) {
        if (!strcmp (value, "userSpaceOnUse"))
            grad->obj_bbox = FALSE;
        else if (!strcmp (value, "objectBoundingBox"))
            grad->obj_bbox = TRUE;
        grad->hasbbox = TRUE;
    }
501 502
}

503
static void
504
rsvg_radial_gradient_free (gpointer impl)
505
{
506 507
    RsvgRadialGradient *self = impl;

508
    g_free (self->fallback);
509
    g_free (self);
510 511
}

512
RsvgNode *
513
rsvg_new_radial_gradient (const char *element_name, RsvgNode *parent)
514
{
515
    RsvgRadialGradient *grad = g_new0 (RsvgRadialGradient, 1);
Christian Persch's avatar
Christian Persch committed
516
    cairo_matrix_init_identity (&grad->affine);
517
    grad->obj_bbox = TRUE;
518
    grad->spread = CAIRO_EXTEND_PAD;
519
    grad->fallback = NULL;
520
    grad->cx = grad->cy = grad->r = grad->fx = grad->fy = rsvg_length_parse ("0.5", LENGTH_DIR_BOTH);
521 522
    grad->hascx = grad->hascy = grad->hasfx = grad->hasfy = grad->hasr = grad->hasbbox =
        grad->hasspread = grad->hastransform = FALSE;
523 524 525 526 527 528 529 530

    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_RADIAL_GRADIENT,
                                parent,
                                rsvg_state_new (),
                                grad,
                                rsvg_radial_gradient_set_atts,
                                rsvg_paint_server_draw,
                                rsvg_radial_gradient_free);
531
}