rsvg-base.c 80.1 KB
Newer Older
1
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/* vim: set sw=4 sts=4 expandtab: */
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
   rsvg.c: SAX-based renderer for SVG files into a GdkPixbuf.

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002 Dom Lachowicz <cinamod@hotmail.com>

   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: Raph Levien <raph@artofcode.com>
*/

#include "config.h"
28
#define _GNU_SOURCE 1
29 30

#include "rsvg-private.h"
31
#include "rsvg-compat.h"
32 33 34
#include "rsvg-css.h"
#include "rsvg-styles.h"
#include "rsvg-shapes.h"
35
#include "rsvg-structure.h"
36
#include "rsvg-io.h"
37 38 39 40
#include "rsvg-text.h"
#include "rsvg-filter.h"
#include "rsvg-mask.h"
#include "rsvg-marker.h"
41
#include "rsvg-cairo-render.h"
42

43
#include <libxml/uri.h>
44 45
#include <libxml/parser.h>
#include <libxml/parserInternals.h>
46

Christian Persch's avatar
Christian Persch committed
47 48
#include <gio/gio.h>

49 50 51
#include <math.h>
#include <string.h>
#include <stdarg.h>
52 53
#include <limits.h>
#include <stdlib.h>
54

55
#include "rsvg-path-builder.h"
56
#include "rsvg-paint-server.h"
57
#include "rsvg-xml.h"
58

59
#ifdef G_OS_WIN32
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
static char *
rsvg_realpath_utf8 (const char *filename, const char *unused)
{
    wchar_t *wfilename;
    wchar_t *wfull;
    char *full;

    wfilename = g_utf8_to_utf16 (filename, -1, NULL, NULL, NULL);
    if (!wfilename)
        return NULL;

    wfull = _wfullpath (NULL, wfilename, 0);
    g_free (wfilename);
    if (!wfull)
        return NULL;

    full = g_utf16_to_utf8 (wfull, -1, NULL, NULL, NULL);
    free (wfull);

    if (!full)
        return NULL;

    return full;
}

#define realpath(a,b) rsvg_realpath_utf8 (a, b)
86 87
#endif

88 89 90 91 92
/*
 * This is configurable at runtime
 */
#define RSVG_DEFAULT_DPI_X 90.0
#define RSVG_DEFAULT_DPI_Y 90.0
93 94

G_GNUC_INTERNAL
95
double rsvg_internal_dpi_x = RSVG_DEFAULT_DPI_X;
96
G_GNUC_INTERNAL
97
double rsvg_internal_dpi_y = RSVG_DEFAULT_DPI_Y;
98

99 100 101
static xmlSAXHandler rsvgSAXHandlerStruct;
static gboolean rsvgSAXHandlerStructInited = FALSE;

102
typedef struct _RsvgSaxHandlerDefs {
103 104
    RsvgSaxHandler super;
    RsvgHandle *ctx;
105 106 107
} RsvgSaxHandlerDefs;

typedef struct _RsvgSaxHandlerStyle {
108 109 110 111
    RsvgSaxHandler super;
    RsvgSaxHandlerDefs *parent;
    RsvgHandle *ctx;
    GString *style;
112
    gboolean is_text_css;
113 114
} RsvgSaxHandlerStyle;

115 116 117 118 119 120 121 122
typedef struct {
    RsvgSaxHandler super;
    RsvgHandle *ctx;
    const char *name;
    GString *string;
    GString **stringptr;
} RsvgSaxHandlerExtra;

123
/* hide this fact from the general public */
124 125 126
typedef RsvgSaxHandlerExtra RsvgSaxHandlerTitle;
typedef RsvgSaxHandlerExtra RsvgSaxHandlerDesc;
typedef RsvgSaxHandlerExtra RsvgSaxHandlerMetadata;
127 128

static void
129
rsvg_style_handler_free (RsvgSaxHandler * self)
130
{
131 132 133
    RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *) self;
    RsvgHandle *ctx = z->ctx;

134 135
    if (z->is_text_css)
        rsvg_parse_cssbuffer (ctx, z->style->str, z->style->len);
136 137 138

    g_string_free (z->style, TRUE);
    g_free (z);
139 140 141
}

static void
142
rsvg_style_handler_characters (RsvgSaxHandler * self, const char *ch, int len)
143
{
144 145
    RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *) self;
    g_string_append_len (z->style, ch, len);
146 147 148
}

static void
149
rsvg_style_handler_start (RsvgSaxHandler * self, const char *name, RsvgPropertyBag * atts)
150 151 152 153
{
}

static void
154 155 156 157 158 159 160 161 162 163 164 165
rsvg_style_handler_end (RsvgSaxHandler * self, const char *name)
{
    RsvgSaxHandlerStyle *z = (RsvgSaxHandlerStyle *) self;
    RsvgHandle *ctx = z->ctx;
    RsvgSaxHandler *prev = &z->parent->super;

    if (!strcmp (name, "style")) {
        if (ctx->priv->handler != NULL) {
            ctx->priv->handler->free (ctx->priv->handler);
            ctx->priv->handler = prev;
        }
    }
166 167 168
}

static void
169
rsvg_start_style (RsvgHandle * ctx, RsvgPropertyBag *atts)
170
{
171
    RsvgSaxHandlerStyle *handler = g_new0 (RsvgSaxHandlerStyle, 1);
172 173 174
    const char *type;

    type = rsvg_property_bag_lookup (atts, "type");
175 176 177 178 179 180 181 182

    handler->super.free = rsvg_style_handler_free;
    handler->super.characters = rsvg_style_handler_characters;
    handler->super.start_element = rsvg_style_handler_start;
    handler->super.end_element = rsvg_style_handler_end;
    handler->ctx = ctx;

    handler->style = g_string_new (NULL);
183 184 185 186 187 188 189 190 191 192 193 194 195 196

    /* FIXME: See these:
     *
     * https://www.w3.org/TR/SVG/styling.html#StyleElementTypeAttribute
     * https://www.w3.org/TR/SVG/styling.html#ContentStyleTypeAttribute
     *
     * If the "type" attribute is not present, we should fallback to the
     * "contentStyleType" attribute of the svg element, which in turn
     * defaults to "text/css".
     *
     * See where is_text_css is used to see where we parse the contents
     * of the style element.
     */
    handler->is_text_css = (type == NULL) || (g_ascii_strcasecmp (type, "text/css") == 0);
197 198 199

    handler->parent = (RsvgSaxHandlerDefs *) ctx->priv->handler;
    ctx->priv->handler = &handler->super;
200 201
}

202 203 204 205 206 207
static void
add_node_to_handle (RsvgHandle *ctx, RsvgNode *node)
{
    g_assert (ctx != NULL);
    g_assert (node != NULL);

208
    g_ptr_array_add (ctx->priv->all_nodes, rsvg_node_ref (node));
209 210
}

211 212 213 214 215 216 217 218 219 220 221
static void
register_node_in_defs (RsvgHandle *ctx, RsvgNode *node, RsvgPropertyBag *atts)
{
    const char *id;

    id = rsvg_property_bag_lookup (atts, "id");
    if (id) {
        rsvg_defs_register_node_by_id (ctx->priv->defs, id, node);
    }
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
static void
push_element_name (RsvgHandle *ctx, const char *name)
{
    /* libxml holds on to the name while parsing; we won't dup the name here */
    ctx->priv->element_name_stack = g_slist_prepend (ctx->priv->element_name_stack, (void *) name);
}

static gboolean
topmost_element_name_is (RsvgHandle *ctx, const char *name)
{
    if (ctx->priv->element_name_stack) {
        const char *name_in_stack = ctx->priv->element_name_stack->data;

        return strcmp (name, name_in_stack) == 0;
    } else
        return FALSE;
}

static void
pop_element_name (RsvgHandle *ctx)
{
    ctx->priv->element_name_stack = g_slist_delete_link (ctx->priv->element_name_stack, ctx->priv->element_name_stack);
}

static void
free_element_name_stack (RsvgHandle *ctx)
{
    g_slist_free (ctx->priv->element_name_stack);
    ctx->priv->element_name_stack = NULL;
}
252

253
typedef RsvgNode *(* CreateNodeFn) (const char *element_name, RsvgNode *parent);
254 255

typedef struct {
256 257 258
    const char   *element_name;
    gboolean      supports_class_attribute; /* from https://www.w3.org/TR/SVG/attindex.html#RegularAttributes */
    CreateNodeFn  create_fn;
259 260
} NodeCreator;

261 262 263 264
/* Keep these sorted by element_name!
 *
 * Lines in comments are elements that we don't support.
 */
265
static const NodeCreator node_creators[] = {
266
    { "a",                   TRUE,  rsvg_node_group_new },    /* treat anchors as groups for now */
267 268 269 270 271 272 273
    /* "altGlyph",           TRUE,  */
    /* "altGlyphDef",        FALSE, */
    /* "altGlyphItem",       FALSE, */
    /* "animate",            FALSE, */
    /* "animateColor",       FALSE, */
    /* "animateMotion",      FALSE, */
    /* "animateTransform",   FALSE, */
274
    { "circle",              TRUE,  rsvg_node_circle_new },
275 276
    { "clipPath",            TRUE,  rsvg_new_clip_path },
    /* "color-profile",      FALSE, */
277
    { "conicalGradient",     TRUE,  rsvg_node_radial_gradient_new },
278
    /* "cursor",             FALSE, */
279
    { "defs",                TRUE,  rsvg_node_defs_new },
280
    /* "desc",               TRUE,  */
281
    { "ellipse",             TRUE,  rsvg_node_ellipse_new },
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
    { "feBlend",             TRUE,  rsvg_new_filter_primitive_blend },
    { "feColorMatrix",       TRUE,  rsvg_new_filter_primitive_color_matrix },
    { "feComponentTransfer", TRUE,  rsvg_new_filter_primitive_component_transfer },
    { "feComposite",         TRUE,  rsvg_new_filter_primitive_composite },
    { "feConvolveMatrix",    TRUE,  rsvg_new_filter_primitive_convolve_matrix },
    { "feDiffuseLighting",   TRUE,  rsvg_new_filter_primitive_diffuse_lighting },
    { "feDisplacementMap",   TRUE,  rsvg_new_filter_primitive_displacement_map },
    { "feDistantLight",      FALSE, rsvg_new_node_light_source },
    { "feFlood",             TRUE,  rsvg_new_filter_primitive_flood },
    { "feFuncA",             FALSE, rsvg_new_node_component_transfer_function },
    { "feFuncB",             FALSE, rsvg_new_node_component_transfer_function },
    { "feFuncG",             FALSE, rsvg_new_node_component_transfer_function },
    { "feFuncR",             FALSE, rsvg_new_node_component_transfer_function },
    { "feGaussianBlur",      TRUE,  rsvg_new_filter_primitive_gaussian_blur },
    { "feImage",             TRUE,  rsvg_new_filter_primitive_image },
    { "feMerge",             TRUE,  rsvg_new_filter_primitive_merge },
    { "feMergeNode",         FALSE, rsvg_new_filter_primitive_merge_node },
    { "feMorphology",        TRUE,  rsvg_new_filter_primitive_erode },
    { "feOffset",            TRUE,  rsvg_new_filter_primitive_offset },
    { "fePointLight",        FALSE, rsvg_new_node_light_source },
    { "feSpecularLighting",  TRUE,  rsvg_new_filter_primitive_specular_lighting },
    { "feSpotLight",         FALSE, rsvg_new_node_light_source },
    { "feTile",              TRUE,  rsvg_new_filter_primitive_tile },
    { "feTurbulence",        TRUE,  rsvg_new_filter_primitive_turbulence },
    { "filter",              TRUE,  rsvg_new_filter },
    /* "font",               TRUE,  */
    /* "font-face",          FALSE, */
    /* "font-face-format",   FALSE, */
    /* "font-face-name",     FALSE, */
    /* "font-face-src",      FALSE, */
    /* "font-face-uri",      FALSE, */
    /* "foreignObject",      TRUE,  */
314
    { "g",                   TRUE,  rsvg_node_group_new },
315 316 317
    /* "glyph",              TRUE,  */
    /* "glyphRef",           TRUE,  */
    /* "hkern",              FALSE, */
318
    { "image",               TRUE,  rsvg_node_image_new },
319
    { "line",                TRUE,  rsvg_node_line_new },
320
    { "linearGradient",      TRUE,  rsvg_node_linear_gradient_new },
321
    { "marker",              TRUE,  rsvg_node_marker_new },
322 323 324 325
    { "mask",                TRUE,  rsvg_new_mask },
    /* "metadata",           FALSE, */
    /* "missing-glyph",      TRUE,  */
    /* "mpath"               FALSE, */
326
    { "multiImage",          FALSE, rsvg_node_switch_new }, /* hack to make multiImage sort-of work */
327
    { "path",                TRUE,  rsvg_node_path_new },
328
    { "pattern",             TRUE,  rsvg_node_pattern_new },
329 330
    { "polygon",             TRUE,  rsvg_node_polygon_new },
    { "polyline",            TRUE,  rsvg_node_polyline_new },
331
    { "radialGradient",      TRUE,  rsvg_node_radial_gradient_new },
332
    { "rect",                TRUE,  rsvg_node_rect_new },
333 334
    /* "script",             FALSE, */
    /* "set",                FALSE, */
335
    { "stop",                TRUE,  rsvg_node_stop_new },
336
    /* "style",              FALSE, */
337
    { "subImage",            FALSE, rsvg_node_group_new },
338
    { "subImageRef",         FALSE, rsvg_node_image_new },
339
    { "svg",                 TRUE,  rsvg_node_svg_new },
340
    { "switch",              TRUE,  rsvg_node_switch_new },
341
    { "symbol",              TRUE,  rsvg_node_symbol_new },
342 343 344 345 346
    { "text",                TRUE,  rsvg_new_text },
    /* "textPath",           TRUE,  */
    /* "title",              TRUE,  */
    { "tref",                TRUE,  rsvg_new_tref },
    { "tspan",               TRUE,  rsvg_new_tspan },
347
    { "use",                 TRUE,  rsvg_node_use_new },
348 349
    /* "view",               FALSE, */
    /* "vkern",              FALSE, */
350 351
};

352 353 354
/* Whenever we encounter a node we don't understand, represent it as a defs.
 * This is like a group, but it doesn't do any rendering of children.  The
 * effect is that we will ignore all children of unknown elements.
355
 */
356
static const NodeCreator default_node_creator = { NULL, TRUE, rsvg_node_defs_new };
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374

/* Used from bsearch() */
static int
compare_node_creators_fn (const void *a, const void *b)
{
    const NodeCreator *na = a;
    const NodeCreator *nb = b;

    return strcmp (na->element_name, nb->element_name);
}

static const NodeCreator *
get_node_creator_for_element_name (const char *name)
{
    NodeCreator key;
    const NodeCreator *result;

    key.element_name = name;
375
    key.supports_class_attribute = FALSE;
376 377 378 379 380 381 382 383 384 385 386 387 388 389
    key.create_fn = NULL;

    result = bsearch (&key,
                      node_creators,
                      G_N_ELEMENTS (node_creators),
                      sizeof (NodeCreator),
                      compare_node_creators_fn);

    if (result == NULL)
        result = &default_node_creator;

    return result;
}

390 391 392 393 394 395 396
static void
node_set_atts (RsvgNode * node, RsvgHandle * ctx, const NodeCreator *creator, RsvgPropertyBag * atts)
{
    if (rsvg_property_bag_size (atts) > 0) {
        const char *id;
        const char *klazz;

397
        rsvg_node_set_atts (node, ctx, atts);
398 399 400 401 402

        /* The "svg" node is special; it will load its id/class
         * attributes until the end, when rsvg_end_element() calls
         * _rsvg_node_svg_apply_atts()
         */
403
        if (rsvg_node_get_type (node) != RSVG_NODE_TYPE_SVG) {
404 405 406 407 408 409 410
            id = rsvg_property_bag_lookup (atts, "id");

            if (creator->supports_class_attribute)
                klazz = rsvg_property_bag_lookup (atts, "class");
            else
                klazz = NULL;

411
            rsvg_parse_style_attrs (ctx, node, creator->element_name, klazz, id, atts);
412 413 414 415
        }
    }
}

416
static void
417 418
rsvg_standard_element_start (RsvgHandle * ctx, const char *name, RsvgPropertyBag * atts)
{
419
    const NodeCreator *creator;
420
    RsvgNode *newnode = NULL;
421 422 423 424

    creator = get_node_creator_for_element_name (name);
    g_assert (creator != NULL && creator->create_fn != NULL);

425
    newnode = creator->create_fn (name, ctx->priv->currentnode);
426
    g_assert (newnode != NULL);
427

428
    g_assert (rsvg_node_get_type (newnode) != RSVG_NODE_TYPE_INVALID);
429

430
    push_element_name (ctx, name);
431

432 433
    add_node_to_handle (ctx, newnode);
    register_node_in_defs (ctx, newnode, atts);
434

435 436 437 438 439 440
    if (ctx->priv->currentnode) {
        rsvg_node_add_child (ctx->priv->currentnode, newnode);
        ctx->priv->currentnode = rsvg_node_unref (ctx->priv->currentnode);
    } else if (rsvg_node_get_type (newnode) == RSVG_NODE_TYPE_SVG) {
        ctx->priv->treebase = rsvg_node_ref (newnode);
    }
441

442
    ctx->priv->currentnode = rsvg_node_ref (newnode);
443

444
    node_set_atts (newnode, ctx, creator, atts);
445

446
    newnode = rsvg_node_unref (newnode);
447 448
}

449
/* extra (title, desc, metadata) */
450 451

static void
452
rsvg_extra_handler_free (RsvgSaxHandler * self)
453
{
454 455 456 457 458 459 460 461 462 463
    RsvgSaxHandlerExtra *z = (RsvgSaxHandlerExtra *) self;

    if (z->stringptr) {
        if (*z->stringptr)
            g_string_free (*z->stringptr, TRUE);
        *z->stringptr = z->string;
    } else if (z->string) {
        g_string_free (z->string, TRUE);
    }

464
    g_free (self);
465 466 467
}

static void
468
rsvg_extra_handler_characters (RsvgSaxHandler * self, const char *ch, int len)
469
{
470
    RsvgSaxHandlerExtra *z = (RsvgSaxHandlerExtra *) self;
471

472
    /* This isn't quite the correct behavior - in theory, any graphics
473
       element may contain a title, desc, or metadata element */
474

475
    if (!z->string)
476
        return;
477

478 479
    if (!ch || !len)
        return;
Christian Persch's avatar
Christian Persch committed
480

481 482 483
    if (!g_utf8_validate ((char *) ch, len, NULL)) {
        char *utf8;
        utf8 = rsvg_make_valid_utf8 ((char *) ch, len);
484
        g_string_append (z->string, utf8);
485 486
        g_free (utf8);
    } else {
487
        g_string_append_len (z->string, (char *) ch, len);
488
    }
489 490 491
}

static void
492
rsvg_extra_handler_start (RsvgSaxHandler * self, const char *name, RsvgPropertyBag * atts)
493 494 495 496
{
}

static void
497
rsvg_extra_handler_end (RsvgSaxHandler * self, const char *name)
498
{
499
    RsvgSaxHandlerExtra *z = (RsvgSaxHandlerExtra *) self;
500 501
    RsvgHandle *ctx = z->ctx;

502
    if (!strcmp (name, z->name)) {
503 504 505 506 507
        if (ctx->priv->handler != NULL) {
            ctx->priv->handler->free (ctx->priv->handler);
            ctx->priv->handler = NULL;
        }
    }
508 509
}

510 511 512 513
static RsvgSaxHandlerExtra *
rsvg_start_extra (RsvgHandle * ctx,
                  const char *name,
                  GString **stringptr)
514
{
515
    RsvgSaxHandlerExtra *handler = g_new0 (RsvgSaxHandlerExtra, 1);
Christian Persch's avatar
Christian Persch committed
516 517 518 519
    RsvgNode *treebase = ctx->priv->treebase;
    RsvgNode *currentnode = ctx->priv->currentnode;
    gboolean do_care;

520
    /* only parse <extra> for the <svg> node.
Christian Persch's avatar
Christian Persch committed
521
     * This isn't quite the correct behavior - any graphics
522
     * element may contain a <extra> element.
Christian Persch's avatar
Christian Persch committed
523
     */
524
    do_care = treebase != NULL && rsvg_node_is_same (treebase, currentnode);
525

526
    handler->super.free = rsvg_extra_handler_free;
527
    handler->super.characters = rsvg_extra_handler_characters;
528 529
    handler->super.start_element = rsvg_extra_handler_start;
    handler->super.end_element = rsvg_extra_handler_end;
530
    handler->ctx = ctx;
531 532 533
    handler->name = name; /* interned */
    handler->string = do_care ? g_string_new (NULL) : NULL;
    handler->stringptr = do_care ? stringptr : NULL;
534 535

    ctx->priv->handler = &handler->super;
536

537
    return handler;
538 539
}

540
/* start desc */
541 542

static void
543
rsvg_start_desc (RsvgHandle * ctx)
544
{
545
    rsvg_start_extra (ctx, "desc", &ctx->priv->desc);
546 547
}

548
/* end desc */
549

550
/* start title */
551 552

static void
553
rsvg_start_title (RsvgHandle * ctx)
554
{
555
    rsvg_start_extra (ctx, "title", &ctx->priv->title);
556 557 558 559 560 561 562
}

/* end title */

/* start metadata */

static void
563
rsvg_metadata_props_enumerate (const char *key, const char *value, gpointer user_data)
564
{
565 566
    GString *metadata = (GString *) user_data;
    g_string_append_printf (metadata, "%s=\"%s\" ", key, value);
567 568 569
}

static void
570
rsvg_metadata_handler_start (RsvgSaxHandler * self, const char *name, RsvgPropertyBag * atts)
571
{
572
    RsvgSaxHandlerMetadata *z = (RsvgSaxHandlerMetadata *) self;
573

574 575 576 577 578 579 580 581
    rsvg_extra_handler_start (self, name, atts);

    if (!z->string)
        return;

    g_string_append_printf (z->string, "<%s ", name);
    rsvg_property_bag_enumerate (atts, rsvg_metadata_props_enumerate, z->string);
    g_string_append (z->string, ">\n");
582 583 584
}

static void
585 586 587 588
rsvg_metadata_handler_end (RsvgSaxHandler * self, const char *name)
{
    RsvgSaxHandlerMetadata *z = (RsvgSaxHandlerMetadata *) self;

589 590 591 592 593 594
    if (strcmp (name, z->name) != 0) {
        if (z->string)
            g_string_append_printf (z->string, "</%s>\n", name);
    } else {
        rsvg_extra_handler_end (self, name);
    }
595 596 597
}

static void
598
rsvg_start_metadata (RsvgHandle * ctx)
599
{
600
    RsvgSaxHandlerMetadata *handler = rsvg_start_extra (ctx, "metadata", &ctx->priv->metadata);
601 602 603

    handler->super.start_element = rsvg_metadata_handler_start;
    handler->super.end_element = rsvg_metadata_handler_end;
604 605 606 607
}

/* end metadata */

608 609 610
/* start xinclude */

typedef struct _RsvgSaxHandlerXinclude {
611
    RsvgSaxHandler super;
612

613 614 615 616
    RsvgSaxHandler *prev_handler;
    RsvgHandle *ctx;
    gboolean success;
    gboolean in_fallback;
617 618 619
} RsvgSaxHandlerXinclude;

static void
620
 rsvg_start_xinclude (RsvgHandle * ctx, RsvgPropertyBag * atts);
621
static void
622
 rsvg_characters_impl (RsvgHandle * ctx, const xmlChar * ch, int len);
623 624

static void
625
rsvg_xinclude_handler_free (RsvgSaxHandler * self)
626
{
627
    g_free (self);
628 629 630
}

static void
631
rsvg_xinclude_handler_characters (RsvgSaxHandler * self, const char *ch, int len)
632
{
633
    RsvgSaxHandlerXinclude *z = (RsvgSaxHandlerXinclude *) self;
634

635 636 637
    if (z->in_fallback) {
        rsvg_characters_impl (z->ctx, (const xmlChar *) ch, len);
    }
638 639 640
}

static void
641 642 643 644 645 646 647 648 649 650 651 652 653 654
rsvg_xinclude_handler_start (RsvgSaxHandler * self, const char *name, RsvgPropertyBag * atts)
{
    RsvgSaxHandlerXinclude *z = (RsvgSaxHandlerXinclude *) self;

    if (!z->success) {
        if (z->in_fallback) {
            if (!strcmp (name, "xi:include"))
                rsvg_start_xinclude (z->ctx, atts);
            else
                rsvg_standard_element_start (z->ctx, (const char *) name, atts);
        } else if (!strcmp (name, "xi:fallback")) {
            z->in_fallback = TRUE;
        }
    }
655 656 657
}

static void
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
rsvg_xinclude_handler_end (RsvgSaxHandler * self, const char *name)
{
    RsvgSaxHandlerXinclude *z = (RsvgSaxHandlerXinclude *) self;
    RsvgHandle *ctx = z->ctx;

    if (!strcmp (name, "include") || !strcmp (name, "xi:include")) {
        if (ctx->priv->handler != NULL) {
            RsvgSaxHandler *previous_handler;

            previous_handler = z->prev_handler;
            ctx->priv->handler->free (ctx->priv->handler);
            ctx->priv->handler = previous_handler;
        }
    } else if (z->in_fallback) {
        if (!strcmp (name, "xi:fallback"))
            z->in_fallback = FALSE;
    }
675 676
}

677
static void
678 679
rsvg_set_xml_parse_options(xmlParserCtxtPtr xml_parser,
                           RsvgHandle *ctx)
680
{
681 682 683 684
    int options;

    options = (XML_PARSE_NONET |
               XML_PARSE_BIG_LINES);
685 686

    if (ctx->priv->flags & RSVG_HANDLE_FLAG_UNLIMITED) {
687
        options |= XML_PARSE_HUGE;
688 689
    }

690
    xmlCtxtUseOptions (xml_parser, options);
691 692 693 694 695

    /* if false, external entities work, but internal ones don't. if true, internal entities
       work, but external ones don't. favor internal entities, in order to not cause a
       regression */
    xml_parser->replaceEntities = TRUE;
696 697
}

698 699 700 701 702 703 704 705 706 707 708 709
static xmlParserCtxtPtr
create_xml_parser (RsvgHandle *handle,
                   const char *base_uri)
{
    xmlParserCtxtPtr parser;

    parser = xmlCreatePushParserCtxt (&rsvgSAXHandlerStruct, handle, NULL, 0, base_uri);
    rsvg_set_xml_parse_options (parser, handle);

    return parser;
}

710 711
/* http://www.w3.org/TR/xinclude/ */
static void
712 713 714
rsvg_start_xinclude (RsvgHandle * ctx, RsvgPropertyBag * atts)
{
    RsvgSaxHandlerXinclude *handler;
715
    const char *href, *parse;
716 717 718
    gboolean success = FALSE;

    href = rsvg_property_bag_lookup (atts, "href");
719 720
    if (href == NULL)
        goto fallback;
721

722 723
    parse = rsvg_property_bag_lookup (atts, "parse");
    if (parse && !strcmp (parse, "text")) {
724
        char *data;
725 726
        gsize data_len;
        const char *encoding;
727

728
        data = _rsvg_handle_acquire_data (ctx, href, NULL, &data_len, NULL);
729 730
        if (data == NULL)
            goto fallback;
731

732 733 734 735
        encoding = rsvg_property_bag_lookup (atts, "encoding");
        if (encoding && g_ascii_strcasecmp (encoding, "UTF-8") != 0) {
            char *text_data;
            gsize text_data_len;
736

737 738 739 740 741 742 743 744
            text_data = g_convert (data, data_len, "utf-8", encoding, NULL,
                                   &text_data_len, NULL);
            g_free (data);

            data = text_data;
            data_len = text_data_len;
        }

745
        rsvg_characters_impl (ctx, (xmlChar *) data, data_len);
746

747 748 749 750 751 752 753
        g_free (data);
    } else {
        /* xml */
        GInputStream *stream;
        GError *err = NULL;
        xmlParserCtxtPtr xml_parser;

754
        stream = _rsvg_handle_acquire_stream (ctx, href, NULL, NULL);
755 756 757
        if (stream == NULL)
            goto fallback;

758 759 760 761 762 763
        xml_parser = rsvg_create_xml_parser_from_stream (&rsvgSAXHandlerStruct,
                                                         ctx,
                                                         stream,
                                                         NULL, /* cancellable */
                                                         &err);
        rsvg_set_xml_parse_options (xml_parser, ctx);
764 765 766

        g_object_unref (stream);

767 768
        if (xml_parser) {
            (void) xmlParseDocument (xml_parser);
769 770

            xml_parser = rsvg_free_xml_parser_and_doc (xml_parser);
771
        }
772 773

        g_clear_error (&err);
774 775
    }

776 777 778 779
    success = TRUE;

  fallback:

780 781 782 783 784 785 786 787 788 789 790 791
    /* needed to handle xi:fallback */
    handler = g_new0 (RsvgSaxHandlerXinclude, 1);

    handler->super.free = rsvg_xinclude_handler_free;
    handler->super.characters = rsvg_xinclude_handler_characters;
    handler->super.start_element = rsvg_xinclude_handler_start;
    handler->super.end_element = rsvg_xinclude_handler_end;
    handler->prev_handler = ctx->priv->handler;
    handler->ctx = ctx;
    handler->success = success;

    ctx->priv->handler = &handler->super;
792 793 794 795
}

/* end xinclude */

796
static void
797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814
rsvg_start_element (void *data, const xmlChar * name, const xmlChar ** atts)
{
    RsvgPropertyBag *bag;
    RsvgHandle *ctx = (RsvgHandle *) data;

    bag = rsvg_property_bag_new ((const char **) atts);

    if (ctx->priv->handler) {
        ctx->priv->handler_nest++;
        if (ctx->priv->handler->start_element != NULL)
            ctx->priv->handler->start_element (ctx->priv->handler, (const char *) name, bag);
    } else {
        const char *tempname;
        for (tempname = (const char *) name; *tempname != '\0'; tempname++)
            if (*tempname == ':')
                name = (const xmlChar *) (tempname + 1);

        if (!strcmp ((const char *) name, "style"))
815
            rsvg_start_style (ctx, bag);
816
        else if (!strcmp ((const char *) name, "title"))
817
            rsvg_start_title (ctx);
818
        else if (!strcmp ((const char *) name, "desc"))
819
            rsvg_start_desc (ctx);
820
        else if (!strcmp ((const char *) name, "metadata"))
821
            rsvg_start_metadata (ctx);
822 823 824 825
        else if (!strcmp ((const char *) name, "include"))      /* xi:include */
            rsvg_start_xinclude (ctx, bag);
        else
            rsvg_standard_element_start (ctx, (const char *) name, bag);
826 827
    }

828
    rsvg_property_bag_free (bag);
829 830 831
}

static void
832
rsvg_end_element (void *data, const xmlChar * xmlname)
833
{
834
    RsvgHandle *ctx = (RsvgHandle *) data;
835
    const char *name = (const char *) xmlname;
836

837 838
    if (ctx->priv->handler_nest > 0 && ctx->priv->handler != NULL) {
        if (ctx->priv->handler->end_element != NULL)
839
            ctx->priv->handler->end_element (ctx->priv->handler, name);
840 841
        ctx->priv->handler_nest--;
    } else {
842
        const char *tempname;
843
        for (tempname = name; *tempname != '\0'; tempname++)
844
            if (*tempname == ':')
845
                name = tempname + 1;
846

847 848 849 850
        if (ctx->priv->handler != NULL) {
            ctx->priv->handler->free (ctx->priv->handler);
            ctx->priv->handler = NULL;
        }
851

852
        if (ctx->priv->currentnode && topmost_element_name_is (ctx, name)) {
853 854 855
            RsvgNode *parent;

            parent = rsvg_node_get_parent (ctx->priv->currentnode);
856
            ctx->priv->currentnode = rsvg_node_unref (ctx->priv->currentnode);
857
            ctx->priv->currentnode = parent;
858
            pop_element_name (ctx);
859
        }
860

861
        /* FIXMEchpe: shouldn't this check that currentnode == treebase or sth like that? */
862 863
        if (ctx->priv->treebase && !strcmp (name, "svg")) {
            g_assert (rsvg_node_get_type (ctx->priv->treebase) == RSVG_NODE_TYPE_SVG);
864
            rsvg_node_svg_apply_atts (ctx->priv->treebase, ctx);
865
        }
866
    }
867 868
}

869
static void
870
rsvg_node_chars_set_atts (RsvgNode *node, gpointer impl, RsvgHandle *handle, RsvgPropertyBag * atts)
871
{
872 873 874 875 876 877 878 879 880 881 882 883 884
    /* nothing */
}

static void
rsvg_node_chars_draw (RsvgNode *node, gpointer impl, RsvgDrawingCtx *ctx, int dominate)
{
    /* nothing */
}

static void
rsvg_node_chars_free (gpointer impl)
{
    RsvgNodeChars *self = impl;
885
    g_string_free (self->contents, TRUE);
886
    g_free (self);
887 888
}

889
static RsvgNode *
890
rsvg_new_node_chars (const char *text,
891 892
                     int len,
                     RsvgNode *parent)
893 894
{
    RsvgNodeChars *self;
895
    RsvgState *state;
896

897
    self = g_new0 (RsvgNodeChars, 1);
898 899 900 901 902 903 904 905 906 907

    if (!g_utf8_validate (text, len, NULL)) {
        char *utf8;
        utf8 = rsvg_make_valid_utf8 (text, len);
        self->contents = g_string_new (utf8);
        g_free (utf8);
    } else {
        self->contents = g_string_new_len (text, len);
    }

908 909
    state = rsvg_state_new ();
    state->cond_true = FALSE;
910

911 912 913 914 915 916 917
    return rsvg_rust_cnode_new (RSVG_NODE_TYPE_CHARS,
                                parent,
                                state,
                                self,
                                rsvg_node_chars_set_atts,
                                rsvg_node_chars_draw,
                                rsvg_node_chars_free);
918 919
}

920 921 922 923 924 925 926
static gboolean
find_last_chars_node (RsvgNode *node, gpointer data)
{
    RsvgNode **dest;

    dest = data;

927
    if (rsvg_node_get_type (node) == RSVG_NODE_TYPE_CHARS) {
928
        *dest = rsvg_node_ref (node);
929
    } else if (rsvg_node_get_type (node) == RSVG_NODE_TYPE_TSPAN) {
930
        *dest = rsvg_node_unref (*dest); /* Discard the last chars node we found */
931 932 933 934 935
    }

    return TRUE;
}

936
static void
937
rsvg_characters_impl (RsvgHandle * ctx, const xmlChar * ch, int len)
938
{
939
    RsvgNode *node;
940

941 942
    if (!ch || !len)
        return;
943

944
    if (ctx->priv->currentnode) {
945
        RsvgNodeType type = rsvg_node_get_type (ctx->priv->currentnode);
946
        if (type == RSVG_NODE_TYPE_TSPAN || type == RSVG_NODE_TYPE_TEXT) {
947 948
            RsvgNodeChars *self;

949 950
            /* find the last CHARS node in the text or tspan node, so that we
               can coalesce the text, and thus avoid screwing up the Pango layouts */
951
            node = NULL;
952 953
            rsvg_node_foreach_child (ctx->priv->currentnode,
                                     find_last_chars_node,
954 955
                                     &node);

956 957 958
            if (node) {
                g_assert (rsvg_node_get_type (node) == RSVG_NODE_TYPE_CHARS);
                self = rsvg_rust_cnode_get_impl (node);
959 960 961 962 963 964 965 966 967 968

                if (!g_utf8_validate ((char *) ch, len, NULL)) {
                    char *utf8;
                    utf8 = rsvg_make_valid_utf8 ((char *) ch, len);
                    g_string_append (self->contents, utf8);
                    g_free (utf8);
                } else {
                    g_string_append_len (self->contents, (char *)ch, len);
                }

969
                node = rsvg_node_unref (node);
970 971 972 973
                return;
            }
        }
    }
974

975
    node = rsvg_new_node_chars ((char *) ch, len, ctx->priv->currentnode);
976

977
    add_node_to_handle (ctx, node);
978

979
    if (ctx->priv->currentnode)
980
        rsvg_node_add_child (ctx->priv->currentnode, node);
981 982

    node = rsvg_node_unref (node);
983 984
}

985
static void
986
rsvg_characters (void *data, const xmlChar * ch, int len)
987
{
988 989 990 991 992 993 994 995
    RsvgHandle *ctx = (RsvgHandle *) data;

    if (ctx->priv->handler && ctx->priv->handler->characters != NULL) {
        ctx->priv->handler->characters (ctx->priv->handler, (const char *) ch, len);
        return;
    }

    rsvg_characters_impl (ctx, ch, len);
996 997
}

998
static xmlEntityPtr
999
rsvg_get_entity (void *data, const xmlChar * name)
1000
{
1001 1002
    RsvgHandle *ctx = (RsvgHandle *) data;
    xmlEntityPtr entity;
1003

1004
    entity = g_hash_table_lookup (ctx->priv->entities, name);
1005

1006
    return entity;
1007 1008 1009
}

static void
1010 1011 1012 1013 1014 1015
rsvg_entity_decl (void *data, const xmlChar * name, int type,
                  const xmlChar * publicId, const xmlChar * systemId, xmlChar * content)
{
    RsvgHandle *ctx = (RsvgHandle *) data;
    GHashTable *entities = ctx->priv->entities;
    xmlEntityPtr entity;
1016 1017 1018 1019 1020 1021 1022
    xmlChar *resolvedSystemId = NULL, *resolvedPublicId = NULL;

    if (systemId)
        resolvedSystemId = xmlBuildRelativeURI (systemId, (xmlChar*) rsvg_handle_get_base_uri (ctx));
    else if (publicId)
        resolvedPublicId = xmlBuildRelativeURI (publicId, (xmlChar*) rsvg_handle_get_base_uri (ctx));

1023
    if (type == XML_EXTERNAL_PARAMETER_ENTITY && !content) {
1024
        char *entity_data;
1025 1026 1027
        gsize entity_data_len;

        if (systemId)
1028 1029
            entity_data = _rsvg_handle_acquire_data (ctx,
                                                     (const char *) systemId,
1030
                                                     NULL,
1031 1032
                                                     &entity_data_len,
                                                     NULL);
1033
        else if (publicId)
1034 1035
            entity_data = _rsvg_handle_acquire_data (ctx,
                                                     (const char *) publicId,
1036
                                                     NULL,
1037 1038
                                                     &entity_data_len,
                                                     NULL);
1039 1040 1041
        else
            entity_data = NULL;

1042 1043 1044 1045
        if (entity_data) {
            content = xmlCharStrndup (entity_data, entity_data_len);
            g_free (entity_data);
        }
1046
    }
1047

1048 1049
    entity = xmlNewEntity(NULL, name, type, resolvedPublicId, resolvedSystemId, content);

Christian Persch's avatar
Christian Persch committed
1050 1051
    xmlFree(resolvedPublicId);
    xmlFree(resolvedSystemId);
1052 1053

    g_hash_table_insert (entities, g_strdup ((const char*) name), entity);
1054 1055
}

1056
static void
1057 1058 1059 1060
rsvg_unparsed_entity_decl (void *ctx,
                           const xmlChar * name,
                           const xmlChar * publicId,
                           const xmlChar * systemId, const xmlChar * notationName)
1061
{
1062
    rsvg_entity_decl (ctx, name, XML_INTERNAL_GENERAL_ENTITY, publicId, systemId, NULL);
1063 1064
}

1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
static xmlEntityPtr
rsvg_get_parameter_entity (void *data, const xmlChar * name)
{
    RsvgHandle *ctx = (RsvgHandle *) data;
    xmlEntityPtr entity;

    entity = g_hash_table_lookup (ctx->priv->entities, name);

    return entity;
}

1076 1077 1078 1079
static void
rsvg_error_cb (void *data, const char *msg, ...)
{
#ifdef G_ENABLE_DEBUG
1080 1081 1082 1083 1084
    va_list args;

    va_start (args, msg);
    vfprintf (stderr, msg, args);
    va_end (args);
1085 1086 1087
#endif
}

1088
static void
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099
rsvg_processing_instruction (void *ctx, const xmlChar * target, const xmlChar * data)
{
    /* http://www.w3.org/TR/xml-stylesheet/ */
    RsvgHandle *handle = (RsvgHandle *) ctx;

    if (!strcmp ((const char *) target, "xml-stylesheet")) {
        RsvgPropertyBag *atts;
        char **xml_atts;

        xml_atts = rsvg_css_parse_xml_attribute_string ((const char *) data);

1100
        if (xml_atts) {
1101 1102
            const char *value;

1103
            atts = rsvg_property_bag_new ((const char **) xml_atts);
1104
            value = rsvg_property_bag_lookup (atts, "alternate");
1105
            if (!value || !value[0] || (strcmp (value, "no") != 0)) {
1106 1107 1108
                value = rsvg_property_bag_lookup (atts, "type");
                if (value && strcmp (value, "text/css") == 0) {
                    value = rsvg_property_bag_lookup (atts, "href");
1109
                    if (value && value[0]) {
1110
                        char *style_data;
1111
                        gsize style_data_len;
1112
                        char *mime_type = NULL;
1113

1114 1115
                        style_data = _rsvg_handle_acquire_data (handle,
                                                                value,
1116
                                                                &mime_type,
1117 1118
                                                                &style_data_len,
                                                                NULL);
1119
                        if (style_data &&
1120 1121
                            mime_type &&
                            strcmp (mime_type, "text/css") == 0) {
1122
                            rsvg_parse_cssbuffer (handle, style_data, style_data_len);
1123
                        }
1124 1125

                        g_free (mime_type);
1126
                        g_free (style_data);
1127 1128 1129 1130 1131
                    }
                }
            }

            rsvg_property_bag_free (atts);
1132
            g_strfreev (xml_atts);
1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143
        }
    }
}

void
rsvg_SAX_handler_struct_init (void)
{
    if (!rsvgSAXHandlerStructInited) {
        rsvgSAXHandlerStructInited = TRUE;

        memset (&rsvgSAXHandlerStruct, 0, sizeof (rsvgSAXHandlerStruct));
1144

1145 1146 1147
        rsvgSAXHandlerStruct.getEntity = rsvg_get_entity;
        rsvgSAXHandlerStruct.entityDecl = rsvg_entity_decl;
        rsvgSAXHandlerStruct.unparsedEntityDecl = rsvg_unparsed_entity_decl;
1148
        rsvgSAXHandlerStruct.getParameterEntity = rsvg_get_parameter_entity;
1149 1150 1151 1152 1153 1154 1155
        rsvgSAXHandlerStruct.characters = rsvg_characters;
        rsvgSAXHandlerStruct.error = rsvg_error_cb;
        rsvgSAXHandlerStruct.cdataBlock = rsvg_characters;
        rsvgSAXHandlerStruct.startElement = rsvg_start_element;
        rsvgSAXHandlerStruct.endElement = rsvg_end_element;
        rsvgSAXHandlerStruct.processingInstruction = rsvg_processing_instruction;
    }
1156 1157
}

Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1158 1159 1160 1161 1162 1163 1164 1165
/* http://www.ietf.org/rfc/rfc2396.txt */

static gboolean
rsvg_path_is_uri (char const *path)
{
    char const *p;

    if (path == NULL)
1166
        return FALSE;
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1167 1168

    if (strlen (path) < 4)
1169
        return FALSE;
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1170

1171 1172 1173 1174
    if ((path[0] < 'a' || path[0] > 'z') &&
        (path[0] < 'A' || path[0] > 'Z')) {
        return FALSE;
    }
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1175 1176

    for (p = &path[1];
1177 1178 1179 1180 1181 1182 1183
	    (*p >= 'a' && *p <= 'z') ||
        (*p >= 'A' && *p <= 'Z') ||
        (*p >= '0' && *p <= '9') ||
         *p == '+' ||
         *p == '-' ||
         *p == '.';
        p++);
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1184 1185

    if (strlen (p) < 3)
1186
        return FALSE;
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1187 1188 1189 1190

    return (p[0] == ':' && p[1] == '/' && p[2] == '/');
}

1191
gchar *
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1192
rsvg_get_base_uri_from_filename (const gchar * filename)
1193
{
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1194 1195
    gchar *current_dir;
    gchar *absolute_filename;
1196 1197
    gchar *base_uri;

1198

Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1199 1200
    if (g_path_is_absolute (filename))
        return g_filename_to_uri (filename, NULL, NULL);
1201

Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1202 1203 1204 1205 1206
    current_dir = g_get_current_dir ();
    absolute_filename = g_build_filename (current_dir, filename, NULL);
    base_uri = g_filename_to_uri (absolute_filename, NULL, NULL);
    g_free (absolute_filename);
    g_free (current_dir);
1207

1208
    return base_uri;
1209 1210 1211
}

/**
1212
 * rsvg_handle_set_base_uri:
1213
 * @handle: A #RsvgHandle
1214 1215
 * @base_uri: The base uri
 *
1216 1217
 * Set the base URI for this SVG. This can only be called before rsvg_handle_write()
 * has been called.
1218
 *
1219
 * Since: 2.9
1220
 */
1221 1222
void
rsvg_handle_set_base_uri (RsvgHandle * handle, const char *base_uri)
1223
{
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1224
    gchar *uri;
1225
    GFile *file;
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1226 1227 1228 1229 1230 1231

    g_return_if_fail (handle != NULL);

    if (base_uri == NULL)
	return;

1232
    if (rsvg_path_is_uri (base_uri))
1233
        uri = g_strdup (base_uri);
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
1234
    else
1235
        uri = rsvg_get_base_uri_from_filename (base_uri);
1236

1237 1238 1239 1240
    file = g_file_new_for_uri (uri ? uri : "data:");
    rsvg_handle_set_base_gfile (handle, file);
    g_object_unref (file);
    g_free (uri);
1241 1242
}

1243 1244 1245
/**
 * rsvg_handle_set_base_gfile:
 * @handle: a #RsvgHandle
1246
 * @base_file: a #GFile
1247 1248 1249
 *
 * Set the base URI for @handle from @file.
 * Note: This function may only be called before rsvg_handle_write()
Christian Persch's avatar
Christian Persch committed
1250
 * or rsvg_handle_read_stream_sync() has been called.
1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
 *
 * Since: 2.32
 */
void
rsvg_handle_set_base_gfile (RsvgHandle *handle,
                            GFile      *base_file)
{
    RsvgHandlePrivate *priv;

    g_return_if_fail (RSVG_IS_HANDLE (handle));
    g_return_if_fail (G_IS_FILE (base_file));

    priv = handle->priv;

    g_object_ref (base_file);
    if (priv->base_gfile)
        g_object_unref (priv->base_gfile);
    priv->base_gfile = base_file;
1269

1270 1271 1272 1273
    g_free (priv->base_uri);
    priv->base_uri = g_file_get_uri (base_file);
}

1274
/**
1275
 * rsvg_handle_get_base_uri:
1276 1277
 * @handle: A #RsvgHandle
 *
1278 1279
 * Gets the base uri for this #RsvgHandle.
 *
1280
 * Returns: the base uri, possibly null
Christian Persch's avatar
Christian Persch committed
1281
 * Since: 2.8
1282
 */
1283
const char *
1284
rsvg_handle_get_base_uri (RsvgHandle * handle)
1285
{
1286 1287
    g_return_val_if_fail (handle, NULL);
    return handle->priv->base_uri;
1288 1289 1290
}

/**
1291
 * rsvg_error_quark: