rsvg-image.c 15.6 KB
Newer Older
1
/* vim: set sw=4 sts=4: -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 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 28
/*
   rsvg-image.c: Image loading and displaying

   Copyright (C) 2000 Eazel, Inc.
   Copyright (C) 2002, 2003, 2004, 2005 Dom Lachowicz <cinamod@hotmail.com>
   Copyright (C) 2003, 2004, 2005 Caleb Moore <c.moore@student.unsw.edu.au>

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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

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

   Authors: Raph Levien <raph@artofcode.com>, 
            Dom Lachowicz <cinamod@hotmail.com>, 
            Caleb Moore <c.moore@student.unsw.edu.au>
*/

29 30
#include "config.h"

31 32 33 34 35
#include "rsvg-image.h"
#include <string.h>
#include <math.h>
#include <errno.h>
#include "rsvg-css.h"
36 37 38
#ifdef HAVE_GIO
#include <gio/gio.h>
#endif
39 40

static const char s_UTF8_B64Alphabet[64] = {
41 42 43 44 45 46 47
    0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
    0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,   /* A-Z */
    0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
    0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,   /* a-z */
    0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, /* 0-9 */
    0x2b,                       /* + */
    0x2f                        /* / */
48 49 50
};
static const char utf8_b64_pad = 0x3d;

51 52
static gboolean
b64_decode_char (char c, int *b64)
53
{
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
    if ((c >= 0x41) && (c <= 0x5a)) {
        *b64 = c - 0x41;
        return TRUE;
    }
    if ((c >= 0x61) && (c <= 0x7a)) {
        *b64 = c - (0x61 - 26);
        return TRUE;
    }
    if ((c >= 0x30) && (c <= 0x39)) {
        *b64 = c + (52 - 0x30);
        return TRUE;
    }
    if (c == 0x2b) {
        *b64 = 62;
        return TRUE;
    }
    if (c == 0x2f) {
        *b64 = 63;
        return TRUE;
    }
    return FALSE;
75 76
}

77 78
static gboolean
utf8_base64_decode (guchar ** binptr, size_t * binlen, const char *b64ptr, size_t b64len)
79
{
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
    gboolean decoded = TRUE;
    gboolean padding = FALSE;

    int i = 0;
    glong ucs4_len, j;

    unsigned char byte1 = 0;
    unsigned char byte2;

    gunichar ucs4, *ucs4_str;

    if (b64len == 0)
        return TRUE;

    if ((binptr == 0) || (b64ptr == 0))
        return FALSE;

    ucs4_str = g_utf8_to_ucs4_fast (b64ptr, b64len, &ucs4_len);

    for (j = 0; j < ucs4_len; j++) {
        ucs4 = ucs4_str[j];
        if ((ucs4 & 0x7f) == ucs4) {
            int b64;
            char c = (char) (ucs4);

            if (b64_decode_char (c, &b64)) {
                if (padding || (*binlen == 0)) {
                    decoded = FALSE;
                    break;
                }

                switch (i) {
                case 0:
                    byte1 = (unsigned char) (b64) << 2;
                    i++;
                    break;
                case 1:
                    byte2 = (unsigned char) (b64);
                    byte1 |= byte2 >> 4;
                    *(*binptr)++ = (char) (byte1);
                    (*binlen)--;
                    byte1 = (byte2 & 0x0f) << 4;
                    i++;
                    break;
                case 2:
                    byte2 = (unsigned char) (b64);
                    byte1 |= byte2 >> 2;
                    *(*binptr)++ = (char) (byte1);
                    (*binlen)--;
                    byte1 = (byte2 & 0x03) << 6;
                    i++;
                    break;
                default:
                    byte1 |= (unsigned char) (b64);
                    *(*binptr)++ = (char) (byte1);
                    (*binlen)--;
                    i = 0;
                    break;
                }

                if (!decoded)
                    break;

                continue;
            } else if (c == utf8_b64_pad) {
                switch (i) {
                case 0:
                case 1:
                    decoded = FALSE;
                    break;
                case 2:
                    if (*binlen == 0)
                        decoded = FALSE;
                    else {
                        *(*binptr)++ = (char) (byte1);
                        (*binlen)--;
                        padding = TRUE;
                    }
                    i++;
                    break;
                default:
                    if (!padding) {
                        if (*binlen == 0)
                            decoded = FALSE;
                        else {
                            *(*binptr)++ = (char) (byte1);
                            (*binlen)--;
                            padding = TRUE;
                        }
                    }
                    i = 0;
                    break;
                }
                if (!decoded)
                    break;

                continue;
            }
        }
        if (g_unichar_isspace (ucs4))
            continue;

        decoded = FALSE;
        break;
    }

    g_free (ucs4_str);
    return decoded;
188 189 190
}

static GByteArray *
191
rsvg_acquire_base64_resource (const char *data, GError ** error)
192
{
193
    GByteArray *array;
194

195 196
    guchar *bufptr;
    size_t buffer_len, buffer_max_len, data_len;
197

198
    rsvg_return_val_if_fail (data != NULL, NULL, error);
199

200 201 202
    while (*data)
        if (*data++ == ',')
            break;
203

204
    data_len = strlen (data);
205

206 207
    buffer_max_len = ((data_len >> 2) + 1) * 3;
    buffer_len = buffer_max_len;
208

209 210 211 212 213 214 215 216 217 218 219
    array = g_byte_array_sized_new (buffer_max_len);
    bufptr = array->data;

    if (!utf8_base64_decode (&bufptr, &buffer_len, data, data_len)) {
        g_byte_array_free (array, TRUE);
        return NULL;
    }

    array->len = buffer_max_len - buffer_len;

    return array;
220 221 222
}

gchar *
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
223
rsvg_get_file_path (const gchar * filename, const gchar * base_uri)
224
{
225
    gchar *absolute_filename;
226

227 228 229 230
    if (g_file_test (filename, G_FILE_TEST_EXISTS) || g_path_is_absolute (filename)) {
        absolute_filename = g_strdup (filename);
    } else {
        gchar *tmpcdir;
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
231 232 233 234 235 236 237 238 239 240 241
	gchar *base_filename;

        if (base_uri) {
	    base_filename = g_filename_from_uri (base_uri, NULL, NULL);
	    if (base_filename != NULL) {
		tmpcdir = g_path_get_dirname (base_filename);
		g_free (base_filename);
	    } else 
		return NULL;
	} else
	    tmpcdir = g_get_current_dir ();
242

243 244 245
        absolute_filename = g_build_filename (tmpcdir, filename, NULL);
        g_free (tmpcdir);
    }
246

247
    return absolute_filename;
248 249 250
}

static GByteArray *
251
rsvg_acquire_file_resource (const char *filename, const char *base_uri, GError ** error)
252
{
253 254 255 256 257 258 259 260 261 262
    GByteArray *array;
    gchar *path;

    guchar buffer[4096];
    int length;
    FILE *f;

    rsvg_return_val_if_fail (filename != NULL, NULL, error);

    path = rsvg_get_file_path (filename, base_uri);
Emmanuel Pacaud's avatar
Emmanuel Pacaud committed
263 264 265
    if (path == NULL)
	return NULL;

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
    f = fopen (path, "rb");
    g_free (path);

    if (!f) {
        g_set_error (error,
                     G_FILE_ERROR,
                     g_file_error_from_errno (errno),
                     _("Failed to open file '%s': %s"), filename, g_strerror (errno));
        return NULL;
    }

    /* TODO: an optimization is to use the file's size */
    array = g_byte_array_new ();

    while (!feof (f)) {
        length = fread (buffer, 1, sizeof (buffer), f);
        if (length > 0) {
            if (g_byte_array_append (array, buffer, length) == NULL) {
                fclose (f);
                g_byte_array_free (array, TRUE);
                return NULL;
            }
        } else if (ferror (f)) {
            fclose (f);
            g_byte_array_free (array, TRUE);
            return NULL;
        }
    }

    fclose (f);

    return array;
298 299
}

300
#ifdef HAVE_GIO
301

302 303 304 305 306 307 308 309 310 311 312
static void
rsvg_free_error (GError ** err)
{
	if (err) {
		if (*err) {
			g_error_free (*err);
			*err = NULL;
		}
	}
}

313
static GByteArray *
314
rsvg_acquire_vfs_resource (const char *filename, const char *base_uri, GError ** error)
315
{
316 317
    GByteArray *array;

318 319 320
    GFile *file;
    char *data;
    gsize size;
321
    gboolean res = FALSE;
322 323 324

    rsvg_return_val_if_fail (filename != NULL, NULL, error);

325
    file = g_file_new_for_uri (filename);
326

327 328 329
    if (!(res = g_file_load_contents (file, NULL, &data, &size, NULL, error))) {
        if (base_uri != NULL) {
            GFile *base;
330

331 332 333
			rsvg_free_error(error);
			
			g_object_unref (file);
334 335 336 337 338

            base = g_file_new_for_uri (base_uri);
            file = g_file_resolve_relative_path (base, filename);
            g_object_unref (base);

339
			res = g_file_load_contents (file, NULL, &data, &size, NULL, error);
340 341 342
        }
    }

343
	g_object_unref (file);
344

345 346
    if (res) {
        array = g_byte_array_new ();
347

348 349 350 351
        g_byte_array_append (array, (guint8 *)data, size);
        g_free (data);
    } else {
        return NULL;
352 353 354
    }

    return array;
355 356 357 358
}
#endif

GByteArray *
359
_rsvg_acquire_xlink_href_resource (const char *href, const char *base_uri, GError ** err)
360
{
361
    GByteArray *arr = NULL;
362

363 364
    if (!(href && *href))
        return NULL;
365

366
    if (!strncmp (href, "data:", 5))
367
        arr = rsvg_acquire_base64_resource (href, NULL);
368 369

    if (!arr)
370
        arr = rsvg_acquire_file_resource (href, base_uri, NULL);
371

372
#ifdef HAVE_GIO
373
    if (!arr)
374
        arr = rsvg_acquire_vfs_resource (href, base_uri, NULL);
375 376
#endif

377
    return arr;
378 379 380
}

GdkPixbuf *
381
rsvg_pixbuf_new_from_href (const char *href, const char *base_uri, GError ** error)
382
{
383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    GByteArray *arr;

    arr = _rsvg_acquire_xlink_href_resource (href, base_uri, error);
    if (arr) {
        GdkPixbufLoader *loader;
        GdkPixbuf *pixbuf = NULL;
        int res;

        loader = gdk_pixbuf_loader_new ();

        res = gdk_pixbuf_loader_write (loader, arr->data, arr->len, error);
        g_byte_array_free (arr, TRUE);

        if (!res) {
            gdk_pixbuf_loader_close (loader, NULL);
            g_object_unref (loader);
            return NULL;
        }

        if (!gdk_pixbuf_loader_close (loader, error)) {
            g_object_unref (loader);
            return NULL;
        }

        pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);

        if (!pixbuf) {
            g_object_unref (loader);
            g_set_error (error,
                         GDK_PIXBUF_ERROR,
                         GDK_PIXBUF_ERROR_FAILED,
                         _
                         ("Failed to load image '%s': reason not known, probably a corrupt image file"),
                         href);
            return NULL;
        }

        g_object_ref (pixbuf);

        g_object_unref (loader);

        return pixbuf;
    }

    return NULL;
428 429 430
}

void
431 432
rsvg_preserve_aspect_ratio (unsigned int aspect_ratio, double width,
                            double height, double *w, double *h, double *x, double *y)
433
{
434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
    double neww, newh;
    if (aspect_ratio & ~RSVG_ASPECT_RATIO_SLICE) {
        neww = *w;
        newh = *h;
        if ((height * *w > width * *h) == ((aspect_ratio & RSVG_ASPECT_RATIO_SLICE) == 0)) {
            neww = width * *h / height;
        } else {
            newh = height * *w / width;
        }

        if (aspect_ratio & RSVG_ASPECT_RATIO_XMIN_YMIN ||
            aspect_ratio & RSVG_ASPECT_RATIO_XMIN_YMID ||
            aspect_ratio & RSVG_ASPECT_RATIO_XMIN_YMAX) {
        } else if (aspect_ratio & RSVG_ASPECT_RATIO_XMID_YMIN ||
                   aspect_ratio & RSVG_ASPECT_RATIO_XMID_YMID ||
                   aspect_ratio & RSVG_ASPECT_RATIO_XMID_YMAX)
            *x -= (neww - *w) / 2;
        else
            *x -= neww - *w;

        if (aspect_ratio & RSVG_ASPECT_RATIO_XMIN_YMIN ||
            aspect_ratio & RSVG_ASPECT_RATIO_XMID_YMIN ||
            aspect_ratio & RSVG_ASPECT_RATIO_XMAX_YMIN) {
        } else if (aspect_ratio & RSVG_ASPECT_RATIO_XMIN_YMID ||
                   aspect_ratio & RSVG_ASPECT_RATIO_XMID_YMID ||
                   aspect_ratio & RSVG_ASPECT_RATIO_XMAX_YMID)
            *y -= (newh - *h) / 2;
        else
            *y -= newh - *h;

        *w = neww;
        *h = newh;
    }
467 468
}

469
static void
Caleb Michael Moore's avatar
Caleb Michael Moore committed
470
rsvg_node_image_free (RsvgNode * self)
471
{
472 473 474
    RsvgNodeImage *z = (RsvgNodeImage *) self;
    rsvg_state_finalize (z->super.state);
    g_free (z->super.state);
475
    z->super.state = NULL;
476 477
    if (z->img)
        g_object_unref (G_OBJECT (z->img));
478
    _rsvg_node_free(self);
479 480
}

481 482
static void
rsvg_node_image_draw (RsvgNode * self, RsvgDrawingCtx * ctx, int dominate)
483
{
484 485 486 487
    RsvgNodeImage *z = (RsvgNodeImage *) self;
    unsigned int aspect_ratio = z->preserve_aspect_ratio;
    GdkPixbuf *img = z->img;
    gdouble x, y, w, h;
Dom Lachowicz's avatar
Dom Lachowicz committed
488

489 490
    if (img == NULL)
        return;
Dom Lachowicz's avatar
Dom Lachowicz committed
491

492 493 494 495
    x = _rsvg_css_normalize_length (&z->x, ctx, 'h');
    y = _rsvg_css_normalize_length (&z->y, ctx, 'v');
    w = _rsvg_css_normalize_length (&z->w, ctx, 'h');
    h = _rsvg_css_normalize_length (&z->h, ctx, 'v');
496

497
    rsvg_state_reinherit_top (ctx, z->super.state, dominate);
498

499
    rsvg_push_discrete_layer (ctx);
500

501 502 503
    if (!rsvg_state_current (ctx)->overflow && (aspect_ratio & RSVG_ASPECT_RATIO_SLICE)) {
        rsvg_add_clipping_rect (ctx, x, y, w, h);
    }
504

505 506
    rsvg_preserve_aspect_ratio (aspect_ratio, (double) gdk_pixbuf_get_width (img),
                                (double) gdk_pixbuf_get_height (img), &w, &h, &x, &y);
507

508
    rsvg_render_image (ctx, img, x, y, w, h);
509

510
    rsvg_pop_discrete_layer (ctx);
511 512
}

513
static void
514
rsvg_node_image_set_atts (RsvgNode * self, RsvgHandle * ctx, RsvgPropertyBag * atts)
515
{
516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
    const char *klazz = NULL, *id = NULL, *value;
    RsvgNodeImage *image = (RsvgNodeImage *) self;

    if (rsvg_property_bag_size (atts)) {
        if ((value = rsvg_property_bag_lookup (atts, "x")))
            image->x = _rsvg_css_parse_length (value);
        if ((value = rsvg_property_bag_lookup (atts, "y")))
            image->y = _rsvg_css_parse_length (value);
        if ((value = rsvg_property_bag_lookup (atts, "width")))
            image->w = _rsvg_css_parse_length (value);
        if ((value = rsvg_property_bag_lookup (atts, "height")))
            image->h = _rsvg_css_parse_length (value);
        /* path is used by some older adobe illustrator versions */
        if ((value = rsvg_property_bag_lookup (atts, "path"))
            || (value = rsvg_property_bag_lookup (atts, "xlink:href"))) {
            image->img = rsvg_pixbuf_new_from_href (value, rsvg_handle_get_base_uri (ctx), NULL);

            if (!image->img) {
534
#ifdef G_ENABLE_DEBUG
535
                g_warning (_("Couldn't load image: %s\n"), value);
536
#endif
537 538 539 540 541 542 543 544 545 546 547 548 549
            }
        }
        if ((value = rsvg_property_bag_lookup (atts, "class")))
            klazz = value;
        if ((value = rsvg_property_bag_lookup (atts, "id"))) {
            id = value;
            rsvg_defs_register_name (ctx->priv->defs, id, &image->super);
        }
        if ((value = rsvg_property_bag_lookup (atts, "preserveAspectRatio")))
            image->preserve_aspect_ratio = rsvg_css_parse_aspect_ratio (value);

        rsvg_parse_style_attrs (ctx, image->super.state, "image", klazz, id, atts);
    }
550
}
551

552 553 554
RsvgNode *
rsvg_new_image (void)
{
555 556 557
    RsvgNodeImage *image;
    image = g_new (RsvgNodeImage, 1);
    _rsvg_node_init (&image->super);
558
    g_assert (image->super.state);
559 560 561 562 563 564 565
    image->img = NULL;
    image->preserve_aspect_ratio = RSVG_ASPECT_RATIO_XMID_YMID;
    image->x = image->y = image->w = image->h = _rsvg_css_parse_length ("0");
    image->super.free = rsvg_node_image_free;
    image->super.draw = rsvg_node_image_draw;
    image->super.set_atts = rsvg_node_image_set_atts;
    return &image->super;
566
}