/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set sw=4 sts=4 ts=4 expandtab: */ /* rsvg-convert.c: Command line utility for exercising rsvg with cairo. Copyright (C) 2005 Red Hat, Inc. Copyright (C) 2005 Dom Lachowicz Copyright (C) 2005 Caleb Moore This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Authors: Carl Worth , Caleb Moore , Dom Lachowicz */ #include "config.h" #include #include #include #include #include #include #ifdef G_OS_UNIX #include #endif #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN #include #include #endif #include "rsvg-css.h" #include "rsvg.h" #include "rsvg-compat.h" #include "rsvg-size-callback.h" #ifdef CAIRO_HAS_PS_SURFACE #include #endif #ifdef CAIRO_HAS_PDF_SURFACE #include #endif #ifdef CAIRO_HAS_SVG_SURFACE #include #endif #ifdef CAIRO_HAS_XML_SURFACE #include #endif static void display_error (GError * err) { if (err) { g_printerr ("%s\n", err->message); g_error_free (err); } } static cairo_status_t rsvg_cairo_write_func (void *closure, const unsigned char *data, unsigned int length) { if (fwrite (data, 1, length, (FILE *) closure) == length) return CAIRO_STATUS_SUCCESS; return CAIRO_STATUS_WRITE_ERROR; } 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 { /* rsvg_handle_has_sub() and rsvg_defs_lookup() expect ids to have a * '#' prepended to them, so they 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. */ export_lookup_id = g_strdup_printf ("#%s", lookup_id); } return export_lookup_id; } int main (int argc, char **argv) { 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; char *export_id = NULL; int keep_aspect_ratio = FALSE; guint32 background_color = 0; char *background_color_str = NULL; gboolean using_stdin = FALSE; gboolean unlimited = FALSE; gboolean keep_image_data = FALSE; gboolean no_keep_image_data = FALSE; GError *error = NULL; int i; char **args = NULL; gint n_args = 0; RsvgHandle *rsvg = NULL; cairo_surface_t *surface = NULL; cairo_t *cr = NULL; RsvgHandleFlags flags = RSVG_HANDLE_FLAGS_NONE; RsvgDimensionData dimensions; FILE *output_file = stdout; char *export_lookup_id; double unscaled_width, unscaled_height; int scaled_width, scaled_height; #ifdef G_OS_WIN32 HANDLE handle; #endif GOptionEntry options_table[] = { {"dpi-x", 'd', 0, G_OPTION_ARG_DOUBLE, &dpi_x, N_("pixels per inch [optional; defaults to 90dpi]"), N_("")}, {"dpi-y", 'p', 0, G_OPTION_ARG_DOUBLE, &dpi_y, N_("pixels per inch [optional; defaults to 90dpi]"), N_("")}, {"x-zoom", 'x', 0, G_OPTION_ARG_DOUBLE, &x_zoom, N_("x zoom factor [optional; defaults to 1.0]"), N_("")}, {"y-zoom", 'y', 0, G_OPTION_ARG_DOUBLE, &y_zoom, N_("y zoom factor [optional; defaults to 1.0]"), N_("")}, {"zoom", 'z', 0, G_OPTION_ARG_DOUBLE, &zoom, N_("zoom factor [optional; defaults to 1.0]"), N_("")}, {"width", 'w', 0, G_OPTION_ARG_INT, &width, N_("width [optional; defaults to the SVG's width]"), N_("")}, {"height", 'h', 0, G_OPTION_ARG_INT, &height, N_("height [optional; defaults to the SVG's height]"), N_("")}, {"format", 'f', 0, G_OPTION_ARG_STRING, &format, N_("save format [optional; defaults to 'png']"), N_("[png, pdf, ps, eps, svg, xml, recording]")}, {"output", 'o', 0, G_OPTION_ARG_STRING, &output, N_("output filename [optional; defaults to stdout]"), NULL}, {"export-id", 'i', 0, G_OPTION_ARG_STRING, &export_id, N_("SVG id of object to export [optional; defaults to exporting all objects]"), N_("")}, {"keep-aspect-ratio", 'a', 0, G_OPTION_ARG_NONE, &keep_aspect_ratio, N_("whether to preserve the aspect ratio [optional; defaults to FALSE]"), NULL}, {"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...]")}, {"unlimited", 'u', 0, G_OPTION_ARG_NONE, &unlimited, N_("Allow huge SVG files"), NULL}, {"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}, {"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} }; /* Set the locale so that UTF-8 filenames work */ setlocale(LC_ALL, ""); RSVG_G_TYPE_INIT; 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)) { g_option_context_free (g_option_context); 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) { g_printerr (_("Error saving to file: %s\n"), output); g_free (output); exit (1); } g_free (output); } if (args) while (args[n_args] != NULL) n_args++; if (n_args == 0) { n_args = 1; using_stdin = TRUE; } else if (n_args > 1 && (!format || !(!strcmp (format, "ps") || !strcmp (format, "eps") || !strcmp (format, "pdf")))) { g_printerr (_("Multiple SVG files are only allowed for PDF and (E)PS output.\n")); exit (1); } 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; if (zoom != 1.0) x_zoom = y_zoom = zoom; rsvg_set_default_dpi_x_y (dpi_x, dpi_y); if (unlimited) flags |= RSVG_HANDLE_FLAG_UNLIMITED; if (keep_image_data) flags |= RSVG_HANDLE_FLAG_KEEP_IMAGE_DATA; for (i = 0; i < n_args; i++) { GFile *file; GInputStream *stream; if (using_stdin) { file = NULL; #ifdef _WIN32 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); #else stream = g_unix_input_stream_new (STDIN_FILENO, FALSE); #endif } else { GFileInfo *file_info; gboolean compressed = FALSE; file = g_file_new_for_commandline_arg (args[i]); stream = (GInputStream *) g_file_read (file, NULL, &error); if ((file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, G_FILE_QUERY_INFO_NONE, NULL, NULL))) { const char *content_type; char *gz_content_type; content_type = g_file_info_get_content_type (file_info); gz_content_type = g_content_type_from_mime_type ("application/x-gzip"); compressed = (content_type != NULL && g_content_type_is_a (content_type, gz_content_type)); g_free (gz_content_type); g_object_unref (file_info); } if (compressed) { GZlibDecompressor *decompressor; GInputStream *converter_stream; decompressor = g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP); converter_stream = g_converter_input_stream_new (stream, G_CONVERTER (decompressor)); g_object_unref (stream); stream = converter_stream; } if (stream == NULL) goto done; } rsvg = rsvg_handle_new_from_stream_sync (stream, file, flags, NULL, &error); done: g_clear_object (&stream); g_clear_object (&file); if (error != NULL) { g_printerr (_("Error reading SVG:")); display_error (error); g_printerr ("\n"); exit (1); } export_lookup_id = get_lookup_id_from_command_line (export_id); if (export_lookup_id != NULL && !rsvg_handle_has_sub (rsvg, export_lookup_id)) { g_printerr (_("File %s does not have an object with id \"%s\"\n"), args[i], export_id); exit (1); } if (i == 0) { struct RsvgSizeCallbackData size_data; if (!rsvg_handle_get_dimensions_sub (rsvg, &dimensions, export_lookup_id)) g_printerr ("Could not get dimensions for file %s\n", args[i]); unscaled_width = dimensions.width; unscaled_height = dimensions.height; /* if both are unspecified, assume user wants to zoom the image in at least 1 dimension */ if (width == -1 && height == -1) { size_data.type = RSVG_SIZE_ZOOM; size_data.x_zoom = x_zoom; size_data.y_zoom = y_zoom; size_data.keep_aspect_ratio = keep_aspect_ratio; } else if (x_zoom == 1.0 && y_zoom == 1.0) { /* if one parameter is unspecified, assume user wants to keep the aspect ratio */ if (width == -1 || height == -1) { size_data.type = RSVG_SIZE_WH_MAX; size_data.width = width; size_data.height = height; size_data.keep_aspect_ratio = keep_aspect_ratio; } else { size_data.type = RSVG_SIZE_WH; 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 */ size_data.type = RSVG_SIZE_ZOOM_MAX; 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; } scaled_width = dimensions.width; scaled_height = dimensions.height; _rsvg_size_callback (&scaled_width, &scaled_height, &size_data); if (!format || !strcmp (format, "png")) surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, scaled_width, scaled_height); #ifdef CAIRO_HAS_PDF_SURFACE else if (!strcmp (format, "pdf")) surface = cairo_pdf_surface_create_for_stream (rsvg_cairo_write_func, output_file, scaled_width, scaled_height); #endif #ifdef CAIRO_HAS_PS_SURFACE else if (!strcmp (format, "ps") || !strcmp (format, "eps")){ surface = cairo_ps_surface_create_for_stream (rsvg_cairo_write_func, output_file, scaled_width, scaled_height); if(!strcmp (format, "eps")) cairo_ps_surface_set_eps(surface, TRUE); } #endif #ifdef CAIRO_HAS_SVG_SURFACE else if (!strcmp (format, "svg")) surface = cairo_svg_surface_create_for_stream (rsvg_cairo_write_func, output_file, scaled_width, scaled_height); #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, scaled_width, scaled_height); 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 #endif else { g_printerr (_("Unknown output format.")); exit (1); } cr = cairo_create (surface); } // Set background color if (background_color_str && g_ascii_strcasecmp(background_color_str, "none") != 0) { background_color = rsvg_css_parse_color(background_color_str, FALSE); cairo_set_source_rgb ( cr, ((background_color >> 16) & 0xff) / 255.0, ((background_color >> 8) & 0xff) / 255.0, ((background_color >> 0) & 0xff) / 255.0); cairo_rectangle (cr, 0, 0, scaled_width, scaled_height); cairo_fill (cr); } if (export_lookup_id) { RsvgPositionData pos; if (!rsvg_handle_get_position_sub (rsvg, &pos, export_lookup_id)) { g_printerr (_("File %s does not have an object with id \"%s\"\n"), args[i], export_id); 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); } cairo_scale (cr, scaled_width / unscaled_width, scaled_height / unscaled_height); rsvg_handle_render_cairo_sub (rsvg, cr, export_lookup_id); g_free (export_lookup_id); if (!format || !strcmp (format, "png")) cairo_surface_write_to_png_stream (surface, rsvg_cairo_write_func, output_file); #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 else if (!strcmp (format, "xml")) ; else if (!strcmp (format, "svg") || !strcmp (format, "pdf") || !strcmp (format, "ps") || !strcmp (format, "eps")) cairo_show_page (cr); else g_assert_not_reached (); g_object_unref (rsvg); } cairo_destroy (cr); cairo_surface_destroy (surface); fclose (output_file); g_strfreev (args); rsvg_cleanup (); return 0; }