/* * Copyright (C) 2009 Canonical Ltd. * Author: Robert Ancell * * 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 #include #include #include #include #include #include // 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_set_error (error, BOOK_TYPE, 0, "ImageMagick returned error code %d, command line was: %s", exit_status, command_line->str); } 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); }