diff options
Diffstat (limited to 'src/book.c')
-rw-r--r-- | src/book.c | 466 |
1 files changed, 466 insertions, 0 deletions
diff --git a/src/book.c b/src/book.c new file mode 100644 index 0000000..6bf4d0a --- /dev/null +++ b/src/book.c @@ -0,0 +1,466 @@ +/* + * Copyright (C) 2009 Canonical Ltd. + * Author: Robert Ancell <robert.ancell@canonical.com> + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include <string.h> +#include <math.h> +#include <gdk/gdk.h> +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <cairo/cairo-pdf.h> +#include <cairo/cairo-ps.h> +#include <unistd.h> // TEMP: Needed for close() in get_temporary_filename() + +#include "book.h" + + +enum { + PAGE_ADDED, + PAGE_REMOVED, + CLEARED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +struct BookPrivate +{ + GList *pages; +}; + +G_DEFINE_TYPE (Book, book, G_TYPE_OBJECT); + + +Book * +book_new () +{ + return g_object_new (BOOK_TYPE, NULL); +} + + +void +book_clear (Book *book) +{ + GList *iter; + for (iter = book->priv->pages; iter; iter = iter->next) { + Page *page = iter->data; + g_object_unref (page); + } + g_list_free (book->priv->pages); + book->priv->pages = NULL; + g_signal_emit (book, signals[CLEARED], 0); +} + + +Page * +book_append_page (Book *book, gint width, gint height, gint dpi, Orientation orientation) +{ + Page *page; + + page = page_new (); + page_setup (page, width, height, dpi, orientation); + + book->priv->pages = g_list_append (book->priv->pages, page); + + g_signal_emit (book, signals[PAGE_ADDED], 0, page); + + return page; +} + + +void +book_delete_page (Book *book, Page *page) +{ + g_signal_emit (book, signals[PAGE_REMOVED], 0, page); + + book->priv->pages = g_list_remove (book->priv->pages, page); + g_object_unref (page); +} + + +gint +book_get_n_pages (Book *book) +{ + return g_list_length (book->priv->pages); +} + + +Page * +book_get_page (Book *book, gint page_number) +{ + if (page_number < 0) + page_number = g_list_length (book->priv->pages) + page_number; + return g_list_nth_data (book->priv->pages, page_number); +} + + +static GFile * +make_indexed_file (const gchar *uri, gint i) +{ + gchar *basename, *suffix, *indexed_uri; + GFile *file; + + if (i == 0) + return g_file_new_for_uri (uri); + + basename = g_path_get_basename (uri); + suffix = g_strrstr (basename, "."); + + if (suffix) + indexed_uri = g_strdup_printf ("%.*s-%d%s", (int) (strlen (uri) - strlen (suffix)), uri, i, suffix); + else + indexed_uri = g_strdup_printf ("%s-%d", uri, i); + g_free (basename); + + file = g_file_new_for_uri (indexed_uri); + g_free (indexed_uri); + + return file; +} + + +static gboolean +book_save_multi_file (Book *book, const gchar *type, GFile *file, GError **error) +{ + GList *iter; + gboolean result = TRUE; + gint i; + gchar *uri; + + uri = g_file_get_uri (file); + for (iter = book->priv->pages, i = 0; iter && result; iter = iter->next, i++) { + Page *page = iter->data; + GFile *file; + + file = make_indexed_file (uri, i); + result = page_save (page, type, file, error); + g_object_unref (file); + } + g_free (uri); + + return result; +} + + +static void +save_ps_pdf_surface (cairo_surface_t *surface, GdkPixbuf *image, gdouble dpi) +{ + cairo_t *context; + + context = cairo_create (surface); + + cairo_scale (context, 72.0 / dpi, 72.0 / dpi); + gdk_cairo_set_source_pixbuf (context, image, 0, 0); + cairo_pattern_set_filter (cairo_get_source (context), CAIRO_FILTER_BEST); + cairo_paint (context); + + cairo_destroy (context); +} + + +static cairo_status_t +write_cairo_data (GFileOutputStream *stream, unsigned char *data, unsigned int length) +{ + gboolean result; + GError *error = NULL; + + result = g_output_stream_write_all (G_OUTPUT_STREAM (stream), data, length, NULL, NULL, &error); + + if (error) { + g_warning ("Error writing data: %s", error->message); + g_error_free (error); + } + + return result ? CAIRO_STATUS_SUCCESS : CAIRO_STATUS_WRITE_ERROR; +} + + +static gboolean +book_save_ps (Book *book, GFile *file, GError **error) +{ + GFileOutputStream *stream; + GList *iter; + cairo_surface_t *surface; + + stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); + if (!stream) + return FALSE; + + surface = cairo_ps_surface_create_for_stream ((cairo_write_func_t) write_cairo_data, + stream, 0, 0); + + for (iter = book->priv->pages; iter; iter = iter->next) { + Page *page = iter->data; + double width, height; + GdkPixbuf *image; + + image = page_get_cropped_image (page); + + width = gdk_pixbuf_get_width (image) * 72.0 / page_get_dpi (page); + height = gdk_pixbuf_get_height (image) * 72.0 / page_get_dpi (page); + cairo_ps_surface_set_size (surface, width, height); + save_ps_pdf_surface (surface, image, page_get_dpi (page)); + cairo_surface_show_page (surface); + + g_object_unref (image); + } + + cairo_surface_destroy (surface); + + g_object_unref (stream); + + return TRUE; +} + + +// TEMP: Copied from simple-scan.c +static GFile * +get_temporary_file (const gchar *prefix, const gchar *extension) +{ + gint fd; + GFile *file; + gchar *filename, *path; + GError *error = NULL; + + /* NOTE: I'm not sure if this is a 100% safe strategy to use g_file_open_tmp(), close and + * use the filename but it appears to work in practise */ + + filename = g_strdup_printf ("%s-XXXXXX.%s", prefix, extension); + fd = g_file_open_tmp (filename, &path, &error); + g_free (filename); + if (fd < 0) { + g_warning ("Error saving email attachment: %s", error->message); + g_clear_error (&error); + return NULL; + } + close (fd); + + file = g_file_new_for_path (path); + g_free (path); + + return file; +} + + +static goffset +get_file_size (GFile *file) +{ + GFileInfo *info; + goffset size = 0; + + info = g_file_query_info (file, + G_FILE_ATTRIBUTE_STANDARD_SIZE, + G_FILE_QUERY_INFO_NONE, + NULL, + NULL); + if (info) { + size = g_file_info_get_size (info); + g_object_unref (info); + } + + return size; +} + + +static gboolean +book_save_pdf_with_imagemagick (Book *book, GFile *file, GError **error) +{ + GList *iter; + GString *command_line; + gboolean result = TRUE; + gint exit_status = 0; + GFile *output_file = NULL; + GList *link, *temporary_files = NULL; + + /* ImageMagick command to create a PDF */ + command_line = g_string_new ("convert -adjoin"); + + /* Save each page to a file */ + for (iter = book->priv->pages; iter && result; iter = iter->next) { + Page *page = iter->data; + GFile *jpeg_file, *tiff_file; + gchar *path; + gint jpeg_size, tiff_size; + + jpeg_file = get_temporary_file ("simple-scan", "jpg"); + result = page_save (page, "jpeg", jpeg_file, error); + jpeg_size = get_file_size (jpeg_file); + temporary_files = g_list_append (temporary_files, jpeg_file); + + tiff_file = get_temporary_file ("simple-scan", "tiff"); + result = page_save (page, "tiff", tiff_file, error); + tiff_size = get_file_size (tiff_file); + temporary_files = g_list_append (temporary_files, tiff_file); + + /* Use the smallest file */ + if (jpeg_size < tiff_size) + path = g_file_get_path (jpeg_file); + else + path = g_file_get_path (tiff_file); + g_string_append_printf (command_line, " %s", path); + g_free (path); + } + + /* Use ImageMagick command to create a PDF */ + if (result) { + gchar *path, *stdout_text = NULL, *stderr_text = NULL; + + output_file = get_temporary_file ("simple-scan", "pdf"); + path = g_file_get_path (output_file); + g_string_append_printf (command_line, " %s", path); + g_free (path); + + result = g_spawn_command_line_sync (command_line->str, &stdout_text, &stderr_text, &exit_status, error); + if (result && exit_status != 0) { + g_warning ("ImageMagick returned error code %d, command line was: %s", exit_status, command_line->str); + g_warning ("stdout: %s", stdout_text); + g_warning ("stderr: %s", stderr_text); + result = FALSE; + } + g_free (stdout_text); + g_free (stderr_text); + } + + /* Move to target URI */ + if (result) { + GFile *dest; + result = g_file_move (output_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error); + g_object_unref (dest); + } + + /* Delete page files */ + for (link = temporary_files; link; link = link->next) { + GFile *f = link->data; + + g_file_delete (f, NULL, NULL); + g_object_unref (f); + } + g_list_free (temporary_files); + + if (output_file) + g_object_unref (output_file); + g_string_free (command_line, TRUE); + + return result; +} + + +static gboolean +book_save_pdf (Book *book, GFile *file, GError **error) +{ + GFileOutputStream *stream; + GList *iter; + cairo_surface_t *surface; + gchar *imagemagick_executable; + + /* Use ImageMagick if it is available as then we can compress the images */ + imagemagick_executable = g_find_program_in_path ("convert"); + if (imagemagick_executable) { + g_free (imagemagick_executable); + return book_save_pdf_with_imagemagick (book, file, error); + } + + stream = g_file_replace (file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, error); + if (!stream) + return FALSE; + + surface = cairo_pdf_surface_create_for_stream ((cairo_write_func_t) write_cairo_data, + stream, 0, 0); + + for (iter = book->priv->pages; iter; iter = iter->next) { + Page *page = iter->data; + double width, height; + GdkPixbuf *image; + + image = page_get_cropped_image (page); + + width = gdk_pixbuf_get_width (image) * 72.0 / page_get_dpi (page); + height = gdk_pixbuf_get_height (image) * 72.0 / page_get_dpi (page); + cairo_pdf_surface_set_size (surface, width, height); + save_ps_pdf_surface (surface, image, page_get_dpi (page)); + cairo_surface_show_page (surface); + + g_object_unref (image); + } + + cairo_surface_destroy (surface); + + g_object_unref (stream); + + return TRUE; +} + + +gboolean +book_save (Book *book, const gchar *type, GFile *file, GError **error) +{ + if (strcmp (type, "jpeg") == 0) + return book_save_multi_file (book, "jpeg", file, error); + else if (strcmp (type, "png") == 0) + return book_save_multi_file (book, "png", file, error); + else if (strcmp (type, "tiff") == 0) + return book_save_multi_file (book, "tiff", file, error); + else if (strcmp (type, "ps") == 0) + return book_save_ps (book, file, error); + else if (strcmp (type, "pdf") == 0) + return book_save_pdf (book, file, error); + else + return FALSE; +} + + +static void +book_finalize (GObject *object) +{ + Book *book = BOOK (object); + book_clear (book); + G_OBJECT_CLASS (book_parent_class)->finalize (object); +} + + +static void +book_class_init (BookClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = book_finalize; + + signals[PAGE_ADDED] = + g_signal_new ("page-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BookClass, page_added), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[PAGE_REMOVED] = + g_signal_new ("page-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BookClass, page_removed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[CLEARED] = + g_signal_new ("cleared", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (BookClass, cleared), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + g_type_class_add_private (klass, sizeof (BookPrivate)); +} + + +static void +book_init (Book *book) +{ + book->priv = G_TYPE_INSTANCE_GET_PRIVATE (book, BOOK_TYPE, BookPrivate); +} |