From c15dc3b14e35850849f3559ac0305b4cac4a7046 Mon Sep 17 00:00:00 2001 From: Alessio Treglia Date: Mon, 12 Jul 2010 19:34:33 +0200 Subject: Imported Upstream version 2.31.5~bzr424 --- src/book.c | 614 +++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 451 insertions(+), 163 deletions(-) (limited to 'src/book.c') diff --git a/src/book.c b/src/book.c index 1c1cc6e..387c7f0 100644 --- a/src/book.c +++ b/src/book.c @@ -9,16 +9,22 @@ * license. */ +#include #include #include +#include +#include #include #include #include #include -#include // TEMP: Needed for close() in get_temporary_filename() #include "book.h" +enum { + PROP_0, + PROP_NEEDS_SAVING +}; enum { PAGE_ADDED, @@ -31,6 +37,8 @@ static guint signals[LAST_SIGNAL] = { 0, }; struct BookPrivate { GList *pages; + + gboolean needs_saving; }; G_DEFINE_TYPE (Book, book, G_TYPE_OBJECT); @@ -57,17 +65,28 @@ book_clear (Book *book) } +static void +page_changed_cb (Page *page, Book *book) +{ + book_set_needs_saving (book, TRUE); +} + + Page * book_append_page (Book *book, gint width, gint height, gint dpi, Orientation orientation) { Page *page; page = page_new (); + g_signal_connect (page, "image-changed", G_CALLBACK (page_changed_cb), book); + g_signal_connect (page, "crop-changed", G_CALLBACK (page_changed_cb), book); 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); + + book_set_needs_saving (book, TRUE); return page; } @@ -76,10 +95,14 @@ book_append_page (Book *book, gint width, gint height, gint dpi, Orientation ori void book_delete_page (Book *book, Page *page) { + g_signal_handlers_disconnect_by_func (page, page_changed_cb, book); + g_signal_emit (book, signals[PAGE_REMOVED], 0, page); book->priv->pages = g_list_remove (book->priv->pages, page); g_object_unref (page); + + book_set_needs_saving (book, TRUE); } @@ -218,160 +241,148 @@ book_save_ps (Book *book, GFile *file, GError **error) } -// TEMP: Copied from simple-scan.c -static GFile * -get_temporary_file (const gchar *prefix, const gchar *extension) +typedef struct { - gint fd; - GFile *file; - gchar *filename, *path; - GError *error = NULL; + int offset; + int n_objects; + GList *object_offsets; + GFileOutputStream *stream; +} PDFWriter; - /* 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); +static PDFWriter * +pdf_writer_new (GFileOutputStream *stream) +{ + PDFWriter *writer; + writer = g_malloc0 (sizeof (PDFWriter)); + writer->stream = g_object_ref (stream); + return writer; +} - return file; + +static void +pdf_writer_free (PDFWriter *writer) +{ + g_object_unref (writer->stream); + g_list_free (writer->object_offsets); + g_free (writer); } -static goffset -get_file_size (GFile *file) +static void +pdf_write (PDFWriter *writer, const unsigned char *data, size_t length) { - 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); - } + g_output_stream_write_all (G_OUTPUT_STREAM (writer->stream), data, length, NULL, NULL, NULL); + writer->offset += length; +} + - return size; +static void +pdf_printf (PDFWriter *writer, const char *format, ...) +{ + va_list args; + gchar *string; + + va_start (args, format); + string = g_strdup_vprintf (format, args); + va_end (args); + pdf_write (writer, (unsigned char *)string, strlen (string)); + + g_free (string); } -static gboolean -book_save_pdf_with_imagemagick (Book *book, GFile *file, GError **error) +static int +pdf_start_object (PDFWriter *writer) { - GList *iter; - GString *command_line; - gboolean result = TRUE; - gint exit_status = 0; - GFile *output_file = NULL; - GList *link, *temporary_files = NULL; + writer->n_objects++; + writer->object_offsets = g_list_append (writer->object_offsets, GINT_TO_POINTER (writer->offset)); + return writer->n_objects; +} - /* 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, *resolution_command, *stdout_text = NULL, *stderr_text = NULL; - 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); - - resolution_command = g_strdup_printf ("convert %s -density %d %s", path, page_get_dpi (page), path); - g_debug ("Executing ImageMagick command: %s", resolution_command); - result = g_spawn_command_line_sync (resolution_command, &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, resolution_command); - 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\n" - "\n" - "Command line: %s\n" - "Stdout: %s\n" - "Stderr: %s", - exit_status, resolution_command, stdout_text, stderr_text); - } - if (!result) +static guchar * +compress_zlib (guchar *data, size_t length, size_t *n_written) +{ + z_stream stream; + guchar *out_data; + + out_data = g_malloc (sizeof (guchar) * length); + + stream.zalloc = Z_NULL; + stream.zfree = Z_NULL; + stream.opaque = Z_NULL; + if (deflateInit (&stream, Z_BEST_COMPRESSION) != Z_OK) + return NULL; + + stream.next_in = data; + stream.avail_in = length; + stream.next_out = out_data; + stream.avail_out = length; + while (stream.avail_in > 0) { + if (deflate (&stream, Z_FINISH) == Z_STREAM_ERROR) break; - - 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); - - g_debug ("Executing ImageMagick command: %s", command_line->str); - 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\n" - "\n" - "Command line: %s\n" - "Stdout: %s\n" - "Stderr: %s", - exit_status, command_line->str, stdout_text, stderr_text); - } - g_free (stdout_text); - g_free (stderr_text); + deflateEnd (&stream); + + if (stream.avail_in > 0) { + g_free (out_data); + return NULL; } - /* Move to target URI */ - if (result) - result = g_file_move (output_file, file, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error); - - /* Delete page files */ - for (link = temporary_files; link; link = link->next) { - GFile *f = link->data; + *n_written = length - stream.avail_out; + + return out_data; +} - g_file_delete (f, NULL, NULL); - g_object_unref (f); + +static void jpeg_init_cb (struct jpeg_compress_struct *info) {} +static boolean jpeg_empty_cb (struct jpeg_compress_struct *info) { return TRUE; } +static void jpeg_term_cb (struct jpeg_compress_struct *info) {} + +static guchar * +compress_jpeg (GdkPixbuf *image, size_t *n_written) +{ + struct jpeg_compress_struct info; + struct jpeg_error_mgr jerr; + struct jpeg_destination_mgr dest_mgr; + int r; + guchar *pixels; + guchar *data; + size_t max_length; + + info.err = jpeg_std_error (&jerr); + jpeg_create_compress (&info); + + pixels = gdk_pixbuf_get_pixels (image); + info.image_width = gdk_pixbuf_get_width (image); + info.image_height = gdk_pixbuf_get_height (image); + info.input_components = 3; + info.in_color_space = JCS_RGB; /* TODO: JCS_GRAYSCALE? */ + jpeg_set_defaults (&info); + + max_length = info.image_width * info.image_height * info.input_components; + data = g_malloc (sizeof (guchar) * max_length); + dest_mgr.next_output_byte = data; + dest_mgr.free_in_buffer = max_length; + dest_mgr.init_destination = jpeg_init_cb; + dest_mgr.empty_output_buffer = jpeg_empty_cb; + dest_mgr.term_destination = jpeg_term_cb; + info.dest = &dest_mgr; + + jpeg_start_compress (&info, TRUE); + for (r = 0; r < info.image_height; r++) { + JSAMPROW row[1]; + row[0] = pixels + r * gdk_pixbuf_get_rowstride (image); + jpeg_write_scanlines (&info, row, 1); } - g_list_free (temporary_files); + jpeg_finish_compress (&info); + *n_written = max_length - dest_mgr.free_in_buffer; - if (output_file) - g_object_unref (output_file); - g_string_free (command_line, TRUE); + jpeg_destroy_compress (&info); - return result; + return data; } @@ -379,43 +390,249 @@ 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); - } + PDFWriter *writer; + int catalog_number, pages_number, info_number; + int xref_offset; + int i; 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); + writer = pdf_writer_new (stream); + g_object_unref (stream); - for (iter = book->priv->pages; iter; iter = iter->next) { - Page *page = iter->data; - double width, height; + /* Header */ + pdf_printf (writer, "%%PDF-1.3\n"); + + /* Catalog */ + catalog_number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", catalog_number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Type /Catalog\n"); + pdf_printf (writer, "/Pages %d 0 R\n", catalog_number + 1); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "endobj\n"); + + /* Pages */ + pdf_printf (writer, "\n"); + pages_number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", pages_number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Type /Pages\n"); + pdf_printf (writer, "/Kids ["); + for (i = 0; i < book_get_n_pages (book); i++) { + pdf_printf (writer, " %d 0 R", pages_number + 1 + (i*3)); + } + pdf_printf (writer, " ]\n"); + pdf_printf (writer, "/Count %d\n", book_get_n_pages (book)); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "endobj\n"); + + for (i = 0; i < book_get_n_pages (book); i++) { + int number, width, height, depth; + size_t data_length, compressed_length; + Page *page; GdkPixbuf *image; - + guchar *pixels, *data, *compressed_data; + gchar *command; + const gchar *color_space, *filter = NULL; + float page_width, page_height; + + page = book_get_page (book, i); + width = page_get_width (page); + height = page_get_height (page); + page_width = width * 72. / page_get_dpi (page); + page_height = height * 72. / page_get_dpi (page); image = page_get_cropped_image (page); + pixels = gdk_pixbuf_get_pixels (image); + + if (page_is_color (page)) { + int row; + + depth = 8; + color_space = "DeviceRGB"; + data_length = height * width * 3 + 1; + data = g_malloc (sizeof (guchar) * data_length); + for (row = 0; row < height; row++) { + int x; + guchar *in_line, *out_line; + + in_line = pixels + row * gdk_pixbuf_get_rowstride (image); + out_line = data + row * width * 3; + for (x = 0; x < width; x++) { + guchar *in_p = in_line + x*3; + guchar *out_p = out_line + x*3; + + out_p[0] = in_p[0]; + out_p[1] = in_p[1]; + out_p[2] = in_p[2]; + } + } + } + else if (page_get_depth (page) == 1) { + int row, offset = 7; + guchar *write_ptr; + + depth = 1; + color_space = "DeviceGray"; + data_length = (height * width + 7) / 8; + data = g_malloc (sizeof (guchar) * data_length); + write_ptr = data; + write_ptr[0] = 0; + for (row = 0; row < height; row++) { + int x; + guchar *in_line; + + in_line = pixels + row * gdk_pixbuf_get_rowstride (image); + for (x = 0; x < width; x++) { + guchar *in_p = in_line + x*3; + if (in_p[0]) + write_ptr[0] |= 1 << offset; + offset--; + if (offset < 0) { + write_ptr++; + write_ptr[0] = 0; + offset = 7; + } + } + } + } + else { + int row; + + depth = 8; + color_space = "DeviceGray"; + data_length = height * width + 1; + data = g_malloc (sizeof (guchar) * data_length); + for (row = 0; row < height; row++) { + int x; + guchar *in_line, *out_line; + + in_line = pixels + row * gdk_pixbuf_get_rowstride (image); + out_line = data + row * width; + for (x = 0; x < width; x++) { + guchar *in_p = in_line + x*3; + guchar *out_p = out_line + x; + + out_p[0] = in_p[0]; + } + } + } - 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); - + /* Compress data */ + compressed_data = compress_zlib (data, data_length, &compressed_length); + if (compressed_data) { + /* Try if JPEG compression is better */ + if (depth > 1) { + guchar *jpeg_data; + size_t jpeg_length; + + jpeg_data = compress_jpeg (image, &jpeg_length); + if (jpeg_length < compressed_length) { + filter = "DCTDecode"; + g_free (data); + g_free (compressed_data); + data = jpeg_data; + data_length = jpeg_length; + } + } + + if (!filter) { + filter = "FlateDecode"; + g_free (data); + data = compressed_data; + data_length = compressed_length; + } + } + + /* Page */ + pdf_printf (writer, "\n"); + number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Type /Page\n"); + pdf_printf (writer, "/Parent %d 0 R\n", pages_number); + pdf_printf (writer, "/Resources << /XObject << /Im%d %d 0 R >> >>\n", i, number+1); + pdf_printf (writer, "/MediaBox [ 0 0 %.2f %.2f ]\n", page_width, page_height); + pdf_printf (writer, "/Contents %d 0 R\n", number+2); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "endobj\n"); + + /* Page image */ + pdf_printf (writer, "\n"); + number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Type /XObject\n"); + pdf_printf (writer, "/Subtype /Image\n"); + pdf_printf (writer, "/Width %d\n", width); + pdf_printf (writer, "/Height %d\n", height); + pdf_printf (writer, "/ColorSpace /%s\n", color_space); + pdf_printf (writer, "/BitsPerComponent %d\n", depth); + pdf_printf (writer, "/Length %d\n", data_length); + if (filter) + pdf_printf (writer, "/Filter /%s\n", filter); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "stream\n"); + pdf_write (writer, data, data_length); + g_free (data); + pdf_printf (writer, "\n"); + pdf_printf (writer, "endstream\n"); + pdf_printf (writer, "endobj\n"); + + /* Page contents */ + command = g_strdup_printf ("q\n" + "%d 0 0 %d 0 0 cm\n" + "/Im%d Do\n" + "Q", width, height, i); + pdf_printf (writer, "\n"); + number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Length %d\n", strlen (command) + 1); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "stream\n"); + pdf_write (writer, (unsigned char *)command, strlen (command)); + pdf_printf (writer, "\n"); + pdf_printf (writer, "endstream\n"); + pdf_printf (writer, "endobj\n"); + g_free (command); + g_object_unref (image); } + + /* Info */ + pdf_printf (writer, "\n"); + info_number = pdf_start_object (writer); + pdf_printf (writer, "%d 0 obj\n", info_number); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Creator (Simple Scan " VERSION ")\n"); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "endobj\n"); + + /* Cross-reference table */ + xref_offset = writer->offset; + pdf_printf (writer, "xref\n"); + pdf_printf (writer, "1 %d\n", writer->n_objects); + GList *link; + for (link = writer->object_offsets; link != NULL; link = link->next) { + int offset = GPOINTER_TO_INT (link->data); + pdf_printf (writer, "%010d 0000 n\n", offset); + } - cairo_surface_destroy (surface); - - g_object_unref (stream); + /* Trailer */ + pdf_printf (writer, "trailer\n"); + pdf_printf (writer, "<<\n"); + pdf_printf (writer, "/Size %d\n", writer->n_objects); + pdf_printf (writer, "/Info %d 0 R\n", info_number); + pdf_printf (writer, "/Root %d 0 R\n", catalog_number); + pdf_printf (writer, ">>\n"); + pdf_printf (writer, "startxref\n"); + pdf_printf (writer, "%d\n", xref_offset); + pdf_printf (writer, "%%%%EOF\n"); + + pdf_writer_free (writer); return TRUE; } @@ -424,18 +641,79 @@ book_save_pdf (Book *book, GFile *file, GError **error) gboolean book_save (Book *book, const gchar *type, GFile *file, GError **error) { + gboolean result = FALSE; + if (strcmp (type, "jpeg") == 0) - return book_save_multi_file (book, "jpeg", file, error); + result = book_save_multi_file (book, "jpeg", file, error); else if (strcmp (type, "png") == 0) - return book_save_multi_file (book, "png", file, error); + result = book_save_multi_file (book, "png", file, error); else if (strcmp (type, "tiff") == 0) - return book_save_multi_file (book, "tiff", file, error); + result = book_save_multi_file (book, "tiff", file, error); else if (strcmp (type, "ps") == 0) - return book_save_ps (book, file, error); + result = book_save_ps (book, file, error); else if (strcmp (type, "pdf") == 0) - return book_save_pdf (book, file, error); - else - return FALSE; + result = book_save_pdf (book, file, error); + + return result; +} + + +void +book_set_needs_saving (Book *book, gboolean needs_saving) +{ + gboolean needed_saving = book->priv->needs_saving; + book->priv->needs_saving = needs_saving; + if (needed_saving != needs_saving) + g_object_notify (G_OBJECT (book), "needs-saving"); +} + + +gboolean +book_get_needs_saving (Book *book) +{ + return book->priv->needs_saving; +} + + +static void +book_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + Book *self; + + self = BOOK (object); + + switch (prop_id) { + case PROP_NEEDS_SAVING: + book_set_needs_saving (self, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +static void +book_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + Book *self; + + self = BOOK (object); + + switch (prop_id) { + case PROP_NEEDS_SAVING: + g_value_set_boolean (value, book_get_needs_saving (self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } } @@ -453,8 +731,18 @@ book_class_init (BookClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + object_class->get_property = book_get_property; + object_class->set_property = book_set_property; object_class->finalize = book_finalize; + g_object_class_install_property (object_class, + PROP_NEEDS_SAVING, + g_param_spec_boolean ("needs-saving", + "needs-saving", + "TRUE if this book needs saving", + FALSE, + G_PARAM_READWRITE)); + signals[PAGE_ADDED] = g_signal_new ("page-added", G_TYPE_FROM_CLASS (klass), -- cgit v1.2.3