Evince
Evince is a document viewer capable of displaying multiple and single page document formats like PDF and Postscript.
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
comics-document.c
Go to the documentation of this file.
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /*
3  * Copyright (C) 2009-2010 Juanjo Marín <juanj.marin@juntadeandalucia.es>
4  * Copyright (C) 2005, Teemu Tervo <teemu.tervo@gmx.net>
5  * Copyright (C) 2016-2017, Bastien Nocera <hadess@hadess.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2, or (at your option)
10  * any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  */
21 
22 #include <config.h>
23 
24 #include <unistd.h>
25 #include <string.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 
29 #include <glib.h>
30 #include <glib/gi18n-lib.h>
31 #include <glib/gstdio.h>
32 #include <gio/gio.h>
33 
34 #include "comics-document.h"
35 #include "ev-document-misc.h"
36 #include "ev-file-helpers.h"
37 #include "ev-archive.h"
38 
39 #define BLOCK_SIZE 10240
40 
42 
44 {
46 };
47 
49 {
51  EvArchive *archive;
52  gchar *archive_path;
53  gchar *archive_uri;
54  GPtrArray *page_names;
55 };
56 
57 static GSList* get_supported_image_extensions (void);
58 
59 EV_BACKEND_REGISTER (ComicsDocument, comics_document)
60 
61 static char **
63 {
64  char **ret = NULL;
65  GPtrArray *array;
66 
67  if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, NULL))
68  goto out;
69 
70  array = g_ptr_array_new ();
71 
72  while (1) {
73  const char *name;
74  GError *error = NULL;
75 
76  if (!ev_archive_read_next_header (comics_document->archive, &error)) {
77  if (error != NULL) {
78  g_warning ("Fatal error handling archive: %s", error->message);
79  g_error_free (error);
80  }
81  break;
82  }
83 
84  name = ev_archive_get_entry_pathname (comics_document->archive);
85 
86  g_debug ("Adding '%s' to the list of files in the comics", name);
87  g_ptr_array_add (array, g_strdup (name));
88  }
89 
90  if (array->len == 0) {
91  g_ptr_array_free (array, TRUE);
92  } else {
93  g_ptr_array_add (array, NULL);
94  ret = (char **) g_ptr_array_free (array, FALSE);
95  }
96 
97 out:
98  ev_archive_reset (comics_document->archive);
99  return ret;
100 }
101 
102 /* This function chooses the archive decompression support
103  * book based on its mime type. */
104 static gboolean
106  ComicsDocument *comics_document,
107  GError **error)
108 {
109  if (g_content_type_is_a (mime_type, "application/x-cbr") ||
110  g_content_type_is_a (mime_type, "application/x-rar")) {
112  return TRUE;
113  } else if (g_content_type_is_a (mime_type, "application/x-cbz") ||
114  g_content_type_is_a (mime_type, "application/zip")) {
116  return TRUE;
117  } else if (g_content_type_is_a (mime_type, "application/x-cb7") ||
118  g_content_type_is_a (mime_type, "application/x-7z-compressed")) {
120  return TRUE;
121  } else if (g_content_type_is_a (mime_type, "application/x-cbt") ||
122  g_content_type_is_a (mime_type, "application/x-tar")) {
124  return TRUE;
125  } else {
126  g_set_error (error,
129  _("Not a comic book MIME type: %s"),
130  mime_type);
131  return FALSE;
132  }
133  g_set_error_literal (error,
136  _("libarchive lacks support for this comic book’s "
137  "compression, please contact your distributor"));
138  return FALSE;
139 }
140 
141 static int
142 sort_page_names (gconstpointer a,
143  gconstpointer b)
144 {
145  gchar *temp1, *temp2;
146  gint ret;
147 
148  temp1 = g_utf8_collate_key_for_filename (* (const char **) a, -1);
149  temp2 = g_utf8_collate_key_for_filename (* (const char **) b, -1);
150 
151  ret = strcmp (temp1, temp2);
152 
153  g_free (temp1);
154  g_free (temp2);
155 
156  return ret;
157 }
158 
159 static gboolean
161  const char *uri,
162  GError **error)
163 {
164  ComicsDocument *comics_document = COMICS_DOCUMENT (document);
165  GSList *supported_extensions;
166  gchar *mime_type;
167  gchar **cb_files, *cb_file;
168  int i;
169  GError *err = NULL;
170  GFile *file;
171 
172  file = g_file_new_for_uri (uri);
173  comics_document->archive_path = g_file_get_path (file);
174  g_object_unref (file);
175 
176  if (!comics_document->archive_path) {
177  g_set_error_literal (error,
180  _("Can not get local path for archive"));
181  return FALSE;
182  }
183 
184  comics_document->archive_uri = g_strdup (uri);
185 
186  mime_type = ev_file_get_mime_type (uri, FALSE, &err);
187  if (mime_type == NULL)
188  return FALSE;
189 
190  if (!comics_check_decompress_support (mime_type, comics_document, error)) {
191  g_free (mime_type);
192  return FALSE;
193  }
194  g_free (mime_type);
195 
196  /* Get list of files in archive */
197  cb_files = comics_document_list (comics_document);
198  if (!cb_files) {
199  g_set_error_literal (error,
202  _("File corrupted or no files in archive"));
203  return FALSE;
204  }
205 
206  comics_document->page_names = g_ptr_array_sized_new (64);
207 
208  supported_extensions = get_supported_image_extensions ();
209  for (i = 0; cb_files[i] != NULL; i++) {
210  cb_file = cb_files[i];
211  gchar *suffix = g_strrstr (cb_file, ".");
212  if (!suffix)
213  continue;
214  suffix = g_ascii_strdown (suffix + 1, -1);
215  if (g_slist_find_custom (supported_extensions, suffix,
216  (GCompareFunc) strcmp) != NULL) {
217  g_ptr_array_add (comics_document->page_names,
218  g_strstrip (g_strdup (cb_file)));
219  }
220  g_free (suffix);
221  }
222  g_strfreev (cb_files);
223  g_slist_foreach (supported_extensions, (GFunc) g_free, NULL);
224  g_slist_free (supported_extensions);
225 
226  if (comics_document->page_names->len == 0) {
227  g_set_error (error,
230  _("No images found in archive %s"),
231  uri);
232  return FALSE;
233  }
234 
235  /* Now sort the pages */
236  g_ptr_array_sort (comics_document->page_names, sort_page_names);
237 
238  return TRUE;
239 }
240 
241 static gboolean
243  const char *uri,
244  GError **error)
245 {
246  ComicsDocument *comics_document = COMICS_DOCUMENT (document);
247 
248  return ev_xfer_uri_simple (comics_document->archive_uri, uri, error);
249 }
250 
251 static int
253 {
254  ComicsDocument *comics_document = COMICS_DOCUMENT (document);
255 
256  if (comics_document->page_names == NULL)
257  return 0;
258 
259  return comics_document->page_names->len;
260 }
261 
262 typedef struct {
263  gboolean got_info;
264  int height;
265  int width;
266 } PixbufInfo;
267 
268 static void
269 get_page_size_prepared_cb (GdkPixbufLoader *loader,
270  int width,
271  int height,
272  PixbufInfo *info)
273 {
274  info->got_info = TRUE;
275  info->height = height;
276  info->width = width;
277 }
278 
279 static void
281  EvPage *page,
282  double *width,
283  double *height)
284 {
285  GdkPixbufLoader *loader;
286  ComicsDocument *comics_document = COMICS_DOCUMENT (document);
287  const char *page_path;
288  PixbufInfo info;
289  GError *error = NULL;
290 
291  if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, &error)) {
292  g_warning ("Fatal error opening archive: %s", error->message);
293  g_error_free (error);
294  goto out;
295  }
296 
297  loader = gdk_pixbuf_loader_new ();
298  info.got_info = FALSE;
299  g_signal_connect (loader, "size-prepared",
300  G_CALLBACK (get_page_size_prepared_cb),
301  &info);
302 
303  page_path = g_ptr_array_index (comics_document->page_names, page->index);
304 
305  while (1) {
306  const char *name;
307  GError *error = NULL;
308 
309  if (!ev_archive_read_next_header (comics_document->archive, &error)) {
310  if (error != NULL) {
311  g_warning ("Fatal error handling archive: %s", error->message);
312  g_error_free (error);
313  }
314  break;
315  }
316 
317  name = ev_archive_get_entry_pathname (comics_document->archive);
318  if (g_strcmp0 (name, page_path) == 0) {
319  char buf[BLOCK_SIZE];
320  gssize read;
321  gint64 left;
322 
323  left = ev_archive_get_entry_size (comics_document->archive);
324  read = ev_archive_read_data (comics_document->archive, buf,
325  MIN(BLOCK_SIZE, left), &error);
326  while (read > 0 && !info.got_info) {
327  if (!gdk_pixbuf_loader_write (loader, (guchar *) buf, read, &error)) {
328  read = -1;
329  break;
330  }
331  left -= read;
332  read = ev_archive_read_data (comics_document->archive, buf,
333  MIN(BLOCK_SIZE, left), &error);
334  }
335  if (read < 0) {
336  g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
337  g_error_free (error);
338  }
339  break;
340  }
341  }
342 
343  gdk_pixbuf_loader_close (loader, NULL);
344  g_object_unref (loader);
345 
346  if (info.got_info) {
347  if (width)
348  *width = info.width;
349  if (height)
350  *height = info.height;
351  }
352 
353 out:
354  ev_archive_reset (comics_document->archive);
355 }
356 
357 static void
358 render_pixbuf_size_prepared_cb (GdkPixbufLoader *loader,
359  gint width,
360  gint height,
361  EvRenderContext *rc)
362 {
363  int scaled_width, scaled_height;
364 
365  ev_render_context_compute_scaled_size (rc, width, height, &scaled_width, &scaled_height);
366  gdk_pixbuf_loader_set_size (loader, scaled_width, scaled_height);
367 }
368 
369 static GdkPixbuf *
371  EvRenderContext *rc)
372 {
373  GdkPixbufLoader *loader;
374  GdkPixbuf *tmp_pixbuf;
375  GdkPixbuf *rotated_pixbuf = NULL;
376  ComicsDocument *comics_document = COMICS_DOCUMENT (document);
377  const char *page_path;
378  GError *error = NULL;
379 
380  if (!ev_archive_open_filename (comics_document->archive, comics_document->archive_path, &error)) {
381  g_warning ("Fatal error opening archive: %s", error->message);
382  g_error_free (error);
383  goto out;
384  }
385 
386  loader = gdk_pixbuf_loader_new ();
387  g_signal_connect (loader, "size-prepared",
388  G_CALLBACK (render_pixbuf_size_prepared_cb),
389  rc);
390 
391  page_path = g_ptr_array_index (comics_document->page_names, rc->page->index);
392 
393  while (1) {
394  const char *name;
395 
396  if (!ev_archive_read_next_header (comics_document->archive, &error)) {
397  if (error != NULL) {
398  g_warning ("Fatal error handling archive: %s", error->message);
399  g_error_free (error);
400  }
401  break;
402  }
403 
404  name = ev_archive_get_entry_pathname (comics_document->archive);
405  if (g_strcmp0 (name, page_path) == 0) {
406  size_t size = ev_archive_get_entry_size (comics_document->archive);
407  char *buf;
408  ssize_t read;
409 
410  buf = g_malloc (size);
411  read = ev_archive_read_data (comics_document->archive, buf, size, &error);
412  if (read <= 0) {
413  if (read < 0) {
414  g_warning ("Fatal error reading '%s' in archive: %s", name, error->message);
415  g_error_free (error);
416  } else {
417  g_warning ("Read an empty file from the archive");
418  }
419  } else {
420  gdk_pixbuf_loader_write (loader, (guchar *) buf, size, NULL);
421  }
422  g_free (buf);
423  gdk_pixbuf_loader_close (loader, NULL);
424  break;
425  }
426  }
427 
428  tmp_pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
429  if (tmp_pixbuf) {
430  if ((rc->rotation % 360) == 0)
431  rotated_pixbuf = g_object_ref (tmp_pixbuf);
432  else
433  rotated_pixbuf = gdk_pixbuf_rotate_simple (tmp_pixbuf,
434  360 - rc->rotation);
435  }
436  g_object_unref (loader);
437 
438 out:
439  ev_archive_reset (comics_document->archive);
440  return rotated_pixbuf;
441 }
442 
443 static cairo_surface_t *
445  EvRenderContext *rc)
446 {
447  GdkPixbuf *pixbuf;
448  cairo_surface_t *surface;
449 
450  pixbuf = comics_document_render_pixbuf (document, rc);
451  surface = ev_document_misc_surface_from_pixbuf (pixbuf);
452  g_object_unref (pixbuf);
453 
454  return surface;
455 }
456 
457 static void
458 comics_document_finalize (GObject *object)
459 {
460  ComicsDocument *comics_document = COMICS_DOCUMENT (object);
461 
462  if (comics_document->page_names) {
463  g_ptr_array_foreach (comics_document->page_names, (GFunc) g_free, NULL);
464  g_ptr_array_free (comics_document->page_names, TRUE);
465  }
466 
467  g_clear_object (&comics_document->archive);
468  g_free (comics_document->archive_path);
469  g_free (comics_document->archive_uri);
470 
471  G_OBJECT_CLASS (comics_document_parent_class)->finalize (object);
472 }
473 
474 static void
476 {
477  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
478  EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass);
479 
480  gobject_class->finalize = comics_document_finalize;
481 
482  ev_document_class->load = comics_document_load;
483  ev_document_class->save = comics_document_save;
484  ev_document_class->get_n_pages = comics_document_get_n_pages;
485  ev_document_class->get_page_size = comics_document_get_page_size;
486  ev_document_class->render = comics_document_render;
487 }
488 
489 static void
491 {
492  comics_document->archive = ev_archive_new ();
493 }
494 
495 /* Returns a list of file extensions supported by gdk-pixbuf */
496 static GSList*
498 {
499  GSList *extensions = NULL;
500  GSList *formats = gdk_pixbuf_get_formats ();
501  GSList *l;
502 
503  for (l = formats; l != NULL; l = l->next) {
504  int i;
505  gchar **ext = gdk_pixbuf_format_get_extensions (l->data);
506 
507  for (i = 0; ext[i] != NULL; i++) {
508  extensions = g_slist_append (extensions,
509  g_strdup (ext[i]));
510  }
511 
512  g_strfreev (ext);
513  }
514 
515  g_slist_free (formats);
516  return extensions;
517 }