rsvg-convert.c 20.4 KB
Newer Older
1 2
/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 28 29 30

   rsvg-convert.c: Command line utility for exercising rsvg with cairo.
 
   Copyright (C) 2005 Red Hat, Inc.
   Copyright (C) 2005 Dom Lachowicz <cinamod@hotmail.com>
   Copyright (C) 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: Carl Worth <cworth@cworth.org>, 
            Caleb Moore <c.moore@student.unsw.edu.au>,
            Dom Lachowicz <cinamod@hotmail.com>
*/

31 32
#define RSVG_DISABLE_DEPRECATION_WARNINGS

33 34
#include "config.h"

35
#include <math.h>
36
#include <errno.h>
37 38 39
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
40
#include <limits.h>
41
#include <locale.h>
42 43
#include <glib/gi18n.h>
#include <gio/gio.h>
44 45

#ifdef G_OS_UNIX
46
#include <gio/gunixinputstream.h>
47
#endif
48

49 50 51
#ifdef G_OS_WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
52
#include <io.h>
53
#include <fcntl.h>
54 55 56 57

#include <gio/gwin32inputstream.h>
#endif

58 59
#include "librsvg/rsvg-css.h"
#include "librsvg/rsvg.h"
60 61 62 63 64 65 66 67 68

#ifdef CAIRO_HAS_PS_SURFACE
#include <cairo-ps.h>
#endif

#ifdef CAIRO_HAS_PDF_SURFACE
#include <cairo-pdf.h>
#endif

69 70 71 72
#ifdef CAIRO_HAS_SVG_SURFACE
#include <cairo-svg.h>
#endif

Christian Persch's avatar
Christian Persch committed
73 74 75 76
#ifdef CAIRO_HAS_XML_SURFACE
#include <cairo-xml.h>
#endif

77 78 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
typedef enum {
    SIZE_KIND_ZOOM,
    SIZE_KIND_WH,
    SIZE_KIND_WH_MAX,
    SIZE_KIND_ZOOM_MAX
} SizeKind;

typedef struct {
    SizeKind kind;
    double x_zoom;
    double y_zoom;
    gint width;
    gint height;

    gboolean keep_aspect_ratio;
} SizeMode;

static void
get_final_size (int *width, int *height, SizeMode *real_data)
{
    double zoomx, zoomy, zoom;

    int in_width, in_height;

    in_width = *width;
    in_height = *height;

    switch (real_data->kind) {
    case SIZE_KIND_ZOOM:
        if (*width < 0 || *height < 0)
            return;

        *width = floor (real_data->x_zoom * *width + 0.5);
        *height = floor (real_data->y_zoom * *height + 0.5);
        break;

    case SIZE_KIND_ZOOM_MAX:
        if (*width < 0 || *height < 0)
            return;

        *width = floor (real_data->x_zoom * *width + 0.5);
        *height = floor (real_data->y_zoom * *height + 0.5);

        if (*width > real_data->width || *height > real_data->height) {
            zoomx = (double) real_data->width / *width;
            zoomy = (double) real_data->height / *height;
            zoom = MIN (zoomx, zoomy);

            *width = floor (zoom * *width + 0.5);
            *height = floor (zoom * *height + 0.5);
        }
        break;

    case SIZE_KIND_WH_MAX:
        if (*width < 0 || *height < 0)
            return;

        zoomx = (double) real_data->width / *width;
        zoomy = (double) real_data->height / *height;
        if (zoomx < 0)
            zoom = zoomy;
        else if (zoomy < 0)
            zoom = zoomx;
        else
            zoom = MIN (zoomx, zoomy);

        *width = floor (zoom * *width + 0.5);
        *height = floor (zoom * *height + 0.5);
        break;

    case SIZE_KIND_WH:
        if (real_data->width != -1)
            *width = real_data->width;
        if (real_data->height != -1)
            *height = real_data->height;
        break;

    default:
        g_assert_not_reached ();
    }

    if (real_data->keep_aspect_ratio) {
        int out_min = MIN (*width, *height);

        if (out_min == *width) {
            *height = in_height * ((double) *width / (double) in_width);
        } else {
            *width = in_width * ((double) *height / (double) in_height);
        }
    }
}

169 170
static void
display_error (GError * err)
171
{
172
    if (err) {
173
        g_printerr ("%s\n", err->message);
174 175
        g_error_free (err);
    }
176 177
}

178
static cairo_status_t
179
rsvg_cairo_write_func (void *closure, const unsigned char *data, unsigned int length)
180
{
181
    if (fwrite (data, 1, length, (FILE *) closure) == length)
182 183
        return CAIRO_STATUS_SUCCESS;
    return CAIRO_STATUS_WRITE_ERROR;
184 185
}

186 187 188 189 190 191 192 193
static char *
get_lookup_id_from_command_line (const char *lookup_id)
{
    char *export_lookup_id;

    if (lookup_id == NULL)
        export_lookup_id = NULL;
    else {
194 195 196 197 198
        /* rsvg_handle_has_sub() expects ids to have a '#' prepended to them, so
         * it can lookup ids in externs like "subfile.svg#subid".  For the
         * user's convenience, we include this '#' automatically; we only
         * support specifying ids from the toplevel, and don't expect users to
         * lookup things in externs.
199 200 201 202 203 204 205
         */
        export_lookup_id = g_strdup_printf ("#%s", lookup_id);
    }

    return export_lookup_id;
}
 
206
int
207
main (int argc, char **argv)
208
{
209 210 211 212 213 214 215 216 217 218 219
    GOptionContext *g_option_context;
    double x_zoom = 1.0;
    double y_zoom = 1.0;
    double zoom = 1.0;
    double dpi_x = -1.0;
    double dpi_y = -1.0;
    int width = -1;
    int height = -1;
    int bVersion = 0;
    char *format = NULL;
    char *output = NULL;
220
    char *export_id = NULL;
221
    int keep_aspect_ratio = FALSE;
Vincent Untz's avatar
Vincent Untz committed
222
    guint32 background_color = 0;
223
    char *background_color_str = NULL;
224
    gboolean using_stdin = FALSE;
225
    gboolean unlimited = FALSE;
226 227
    gboolean keep_image_data = FALSE;
    gboolean no_keep_image_data = FALSE;
228 229
    GError *error = NULL;

230 231
    gboolean success = TRUE;

232
    int i;
233
    char **args = NULL;
234
    gint n_args = 0;
235
    RsvgHandle *rsvg = NULL;
236 237
    cairo_surface_t *surface = NULL;
    cairo_t *cr = NULL;
238
    RsvgHandleFlags flags = RSVG_HANDLE_FLAGS_NONE;
239 240
    RsvgDimensionData dimensions;
    FILE *output_file = stdout;
241
    char *export_lookup_id;
242 243
    double unscaled_width, unscaled_height;
    int scaled_width, scaled_height;
244

245 246 247 248 249 250 251
    char buffer[25];
    char *endptr;
    char *source_date_epoch;
    time_t now;
    struct tm *build_time;
    unsigned long long epoch;

252 253 254 255
#ifdef G_OS_WIN32
    HANDLE handle;
#endif

256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
    GOptionEntry options_table[] = {
        {"dpi-x", 'd', 0, G_OPTION_ARG_DOUBLE, &dpi_x,
         N_("pixels per inch [optional; defaults to 90dpi]"), N_("<float>")},
        {"dpi-y", 'p', 0, G_OPTION_ARG_DOUBLE, &dpi_y,
         N_("pixels per inch [optional; defaults to 90dpi]"), N_("<float>")},
        {"x-zoom", 'x', 0, G_OPTION_ARG_DOUBLE, &x_zoom,
         N_("x zoom factor [optional; defaults to 1.0]"), N_("<float>")},
        {"y-zoom", 'y', 0, G_OPTION_ARG_DOUBLE, &y_zoom,
         N_("y zoom factor [optional; defaults to 1.0]"), N_("<float>")},
        {"zoom", 'z', 0, G_OPTION_ARG_DOUBLE, &zoom, N_("zoom factor [optional; defaults to 1.0]"),
         N_("<float>")},
        {"width", 'w', 0, G_OPTION_ARG_INT, &width,
         N_("width [optional; defaults to the SVG's width]"), N_("<int>")},
        {"height", 'h', 0, G_OPTION_ARG_INT, &height,
         N_("height [optional; defaults to the SVG's height]"), N_("<int>")},
        {"format", 'f', 0, G_OPTION_ARG_STRING, &format,
272
         N_("save format [optional; defaults to 'png']"), N_("[png, pdf, ps, eps, svg, xml, recording]")},
273 274
        {"output", 'o', 0, G_OPTION_ARG_STRING, &output,
         N_("output filename [optional; defaults to stdout]"), NULL},
275 276
        {"export-id", 'i', 0, G_OPTION_ARG_STRING, &export_id,
         N_("SVG id of object to export [optional; defaults to exporting all objects]"), N_("<object id>")},
277 278
        {"keep-aspect-ratio", 'a', 0, G_OPTION_ARG_NONE, &keep_aspect_ratio,
         N_("whether to preserve the aspect ratio [optional; defaults to FALSE]"), NULL},
279 280
        {"background-color", 'b', 0, G_OPTION_ARG_STRING, &background_color_str,
         N_("set the background color [optional; defaults to None]"), N_("[black, white, #abccee, #aaa...]")},
281
        {"unlimited", 'u', 0, G_OPTION_ARG_NONE, &unlimited, N_("Allow huge SVG files"), NULL},
282 283
        {"keep-image-data", 0, 0, G_OPTION_ARG_NONE, &keep_image_data, N_("Keep image data"), NULL},
        {"no-keep-image-data", 0, 0, G_OPTION_ARG_NONE, &no_keep_image_data, N_("Don't keep image data"), NULL},
284 285 286 287 288
        {"version", 'v', 0, G_OPTION_ARG_NONE, &bVersion, N_("show version information"), NULL},
        {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &args, NULL, N_("[FILE...]")},
        {NULL}
    };

289 290
    /* Set the locale so that UTF-8 filenames work */
    setlocale(LC_ALL, "");
291

292 293 294 295
    g_option_context = g_option_context_new (_("- SVG Converter"));
    g_option_context_add_main_entries (g_option_context, options_table, NULL);
    g_option_context_set_help_enabled (g_option_context, TRUE);
    if (!g_option_context_parse (g_option_context, &argc, &argv, &error)) {
Christian Persch's avatar
Christian Persch committed
296
        g_option_context_free (g_option_context);
297 298 299 300 301 302 303 304 305 306 307 308 309 310
        display_error (error);
        exit (1);
    }

    g_option_context_free (g_option_context);

    if (bVersion != 0) {
        printf (_("rsvg-convert version %s\n"), VERSION);
        return 0;
    }

    if (output != NULL) {
        output_file = fopen (output, "wb");
        if (!output_file) {
311
            g_printerr (_("Error saving to file: %s\n"), output);
Christian Persch's avatar
Christian Persch committed
312
            g_free (output);
313 314
            exit (1);
        }
Christian Persch's avatar
Christian Persch committed
315 316

        g_free (output);
317
    }
318 319 320 321 322
#ifdef G_OS_WIN32
    else {
        setmode (fileno (stdout), O_BINARY);
    }
#endif   
323 324 325 326 327 328

    if (args)
        while (args[n_args] != NULL)
            n_args++;

    if (n_args == 0) {
329
        const gchar * const stdin_args[] = { "stdin", NULL };
330 331
        n_args = 1;
        using_stdin = TRUE;
332 333
        g_strfreev (args);
        args = g_strdupv ((gchar **) stdin_args);
334
    } else if (n_args > 1 && (!format || !(!strcmp (format, "ps") || !strcmp (format, "eps") || !strcmp (format, "pdf")))) {
335
        g_printerr (_("Multiple SVG files are only allowed for PDF and (E)PS output.\n"));
336 337 338
        exit (1);
    }

339 340 341 342 343 344 345 346
    if (dpi_x <= 0.0) {
        dpi_x = 90.0;
    }

    if (dpi_y <= 0.0) {
        dpi_y = 90.0;
    }

347 348 349 350 351
    if (format != NULL &&
        (g_str_equal (format, "ps") || g_str_equal (format, "eps") || g_str_equal (format, "pdf")) &&
        !no_keep_image_data)
        keep_image_data = TRUE;

352 353 354
    if (zoom != 1.0)
        x_zoom = y_zoom = zoom;

355 356 357
    if (unlimited)
        flags |= RSVG_HANDLE_FLAG_UNLIMITED;

358 359 360
    if (keep_image_data)
        flags |= RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA;

361
    for (i = 0; i < n_args && success; i++) {
362 363 364 365 366
        GFile *file;
        GInputStream *stream;

        if (using_stdin) {
            file = NULL;
367
#ifdef _WIN32
368 369 370 371 372 373 374 375 376
            handle = GetStdHandle (STD_INPUT_HANDLE);

            if (handle == INVALID_HANDLE_VALUE) {
              gchar *emsg = g_win32_error_message (GetLastError());
              g_printerr ( _("Unable to acquire HANDLE for STDIN: %s\n"), emsg);
              g_free (emsg);
              exit (1);
            }
            stream = g_win32_input_stream_new (handle, FALSE);
377
#else
378
            stream = g_unix_input_stream_new (STDIN_FILENO, FALSE);
379
#endif
380
        } else {
381
            file = g_file_new_for_commandline_arg (args[i]);
382
            stream = (GInputStream *) g_file_read (file, NULL, &error);
383

384 385 386 387 388
            if (stream == NULL)
                goto done;
        }

        rsvg = rsvg_handle_new_from_stream_sync (stream, file, flags, NULL, &error);
389

390 391 392
    done:
        g_clear_object (&stream);
        g_clear_object (&file);
393

394
        if (error != NULL) {
395
            g_printerr (_("Error reading SVG:"));
396
            display_error (error);
397
            g_printerr ("\n");
398 399 400
            exit (1);
        }

401 402 403 404
        g_assert (rsvg != NULL);

        rsvg_handle_set_dpi_x_y (rsvg, dpi_x, dpi_y);

405 406 407
        export_lookup_id = get_lookup_id_from_command_line (export_id);
        if (export_lookup_id != NULL
            && !rsvg_handle_has_sub (rsvg, export_lookup_id)) {
408
            g_printerr (_("File %s does not have an object with id \"%s\"\n"), args[i], export_id);
409 410
            exit (1);
        }
411 412

        if (i == 0) {
413
            SizeMode size_data;
414

415
            if (!rsvg_handle_get_dimensions_sub (rsvg, &dimensions, export_lookup_id)) {
416
                g_printerr ("Could not get dimensions for file %s\n", args[i]);
417 418
                exit (1);
            }
419

420 421 422 423 424
            if (dimensions.width == 0 || dimensions.height == 0) {
                g_printerr ("The SVG %s has no dimensions\n", args[i]);
                exit (1);
            }

425 426 427
            unscaled_width = dimensions.width;
            unscaled_height = dimensions.height;

428 429
            /* if both are unspecified, assume user wants to zoom the image in at least 1 dimension */
            if (width == -1 && height == -1) {
430
                size_data.kind = SIZE_KIND_ZOOM;
431 432 433
                size_data.x_zoom = x_zoom;
                size_data.y_zoom = y_zoom;
                size_data.keep_aspect_ratio = keep_aspect_ratio;
434
            } else if (x_zoom == 1.0 && y_zoom == 1.0) {
435 436
                /* if one parameter is unspecified, assume user wants to keep the aspect ratio */
                if (width == -1 || height == -1) {
437
                    size_data.kind = SIZE_KIND_WH_MAX;
438 439 440 441
                    size_data.width = width;
                    size_data.height = height;
                    size_data.keep_aspect_ratio = keep_aspect_ratio;
                } else {
442
                    size_data.kind = SIZE_KIND_WH;
443 444 445 446 447 448
                    size_data.width = width;
                    size_data.height = height;
                    size_data.keep_aspect_ratio = keep_aspect_ratio;
                }
            } else {
                /* assume the user wants to zoom the image, but cap the maximum size */
449
                size_data.kind = SIZE_KIND_ZOOM_MAX;
450 451 452 453 454 455 456
                size_data.x_zoom = x_zoom;
                size_data.y_zoom = y_zoom;
                size_data.width = width;
                size_data.height = height;
                size_data.keep_aspect_ratio = keep_aspect_ratio;
            }

457 458
            scaled_width = dimensions.width;
            scaled_height = dimensions.height;
459
            get_final_size (&scaled_width, &scaled_height, &size_data);
460 461 462

            if (!format || !strcmp (format, "png"))
                surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
463
                                                      scaled_width, scaled_height);
464
#ifdef CAIRO_HAS_PDF_SURFACE
465
            else if (!strcmp (format, "pdf")) {
466
                surface = cairo_pdf_surface_create_for_stream (rsvg_cairo_write_func, output_file,
467
                                                               scaled_width, scaled_height);
468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
                source_date_epoch = getenv("SOURCE_DATE_EPOCH");
                if (source_date_epoch) {
                    errno = 0;
                    epoch = strtoull(source_date_epoch, &endptr, 10);
                    if ((errno == ERANGE && (epoch == ULLONG_MAX || epoch == 0))
                            || (errno != 0 && epoch == 0)) {
                        g_printerr (_("Environment variable $SOURCE_DATE_EPOCH: strtoull: %s\n"),
                                    strerror(errno));
                        exit (1);
                    }
                    if (endptr == source_date_epoch) {
                        g_printerr (_("Environment variable $SOURCE_DATE_EPOCH: No digits were found: %s\n"),
                                    endptr);
                        exit (1);
                    }
                    if (*endptr != '\0') {
                        g_printerr (_("Environment variable $SOURCE_DATE_EPOCH: Trailing garbage: %s\n"),
                                    endptr);
                        exit (1);
                    }
                    if (epoch > ULONG_MAX) {
                        g_printerr (_("Environment variable $SOURCE_DATE_EPOCH: value must be smaller than or equal to %lu but was found to be: %llu \n"),
                                    ULONG_MAX, epoch);
                        exit (1);
                    }
                    now = (time_t) epoch;
                    build_time = gmtime(&now);
                    g_assert (strftime (buffer, sizeof (buffer), "%Y-%m-%dT%H:%M:%S%z", build_time));
                    cairo_pdf_surface_set_metadata (surface,
                                                    CAIRO_PDF_METADATA_CREATE_DATE,
                                                    buffer);
                }
            }
501 502
#endif
#ifdef CAIRO_HAS_PS_SURFACE
503
            else if (!strcmp (format, "ps") || !strcmp (format, "eps")){
504
                surface = cairo_ps_surface_create_for_stream (rsvg_cairo_write_func, output_file,
505
                                                              scaled_width, scaled_height);
506 507 508
                if(!strcmp (format, "eps"))
                    cairo_ps_surface_set_eps(surface, TRUE);
            }
509 510
#endif
#ifdef CAIRO_HAS_SVG_SURFACE
511
            else if (!strcmp (format, "svg")) {
512
                surface = cairo_svg_surface_create_for_stream (rsvg_cairo_write_func, output_file,
513
                                                               scaled_width, scaled_height);
514 515
                cairo_svg_surface_set_document_unit(surface, CAIRO_SVG_UNIT_PX);
            }
Christian Persch's avatar
Christian Persch committed
516 517 518 519 520
#endif
#ifdef CAIRO_HAS_XML_SURFACE
            else if (!strcmp (format, "xml")) {
                cairo_device_t *device = cairo_xml_create_for_stream (rsvg_cairo_write_func, output_file);
                surface = cairo_xml_surface_create (device, CAIRO_CONTENT_COLOR_ALPHA,
521
                                                    scaled_width, scaled_height);
Christian Persch's avatar
Christian Persch committed
522 523 524 525 526 527
                cairo_device_destroy (device);
            }
#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE (1, 10, 0)
            else if (!strcmp (format, "recording"))
                surface = cairo_recording_surface_create (CAIRO_CONTENT_COLOR_ALPHA, NULL);
#endif
528
#endif
529
            else {
530
                g_printerr (_("Unknown output format."));
531 532 533 534 535
                exit (1);
            }

            cr = cairo_create (surface);
        }
536

537 538
        // Set background color
        if (background_color_str && g_ascii_strcasecmp(background_color_str, "none") != 0) {
539 540
            RsvgCssColorSpec spec;

541
            spec = rsvg_css_parse_color_ (background_color_str);
542 543 544 545 546 547
            if (spec.kind == RSVG_CSS_COLOR_SPEC_ARGB) {
                background_color = spec.argb;
            } else {
                g_printerr (_("Invalid color specification."));
                exit (1);
            }
548 549 550 551 552 553

            cairo_set_source_rgb (
                cr, 
                ((background_color >> 16) & 0xff) / 255.0, 
                ((background_color >> 8) & 0xff) / 255.0, 
                ((background_color >> 0) & 0xff) / 255.0);
554
            cairo_rectangle (cr, 0, 0, scaled_width, scaled_height);
555 556 557
            cairo_fill (cr);
        }

558 559 560 561
        cairo_scale (cr,
                     scaled_width / unscaled_width,
                     scaled_height / unscaled_height);

562 563 564 565
        if (export_lookup_id) {
            RsvgPositionData pos;

            if (!rsvg_handle_get_position_sub (rsvg, &pos, export_lookup_id)) {
566
                g_printerr (_("File %s does not have an object with id \"%s\"\n"), args[i], export_id);
567 568 569 570 571 572 573
                exit (1);
            }

            /* Move the whole thing to 0, 0 so the object to export is at the origin */
            cairo_translate (cr, -pos.x, -pos.y);
        }

574 575 576 577
        if (!rsvg_handle_render_cairo_sub (rsvg, cr, export_lookup_id)) {
            g_printerr ("Could not render file %s\n", args[i]);
            exit (1);
        }
578 579

        g_free (export_lookup_id);
580

581 582
        if (!format || !strcmp (format, "png"))
            cairo_surface_write_to_png_stream (surface, rsvg_cairo_write_func, output_file);
Christian Persch's avatar
Christian Persch committed
583 584 585 586 587 588 589
#if CAIRO_HAS_XML_SURFACE && CAIRO_VERSION >= CAIRO_VERSION_ENCODE (1, 10, 0)
        else if (!strcmp (format, "recording")) {
            cairo_device_t *device = cairo_xml_create_for_stream (rsvg_cairo_write_func, output_file);
            cairo_xml_for_recording_surface (device, surface);
            cairo_device_destroy (device);
        }
#endif
590 591
        else if (!strcmp (format, "xml"))
          ;
592
        else if (!strcmp (format, "svg") || !strcmp (format, "pdf") || !strcmp (format, "ps") || !strcmp (format, "eps"))
593
            cairo_show_page (cr);
594 595
        else
          g_assert_not_reached ();
596

597
        g_object_unref (rsvg);
598
    }
599

600
    cairo_destroy (cr);
Christian Persch's avatar
Christian Persch committed
601

602
    cairo_surface_destroy (surface);
603

604
    fclose (output_file);
605

Christian Persch's avatar
Christian Persch committed
606 607
    g_strfreev (args);

608
    return 0;
609
}