1 #ifdef COPYRIGHT_INFORMATION
2 #include "gplv3.h"
3 #endif
4 /*
5 * Copyright (C) 2002-2012 Edscott Wilson Garcia
6 * EMail: edscott@users.sf.net
7 *
8 * This file include modifications of GPL code provided by
9 * Dov Grobgeld <dov.grobgeld@gmail.com>
10 * Tadej Borovsak tadeboro@gmail.com
11 * see below for details.
12 *
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program;
26 */
27 // XXX: libzip is broken in ubuntu 11.10
28 // and /dev/shm is at /run/shm, so zip code has
29 // to be rewritten in bsd compatible way...
30 #ifdef HAVE_LIBZIP
31 #undef HAVE_LIBZIP
32 #endif
33
34 static
35 GdkPixbuf *
fix_pixbuf_scale(GdkPixbuf * in_pixbuf)36 fix_pixbuf_scale(GdkPixbuf *in_pixbuf){
37 if (!in_pixbuf || !GDK_IS_PIXBUF(in_pixbuf)) return NULL;
38 GdkPixbuf *out_pixbuf=NULL;
39 gint height = gdk_pixbuf_get_height (in_pixbuf);
40 gint width = gdk_pixbuf_get_width (in_pixbuf);
41
42 // this is to fix paper size previews for text files and pdfs
43 gint size = rfm_get_preview_image_size();
44 if((width < height && height != size) ||
45 (width >= height && width != size)) {
46 //out_pixbuf = rfm_pixbuf_scale_simple (in_pixbuf, size, GDK_INTERP_HYPER);
47 out_pixbuf = rfm_pixbuf_scale_stretch (in_pixbuf,
48 5*size/7,size,
49 GDK_INTERP_HYPER);
50 g_object_ref(out_pixbuf);
51 g_object_unref(in_pixbuf);
52 return out_pixbuf;
53 }
54 return in_pixbuf;
55 }
56
57
58 static GdkPixbuf *
load_preview_pixbuf_from_disk(const gchar * thumbnail)59 load_preview_pixbuf_from_disk (const gchar * thumbnail) {
60 GdkPixbuf *pixbuf = NULL;
61 if (rfm_g_file_test(thumbnail, G_FILE_TEST_EXISTS)) {
62 pixbuf = rfm_pixbuf_new_from_file (thumbnail, -1, -1);
63 }
64 #if 10
65 if (!GDK_IS_PIXBUF(pixbuf)) {
66 NOOP ("!GDK_IS_PIXBUF(pixbuf)\n");
67 return NULL;
68 }
69 GdkPixbuf *original=pixbuf;
70 pixbuf = fix_pixbuf_scale(original); // this unrefs old and refs new.
71 if (original != pixbuf) {
72 rfm_pixbuf_save(pixbuf, thumbnail);
73 }
74 #endif
75 return pixbuf;
76 }
77
78
79
80 //static
81 //void show_tip (const population_t * population_p);
82
83 /////////////////////////////////////////////////////////////////
84 // forked version of libmagicwand... No need to mutex thread unsafe library here...
85
86 void * mime_function (void *p, void *q);
87 void * mime_file (void *p);
88 void * mime_encoding (void *p);
89 void * mime_magic (void *p);
90
91 G_MODULE_EXPORT
92 const gchar *
want_imagemagick_preview(record_entry_t * en)93 want_imagemagick_preview (record_entry_t * en) {
94 NOOP (stderr, "want_imagemagick_preview\n");
95 if(!en) return NULL;
96
97
98 if(!en->filetype) {
99 //en->filetype = mime_file ((void *)(en->path));
100 en->filetype = mime_function (en, "mime_file");
101 }
102 if(!en->mimemagic){
103 //en->mimemagic = mime_magic ((void *)(en->path));
104 en->mimemagic = mime_function (en, "mime_magic");
105 if(!en->mimemagic) en->mimemagic =g_strdup(_("unknown"));
106 }
107 gchar *mimetype = g_strconcat ( en->mimetype, "/",en->mimemagic,
108 (en->mimemagic)?"/":NULL, en->filetype, NULL);
109 const gchar *convert_type = NULL;
110
111 if(mimetype && strstr (mimetype, "text") && !(strstr (mimetype, "opendocument"))) { // decode delegate is ghostscript
112 if(!en->encoding) {
113 //en->encoding = mime_encoding ((void *)(en->path));
114 en->encoding = mime_function (en, "mime_encoding");
115 if(!en->encoding) en->encoding=g_strdup(_("unknown"));
116 }
117 NOOP ("mime_encoding= %s\n", en->encoding);
118 if (strcmp(en->encoding,"binary")==0) {
119 return NULL;
120 }
121 convert_type = "TXT";
122 } else if(mimetype && strstr (mimetype, "pdf")) { // decode delegate is ghostscript
123 convert_type = "PDF";
124 } else if(mimetype && (strstr (mimetype, "postscript") || strstr (mimetype, "eps")) ){
125 // decode delegate is ghostscript
126 convert_type = "PS";
127 }
128
129 g_free (mimetype);
130
131 if(!convert_type)
132 return NULL;
133 NOOP ("converttype=%s\n", convert_type);
134
135 static gboolean warned = FALSE;
136
137 gboolean gs_warn = strcmp (convert_type, "PS") == 0 || strcmp (convert_type, "PDF") == 0;
138 if(gs_warn) {
139 gchar *ghostscript = g_find_program_in_path ("gs");
140 if(!ghostscript) {
141 if(!warned) {
142 g_warning
143 ("\n*** Please install ghostscript for ps and pdf previews\n*** Make sure ghostscript fonts are installed too!\n*** You have been warned.\n");
144 fflush (NULL);
145 warned = TRUE;
146 }
147 return NULL;
148 }
149 g_free (ghostscript);
150 }
151 return convert_type;
152 }
153
154 static void *
gs_wait_f(void * data)155 gs_wait_f(void *data){
156 void **arg = data;
157 //GMutex *wait_mutex = arg[0];
158 GCond *wait_signal = arg[1];
159 pid_t pid = GPOINTER_TO_INT(arg[2]);
160 g_free(arg);
161 gint status;
162 waitpid (pid, &status, WUNTRACED);
163 g_cond_signal(wait_signal);
164 g_thread_yield();
165 rfm_cond_free(wait_signal);
166 return NULL;
167 }
168
169 // this function should take a private population_p
170 static GdkPixbuf *
image_magic_preview_forked(const population_t * population_p,gchar * thumbnail,const gchar * convert_type)171 image_magic_preview_forked (const population_t * population_p, gchar * thumbnail, const gchar * convert_type) {
172
173 gchar *ghostscript = g_find_program_in_path ("gs");
174 //gchar *convert = g_find_program_in_path ("convert");
175 if(!ghostscript) {
176 g_error ("cannot find \"%s\" program in path at rodent_magick.i", ghostscript);
177 return NULL;
178 }
179 int fd = open (population_p->en->path, O_RDONLY);
180 if(fd < 0) {
181 return 0;
182 }
183 close (fd);
184
185 population_p = population_p;
186
187
188 // options for pdf/ps and txt previews:
189 // imagemagick: this depends on ghostscript and gsfonts
190 //
191 // plain ghostscript is muuch faster than imagemagick: (for ps/pdf)
192 // gs -dSAFER -dBATCH -dNOPAUSE -sDEVICE=png256 -dFirstPage=1 -dLastPage=1 -dPDFFitPage -r100 -sOutputFile=out.png input.pdf
193 //
194 //
195 // text files...must first convert to ps:
196 // groff -Tps file >file.ps
197 // only problem is the encoding now...
198 // this works, but how to determine the iso code?
199 // iconv -f utf-8 -t iso8859-1 -o file.iso8859 file-utf8
200 //
201 // solution: use paps
202 // paps file > file.ps
203 //
204 // real solution, source paps code to produce preview with cairo/pango
205
206 char *src, *tgt;
207
208 char *arg[13];
209 int i = 0;
210
211 //pdf and ps ghostscript conversion
212 src = g_strdup (population_p->en->path);
213 tgt = g_strdup_printf ("-sOutputFile=%s", thumbnail);
214 arg[i++] = ghostscript;
215 arg[i++] = "-dSAFER";
216 arg[i++] = "-dBATCH";
217 arg[i++] = "-dNOPAUSE";
218 arg[i++] = "-sPAPERSIZE=letter"; // or a4...
219 arg[i++] = "-sDEVICE=png256";
220 arg[i++] = "-dFirstPage=1";
221 arg[i++] = "-dLastPage=1";
222 arg[i++] = "-dPDFFitPage";
223 arg[i++] = "-r100";
224 arg[i++] = tgt;
225 arg[i++] = src;
226 arg[i++] = NULL;
227
228 NOOP ("SHOW_TIPx: %s(%s)\n", arg[0], arg[3]);
229 GdkPixbuf * retval=NULL;
230 // this fork is ok from thread, I guess.
231 TRACE( "--> creating thumbnail %s\n", thumbnail);
232 pid_t pid = fork ();
233 if(!pid) {
234 TRACE( "--> child is creating thumbnail %s\n", thumbnail);
235 execv (arg[0], arg);
236 _exit (123);
237 } else {
238 // Create wait thread.
239 void **arg = (void **)malloc(3*sizeof(void *));
240 if (!arg) g_error("malloc: %s\n", strerror(errno));
241 GCond *wait_signal;
242 GMutex *wait_mutex;
243 rfm_mutex_init(wait_mutex);
244 rfm_cond_init(wait_signal);
245 arg[0] = wait_mutex;
246 arg[1] = wait_signal;
247 arg[2] = GINT_TO_POINTER(pid);
248
249 g_mutex_lock(wait_mutex);
250 rfm_thread_create("ghostscript wait thread", gs_wait_f, arg, FALSE);
251 if (!rfm_cond_timed_wait(wait_signal, wait_mutex, 4)){
252 DBG("Aborting runaway ghostscript preview for %s (pid %d)\n",
253 src, (int)pid);
254 kill(pid, SIGKILL);
255 } else {
256 // this function refs retval
257 retval = load_preview_pixbuf_from_disk (thumbnail); // refs
258 }
259 g_mutex_unlock(wait_mutex);
260 rfm_mutex_free(wait_mutex);
261 NOOP ("SHOW_TIPx: preview created by convert\n");
262 }
263 g_free (ghostscript); //arg[0]
264 //g_free (convert); //arg[0]
265 g_free (src);
266 g_free (tgt);
267 return retval;
268 }
269
270 /////// end forked stuff /////
271 //////////////////////////////////////////////////
272 //
273 // These routines are heavily modified from the "paps" source
274 // GPL by Dov Grobgeld <dov.grobgeld@gmail.com>
275
276 #define BUFSIZE (4096)
277 #define DEFAULT_FONT_FAMILY "Sans"
278 //#define DEFAULT_FONT_FAMILY "Arial"
279 #define DEFAULT_FONT_SIZE "12"
280 #define MAKE_FONT_NAME(f,s) f " " s
281
282 #include <locale.h>
283 #include <pango/pango.h>
284
285 typedef struct {
286 cairo_t *cr;
287 cairo_surface_t *surface;
288 PangoContext *context;
289 int column_width;
290 int column_height;
291 int top_margin;
292 int bottom_margin;
293 int left_margin;
294 int right_margin;
295 int page_width;
296 int page_height;
297 PangoDirection pango_dir;
298 } page_layout_t;
299
300 typedef enum {
301 PAPER_TYPE_A4 = 0,
302 PAPER_TYPE_US_LETTER = 1,
303 PAPER_TYPE_US_LEGAL = 2
304 } paper_type_t;
305
306 typedef struct {
307 int width;
308 int height;
309 } paper_size_t;
310
311 static const paper_size_t paper_sizes[] = {
312 {595, 842}, /* A4 */
313 {612, 792}, /* US letter */
314 {612, 1008} /* US legal */
315 };
316
317 typedef struct linelink_t {
318 PangoLayoutLine *pango_line;
319 PangoRectangle logical_rect;
320 PangoRectangle ink_rect;
321 int formfeed;
322 } linelink_t;
323
324 /* Structure representing a paragraph
325 */
326 typedef struct paragraph_t {
327 char *text;
328 int length;
329 int height; /* Height, in pixels */
330 int formfeed;
331 PangoLayout *layout;
332 } paragraph_t;
333
334
335 static PangoLanguage *
get_language(void)336 get_language (void) {
337 PangoLanguage *retval;
338 gchar *lang = g_strdup (setlocale (LC_CTYPE, NULL));
339 gchar *p;
340
341 p = strchr (lang, '.');
342 if(p)
343 *p = 0;
344 p = strchr (lang, '@');
345 if(p)
346 *p = 0;
347
348 retval = pango_language_from_string (lang);
349 g_free (lang);
350
351 return retval;
352 }
353
354 static PangoDirection
get_direction(const gchar * str)355 get_direction (const gchar * str) {
356 PangoDirection direction;
357 const gchar *p;
358 if(g_utf8_validate (str, -1, NULL)) {
359 for(p = str; p != NULL && *p; p = g_utf8_find_next_char (p, NULL)) {
360 gunichar ch = g_utf8_get_char (p);
361 direction = pango_unichar_direction (ch);
362 if(direction == PANGO_DIRECTION_NEUTRAL)
363 continue;
364 NOOP ("direction found\n");
365 return direction;
366 }
367 }
368 // default:
369 NOOP ("PANGO_DIRECTION_LTR\n");
370 return PANGO_DIRECTION_LTR;
371 }
372
373 static gint
x_strcmp(gconstpointer a,gconstpointer b)374 x_strcmp(gconstpointer a, gconstpointer b){
375 return strcmp((gchar *)a, (gchar *)b);
376 }
377
378 static gchar *
directory_text(const gchar * path)379 directory_text(const gchar *path){
380 gint count=0;
381 DIR *directory = opendir(path);
382 if (!directory) {
383 NOOP("directory_text(): Cannot open %s\n", path);
384 return g_strdup_printf("%s: %s\n", path, strerror(errno));
385 }
386 // http://womble.decadent.org.uk/readdir_r-advisory.html
387
388 #if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD)
389 size_t size = offsetof(struct dirent, d_name) +
390 fpathconf(dirfd(directory), _PC_NAME_MAX) + 1;
391 #else
392 size_t size = offsetof(struct dirent, d_name) +
393 pathconf(path, _PC_NAME_MAX) + 1;
394 #endif
395 struct dirent *d;
396 gchar *utf = rfm_utf_string(path);
397 gchar *dir_text = g_strdup_printf("%s:\n", utf);
398 g_free(utf);
399 struct dirent *buffer = (struct dirent *)malloc(size);
400 if (!buffer) g_error("malloc: %s\n", strerror(errno));
401
402 GSList *list = NULL;
403 gint error;
404 while ((error = readdir_r(directory, buffer, &d)) == 0 && d != NULL){
405 utf = rfm_utf_string(d->d_name);
406 #ifdef HAVE_STRUCT_DIRENT_D_TYPE
407 const gchar *string=_("unknown");
408 switch (d->d_type){
409 case DT_BLK:
410 string = _("Block device");
411 break;
412 case DT_CHR:
413 string = _("Character device");
414 break;
415 case DT_DIR:
416 string = _("Directory");
417 break;
418 case DT_FIFO:
419 string = _("FIFO");
420 break;
421 case DT_LNK:
422 string = _("Symbolic Link");
423 break;
424 case DT_REG:
425 string = _("Regular file");
426 break;
427 case DT_SOCK:
428 string = _("Socket");
429 break;
430 default :
431 break;
432 }
433 gchar *line = g_strdup_printf("[%s]\t%s", string, utf);
434 #else
435 gchar *line = g_strdup_printf("%s", d->d_name);
436 #endif
437 g_free(utf);
438 list = g_slist_prepend(list, line);
439 if (count++ >= 100) break;
440 }
441 closedir (directory);
442 g_free(buffer);
443 if (error) {
444 utf = rfm_utf_string(strerror(error));
445 gchar *g = g_strdup_printf("%s\t%s\n", dir_text, utf);
446 g_free(utf);
447 g_free(dir_text);
448 dir_text = g;
449 } else {
450 list = g_slist_sort(list, x_strcmp);
451 GSList *tmp = list;
452 for (;tmp && tmp->data; tmp = tmp->next){
453 gchar *g = g_strdup_printf("%s\t%s\n", dir_text, (gchar *)tmp->data);
454 g_free(dir_text);
455 dir_text = g;
456 g_free(tmp->data);
457 }
458 }
459 g_slist_free(list);
460
461 return dir_text;
462 }
463
464 static gchar *
read_file(record_entry_t * en)465 read_file (record_entry_t *en) {
466 gchar *path = g_strdup(en->path);
467 gchar *encoding;
468
469 if (!IS_LOCAL_TYPE(en->type) &&
470 !rfm_g_file_test_with_wait(path, G_FILE_TEST_EXISTS)){
471 return NULL;
472 }
473
474 gchar *buffer = (gchar *)malloc (BUFSIZE);
475 if (!buffer) g_error("malloc: %s", strerror(errno));
476 memset (buffer, 0, BUFSIZE);
477
478 if (g_file_test(path, G_FILE_TEST_IS_DIR)){
479 gchar *txt = directory_text(path);
480 strncpy(buffer, txt, BUFSIZE-1);
481 g_free(txt);
482 encoding = g_strdup("UTF-8");
483 goto finishup;
484 }
485 gint fd = open (path, O_RDONLY);
486 if(fd < 0) {
487 DBG ("open(%s): %s\n", path, strerror (errno));
488 g_free(path);
489 g_free (buffer);
490 return NULL;
491 }
492 // string terminated with memset() above.
493 // coverity[string_null_argument : FALSE]
494 gint bytes = read (fd, buffer, BUFSIZE - 1);
495 close (fd);
496 if(bytes < 0) {
497 DBG ("%s: Error reading file %s (%s).\n", g_get_prgname (), path, strerror (errno));
498 g_free(path);
499 g_free (buffer);
500 return NULL;
501 }
502 gint i;
503 for (i=0; i<BUFSIZE-2; i++) {
504 if (buffer[i] < 32 ) {
505 if (buffer[i] == 9) continue;
506 if (buffer[i] == 10) continue;
507 if (buffer[i] == 0) break;
508 else buffer[i] = '.';
509 }
510 }
511 encoding = MIME_encoding ((void *)path);
512
513 if(strrchr (buffer, '\n')) {
514 *(strrchr (buffer, '\n') + 1) = 0;
515 } else {
516 buffer[BUFSIZE - 1] = 0;
517 }
518
519 finishup:;
520
521 gsize bytes_read,
522 bytes_written;
523 GError *error = NULL;
524 if(encoding && (!strstr (encoding, "utf-8") || !strstr (encoding, "UTF-8"))) {
525 gchar *obuffer = g_convert_with_fallback (buffer, -1, "UTF-8", encoding,
526 NULL, &bytes_read, &bytes_written, &error);
527 if(error) {
528 NOOP ("g_convert_with_fallback(%s): %s\n", path, error->message);
529 g_error_free (error);
530 error=NULL;
531 obuffer = g_convert_with_fallback (buffer, -1, "UTF-8", "iso8859-15",
532 NULL, &bytes_read, &bytes_written, &error);
533 if (error) {
534 NOOP ("b.g_convert_with_fallback(%s): %s\n", path, error->message);
535 g_error_free (error);
536 }
537 }
538 NOOP ("convert successful from %s to UTF-8\n", encoding);
539 g_free (buffer);
540 buffer = obuffer;
541 }
542
543 g_free(path);
544 g_free (encoding);
545 return buffer;
546 }
547
548 int
output_page(GList * pango_lines,page_layout_t * page_layout)549 output_page (GList * pango_lines, page_layout_t * page_layout) {
550 int column_pos = 0;
551 int page_idx = 1;
552
553 int pango_column_height = page_layout->page_height - page_layout->top_margin - page_layout->bottom_margin;
554
555 while(pango_lines && pango_lines->data) {
556 linelink_t *line_link = pango_lines->data;
557 PangoLayoutLine *line = line_link->pango_line;
558 if (!line) continue;
559 PangoRectangle logical_rect;
560 PangoRectangle ink_rect;
561 pango_layout_line_get_extents (line, &ink_rect, &logical_rect);
562 /* Assume square aspect ratio for now */
563 column_pos += (line_link->logical_rect.height / PANGO_SCALE);
564 double y_pos = column_pos + page_layout->top_margin;
565 if(y_pos > pango_column_height)
566 break; //just first page
567 double x_pos = page_layout->left_margin;
568 /* Do RTL column layout for RTL direction */
569 if(page_layout->pango_dir == PANGO_DIRECTION_RTL) {
570 x_pos = page_layout->page_width - page_layout->right_margin;
571 }
572 NOOP ("gdk_draw_layout_line: %lf, %lf\n", x_pos, y_pos);
573 cairo_move_to (page_layout->cr, x_pos, y_pos);
574 pango_cairo_show_layout_line (page_layout->cr, line);
575 pango_lines = pango_lines->next;
576 }
577 return page_idx;
578 }
579
580 /* Split a list of paragraphs into a list of lines.
581 */
582 GList *
split_paragraphs_into_lines(page_layout_t * page_layout,GList * paragraphs)583 split_paragraphs_into_lines (page_layout_t * page_layout, GList * paragraphs) {
584 GList *line_list = NULL;
585 int max_height = 0;
586 /* Read the file */
587
588 /* Now split all the paragraphs into lines */
589 GList *par_list;
590
591 par_list = paragraphs;
592 while(par_list && par_list->data) {
593 int para_num_lines;
594 linelink_t *line_link;
595 paragraph_t *para = par_list->data;
596
597 para_num_lines = pango_layout_get_line_count (para->layout);
598 NOOP ("para_num_lines = %d\n", para_num_lines);
599
600 int i;
601 for(i = 0; i < para_num_lines; i++) {
602 PangoRectangle logical_rect;
603 PangoRectangle ink_rect;
604 PangoLayoutLine *pango_line = pango_layout_get_line_readonly (para->layout, i);
605 if (!pango_line) break;
606
607 line_link = g_new (linelink_t, 1);
608 line_link->formfeed = 0;
609 line_link->pango_line = pango_line;
610 pango_layout_line_get_extents (line_link->pango_line, &ink_rect, &logical_rect);
611 line_link->logical_rect = logical_rect;
612 if(para->formfeed && i == (para_num_lines - 1))
613 line_link->formfeed = 1;
614 line_link->ink_rect = ink_rect;
615 line_list = g_list_prepend (line_list, line_link);
616 if(logical_rect.height > max_height)
617 max_height = logical_rect.height;
618 }
619
620 par_list = par_list->next;
621 }
622
623 return g_list_reverse (line_list);
624
625 }
626
627 /* Take a UTF8 string and break it into paragraphs on \n characters
628 */
629 static GList *
split_text_into_paragraphs(page_layout_t * page_layout,char * text)630 split_text_into_paragraphs (page_layout_t * page_layout, char *text) {
631 char *p = text;
632 char *next;
633 gunichar wc;
634 GList *result = NULL;
635 char *last_para = text;
636 int width = page_layout->page_width - page_layout->right_margin - page_layout->left_margin;
637
638 while(p != NULL && *p) {
639 wc = g_utf8_get_char (p);
640 next = g_utf8_next_char (p);
641 if(wc == (gunichar) - 1) {
642 DBG ("%s: Invalid character in input\n", g_get_prgname ());
643 wc = 0;
644 }
645 if(!*p || !wc || wc == '\n' || wc == '\f') {
646 paragraph_t *para = g_new (paragraph_t, 1);
647 para->text = last_para;
648 para->length = p - last_para;
649 para->layout = pango_layout_new (page_layout->context);
650 pango_layout_set_text (para->layout, para->text, para->length);
651 pango_layout_set_justify (para->layout, FALSE);
652 pango_layout_set_alignment (para->layout,
653 page_layout->pango_dir == PANGO_DIRECTION_LTR ? PANGO_ALIGN_LEFT : PANGO_ALIGN_RIGHT);
654 pango_layout_set_wrap (para->layout, PANGO_WRAP_WORD_CHAR);
655
656 pango_layout_set_width (para->layout, width * PANGO_SCALE);
657 para->height = 0;
658
659 last_para = next;
660
661 if(wc == '\f')
662 para->formfeed = 1;
663 else
664 para->formfeed = 0;
665
666 result = g_list_append (result, para);
667 }
668 if(!wc) /* incomplete character at end */
669 break;
670 p = next;
671 }
672
673 return (result);
674 }
675
676
677 typedef struct tarball_t {
678 const gchar *cmd;
679 const gchar *options;
680 const gchar *mimetype;
681 gboolean status;
682 } tarball_t;
683
684 static tarball_t tarball_v[] = {
685 {"tar", "-tzf", "application/x-compressed-tar", FALSE},
686 {"tar", "-tjf", "application/x-bzip-compressed-tar", FALSE},
687 {"tar", "--use-compress-program -tf", "application/x-lzma-compressed-tar", FALSE},
688 {"tar", "-tJf", "application/x-tarz", FALSE},
689 {"tar", "-tf", "application/x-tar", FALSE},
690 {"rpm", "-qip", "application/x-rpm", FALSE},
691 {"rpm", "-qip", "application/x-redhat-package-manager", FALSE},
692 {"dpkg", "--info", "application/x-deb", FALSE},
693 {"dpkg", "--info", "application/x-debian-package", FALSE},
694 {NULL, NULL, NULL}
695 };
696
is_tarball(record_entry_t * en)697 gboolean is_tarball(record_entry_t *en){
698 if (en->st && en->st->st_size > 5000000) return -1;
699 const gchar *mimetype = en->mimetype;
700 if (!mimetype) return -1;
701 static gsize initialized = 0;
702 tarball_t *tarball_p;
703 if (g_once_init_enter (&initialized)) {
704 tarball_p = tarball_v;
705 for (;tarball_p && tarball_p->cmd; tarball_p++){
706 gchar *g = g_find_program_in_path(tarball_p->cmd);
707 if (g) {
708 tarball_p->status = TRUE;
709 g_free(g);
710 }
711 }
712 g_once_init_leave (&initialized, 1);
713 }
714
715 tarball_p = tarball_v;
716 for (;tarball_p && tarball_p->cmd; tarball_p++){
717 if (!tarball_p->status) continue;
718 if (strcmp(mimetype, tarball_p->mimetype)==0) return TRUE;
719 }
720 return FALSE;
721 }
722
723 static gchar *
tarball_text(const gchar * path,const gchar * mimetype)724 tarball_text(const gchar *path, const gchar *mimetype){
725 gchar *cmd=NULL;
726 tarball_t *tarball_p = tarball_v;
727 for (;tarball_p && tarball_p->cmd; tarball_p++){
728 if (!tarball_p->status) continue;
729 if (strcmp(mimetype, tarball_p->mimetype)==0) {
730 cmd = g_strdup_printf("%s %s \"%s\"",
731 tarball_p->cmd, tarball_p->options, path);
732 NOOP(stderr, "CMD = %s\n", cmd);
733 break;
734 }
735 }
736 if (!cmd) return g_strdup(_("File format not recognized"));
737
738 gint count = 0;
739 gchar *t = g_strdup_printf(_("Contents of %s"), path);
740 gchar *text = g_strconcat(t, "\n", NULL);
741 g_free(t);
742
743 FILE *p = popen (cmd, "r");
744 if(p) {
745 gchar line[1024];
746 memset(line, 0, 1024);
747
748 while(fgets (line, 1023, p) && ! feof(p)) {
749 if (count++ >= 50) break;
750 gchar *g = g_strdup_printf("%s\t%s", text, line);
751 g_free(text);
752 text = g;
753 }
754 pclose (p);
755 }
756 g_free(cmd);
757 return text;
758 }
759
760
761 // this function take a private population_p
762 static void *
text_preview_f(void * data)763 text_preview_f (void *data){
764 void **arg = data;
765 gchar * text = arg[0];
766 gchar * thumbnail = arg[1];
767 page_layout_t page_layout;
768
769 NOOP ("PAP: file read complete:\n%s\n", "");
770 NOOP ("PAP: file read complete:\n%s\n", text);
771
772 /* Page layout, either a4 or letter or legal. */
773 page_layout.page_width = paper_sizes[PAPER_TYPE_US_LETTER].width;
774 page_layout.page_height = paper_sizes[PAPER_TYPE_US_LETTER].height;
775 page_layout.top_margin = 36;
776 page_layout.bottom_margin = 36;
777 page_layout.right_margin = 36;
778 page_layout.left_margin = 36;
779
780 // determine pango_dir
781 page_layout.pango_dir = get_direction (text);
782 page_layout.column_height = page_layout.page_height - page_layout.top_margin - page_layout.bottom_margin;
783 page_layout.column_width = page_layout.page_width - page_layout.left_margin - page_layout.right_margin;
784
785 // create cairo drawable surface and context
786 page_layout.surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, page_layout.page_width, page_layout.page_height);
787 //CAIRO_FORMAT_A1 CAIRO_FORMAT_A8
788 page_layout.cr = cairo_create (page_layout.surface);
789 if(cairo_surface_status (page_layout.surface) != CAIRO_STATUS_SUCCESS) {
790 g_error ("cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS");
791 }
792 // clear cairo surface
793 //
794 cairo_set_source_rgb (page_layout.cr, 1.0, 1.0, 1.0);
795 cairo_paint (page_layout.cr);
796 // create pango context
797 page_layout.context = pango_cairo_create_context (page_layout.cr);
798 // Setup pango context
799 pango_cairo_context_set_resolution (page_layout.context, -1);
800 pango_context_set_language (page_layout.context, get_language ());
801 pango_context_set_base_dir (page_layout.context, page_layout.pango_dir);
802
803 // font description
804 gchar *font = MAKE_FONT_NAME (DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE);
805 PangoFontDescription *font_description = pango_font_description_from_string (font);
806 if((pango_font_description_get_set_fields (font_description)
807 & PANGO_FONT_MASK_FAMILY) == 0) {
808 pango_font_description_set_family (font_description, DEFAULT_FONT_FAMILY);
809 }
810 if((pango_font_description_get_set_fields (font_description)
811 & PANGO_FONT_MASK_SIZE) == 0) {
812 pango_font_description_set_size (font_description, atoi (DEFAULT_FONT_SIZE) * PANGO_SCALE);
813 }
814 pango_context_set_font_description (page_layout.context, font_description);
815 pango_font_description_free (font_description);
816
817 NOOP ("pango setup complete\n");
818 GList *paragraphs = split_text_into_paragraphs (&page_layout, text);
819
820 NOOP ("pango split_text_into_paragraphs complete--> %d \n", g_list_length (paragraphs));
821 GList *pango_lines = split_paragraphs_into_lines (&page_layout, paragraphs);
822 NOOP ("pango split_paragraphs_into_lines complete--> %d\n", g_list_length (pango_lines));
823
824 // cairo setup
825 cairo_new_path (page_layout.cr);
826 cairo_set_line_width (page_layout.cr, 0.5);
827 cairo_set_source_rgb (page_layout.cr, 0.0, 0.0, 0.0);
828
829 //output layouts to page ()
830 output_page (pango_lines, &page_layout);
831 // tmp: write png of pixbuf
832 NOOP("// numpages=%d\n", num_pages);
833 cairo_destroy (page_layout.cr);
834 NOOP ("// write thumbnail\n");
835 if(cairo_surface_write_to_png (page_layout.surface, thumbnail) != CAIRO_STATUS_SUCCESS) {
836 DBG ("cairo_surface_write_to_png(surface,) != CAIRO_STATUS_SUCCESS");
837 }
838 cairo_surface_destroy (page_layout.surface);
839 // destroy GLists
840 GList *tmp;
841 for(tmp = pango_lines; tmp && tmp->data; tmp = tmp->next) {
842 linelink_t *line = tmp->data;
843 if(G_IS_OBJECT (line->pango_line)) g_object_unref (line->pango_line);
844 g_free (line);
845 }
846 for(tmp = paragraphs; tmp && tmp->data; tmp = tmp->next) {
847 paragraph_t *para = tmp->data;
848 if(G_IS_OBJECT (para->layout)) {
849 g_object_unref (para->layout);
850 }
851 g_free (para);
852 }
853 if(G_IS_OBJECT (page_layout.context)) g_object_unref (page_layout.context);
854
855 g_list_free (paragraphs);
856 g_list_free (pango_lines);
857
858 // pixbuf generated and reffed in routine:
859 NOOP("load_preview_pixbuf_from_disk %s\n", thumbnail);
860 GdkPixbuf *pixbuf = load_preview_pixbuf_from_disk (thumbnail); // refs
861 return pixbuf;
862 }
863
864 static GdkPixbuf *
text_preview(const population_t * population_p,gchar * thumbnail,view_t * view_p)865 text_preview (const population_t * population_p, gchar * thumbnail, view_t * view_p) {
866 if(!population_p || !population_p->en || !population_p->en->path)
867 return NULL;
868
869 if (population_p->en->mimetype && strstr(population_p->en->mimetype,"text")){
870 // OK
871 }
872
873 // Read a cache page worth of text and convert to utf-8
874 // Tarballs...
875
876 gint Tarballs = is_tarball(population_p->en);
877 if (Tarballs < 0) return NULL;
878
879 gchar *text;
880 if (population_p->en->st && population_p->en->st->st_size == 0){
881 TRACE( "oooooo size for %s == 0\n", population_p->en->path);
882 text = g_strdup_printf("***** %s *****", _("Empty file"));
883 } else if (Tarballs) {
884 text =tarball_text(population_p->en->path, population_p->en->mimetype);
885 } else {
886 text = read_file (population_p->en);
887 }
888 if(!text) return NULL;
889 if (!strchr(text, '\n')){
890 gchar *t = g_strconcat(text,"\n",NULL);
891 g_free(text);
892 text = t;
893 }
894 void *arg[]={text, thumbnail};
895 GdkPixbuf *pixbuf = rfm_context_function(text_preview_f, arg);
896 g_free(text);
897 return pixbuf;
898 }
899
900 static
901 void *
mime_preview_at_size(const population_t * population_p)902 mime_preview_at_size(const population_t * population_p) {
903 gint preview_size = rfm_get_preview_image_size();
904 TRACE( "oooooo mime_preview\n");
905 if(!population_p->en || !population_p->en->st) {
906 NOOP ("SHOW_TIPx: !population_p->en || !population_p->en->st\n");
907 return NULL;
908 }
909
910 // Check if in pixbuf hash. If so, return with the hashed pixbuf.
911 // Note that if the thumbnail is out of date, Null will be returned
912 // from the pixbuf hash.
913 GdkPixbuf *pixbuf = (GdkPixbuf *) rfm_find_in_pixbuf_hash(population_p->en->path, preview_size); // refs
914 if(pixbuf) {
915 TRACE( "oooooo pixbuf located in hash table.\n");
916 return pixbuf;
917 }
918
919 gchar *thumbnail = rfm_get_thumbnail_path (population_p->en->path, preview_size);
920 TRACE( "oooooo thumbnail path=%s\n", thumbnail);
921 // Empty files hack
922 if(population_p->en->st->st_size == 0) {
923 pixbuf = text_preview (population_p, thumbnail, population_p->view_p); //refs
924 // Replace newly created pixbuf in pixbuf hash.
925 // Reference will now belong to the hash table.
926 #if 0
927 g_object_unref(pixbuf);
928 #else
929 rfm_put_in_pixbuf_hash(population_p->en->path, preview_size, pixbuf);
930 #endif
931 g_free(thumbnail);
932 return pixbuf;
933 }
934 // So it is not already loaded (rodent_preview_if_loaded()
935 // should be called beforehand.
936 // Is the pixbuf in the thumbnail cache?
937
938 // Thumbnail should always resolve to a local and absolute path.
939 if(thumbnail && g_file_test (thumbnail, G_FILE_TEST_EXISTS)) {
940 TRACE( "oooooo thumbnail %s exists!\n", thumbnail);
941 struct stat st;
942 if(stat (thumbnail, &st) < 0){
943 DBG("stat(%s): %s", thumbnail, strerror (errno));
944 return NULL;
945 }
946 if(st.st_mtime >= population_p->en->st->st_mtime) {
947 TRACE( "oooooo: %s: thumbnail %s is up to date\n",
948 population_p->en->path, thumbnail);
949 // pixbuf generated and reffed in routine:
950 pixbuf = load_preview_pixbuf_from_disk (thumbnail); // refs
951 if(pixbuf) {
952 g_free (thumbnail);
953 if (!GDK_IS_PIXBUF(pixbuf)) return NULL;
954
955 // pixbuf has ref==1
956 // Replace newly created pixbuf in pixbuf hash.
957 // Reference will now belong to the hash table.
958 #if 0
959 g_object_unref(pixbuf);
960 #else
961 rfm_put_in_pixbuf_hash(population_p->en->path, preview_size, pixbuf);
962 #endif
963 NOOP ("SHOW_TIPx: preview loaded from thumbnail file\n");
964 return pixbuf;
965 }
966 }
967 }
968
969
970 // So it is not in thumbnail cache. So it will have to be regenerated
971 // from scratch.
972
973 TRACE( "oooooo: %s: thumbnail %s must be generated\n",
974 population_p->en->path, thumbnail);
975
976
977 // First we shall try internal gtk image manipulation:
978 if(rfm_entry_is_image (population_p->en)) {
979 // This function will automatically put the pixbuf into the pixbuf hash:
980 pixbuf = rfm_get_pixbuf (population_p->en->path, preview_size); //refs
981 NOOP ("SHOW_TIPx: preview created by gtk\n");
982 g_free (thumbnail);
983 if(pixbuf) return pixbuf;
984 else return NULL;
985 }
986
987 // So it is not an image type
988 // Do we have zip previews plugin?
989 if (rfm_void(RFM_MODULE_DIR, "mimezip", "module_active")){
990 NOOP(stderr, "zip test\n");
991
992 if(!population_p->en->filetype) {
993 population_p->en->filetype = mime_function ((void *)(population_p->en),
994 "mime_file");
995 //population_p->en->filetype = mime_file ((void *)(population_p->en->path));
996 }
997 gboolean OpenDocument = (population_p->en->filetype && strstr (population_p->en->filetype, "OpenDocument") != NULL);
998 gboolean plainzip = (population_p->en->filetype && strstr (population_p->en->filetype, "Zip archive") != NULL);
999 gboolean plainrar = (population_p->en->filetype && strstr (population_p->en->filetype, "RAR archive") != NULL);
1000 if(OpenDocument || plainzip || plainrar) {
1001 const gchar *function=NULL;
1002 if (OpenDocument) function = "get_zip_preview";
1003 else if (plainzip) function = "get_zip_image";
1004 else if (plainrar) function = "get_rar_image";
1005 else g_error("bummer at mime_preview()\n");
1006
1007 pixbuf = rfm_natural(RFM_MODULE_DIR, "mimezip", population_p->en->path, function); //refs
1008 if (pixbuf && GDK_IS_PIXBUF(pixbuf)) {
1009 // Mimezip function will ref to keep things standarized.
1010 // fix_pixbuf_scale unrefs and refs as needed.
1011 GdkPixbuf *old_pixbuf = pixbuf;
1012 pixbuf = fix_pixbuf_scale(old_pixbuf);
1013 // This may or may not be the same pixbuf.
1014 if (pixbuf != old_pixbuf) {
1015 rfm_pixbuf_save(pixbuf, thumbnail);
1016 }
1017 // Replace newly created pixbuf in pixbuf hash.
1018 // Reference will now belong to the hash table.
1019 rfm_put_in_pixbuf_hash(population_p->en->path, preview_size, pixbuf);
1020 } else {
1021 DBG ("Could not retrieve thumbnail from zipped %s\n",
1022 population_p->en->path);
1023 }
1024 g_free (thumbnail);
1025 if (!GDK_IS_PIXBUF(pixbuf)) return NULL;
1026
1027 return pixbuf;
1028 }
1029
1030 } else {
1031 NOOP(stderr, "mimezip not active\n");
1032 }
1033
1034
1035 // Ok, that didn't work either. Is it ghostscript (ps or pdf) or text?
1036 // this will construct the thumbnail in disk to load on next mousemove.
1037 const gchar *convert_type = want_imagemagick_preview (population_p->en);
1038
1039 if(!convert_type) {
1040 NOOP ("SHOW_TIPx: convert type=%s\n",convert_type);
1041 convert_type = "TXT";
1042 //g_free (thumbnail);
1043 //return NULL;
1044 }
1045
1046 // pdf forks to ghostscript to create thumbnail file
1047 pixbuf = NULL;
1048 if(strcmp (convert_type, "PDF") == 0 || strcmp (convert_type, "PDF") == 0) {
1049 // pixbuf generated and reffed in routine:
1050 pixbuf = image_magic_preview_forked (population_p, thumbnail, convert_type);// refs
1051 }
1052 // text uses pango cairo to create thumbnail file
1053 // default to text preview (even of binaries...)
1054 else // if(strcmp (convert_type, "TXT") == 0)
1055 {
1056 view_t *view_p = population_p->view_p;
1057 // pixbuf generated and reffed in routine:
1058 pixbuf = text_preview (population_p, thumbnail, view_p); // refs
1059 }
1060 g_free (thumbnail);
1061 if (!pixbuf || !GDK_IS_PIXBUF(pixbuf)) return NULL;
1062 // Replace newly created pixbuf in pixbuf hash.
1063 // Reference will now belong to the hash table.
1064 #if 0
1065 g_object_unref(pixbuf);
1066 #else
1067 rfm_put_in_pixbuf_hash(population_p->en->path, preview_size, pixbuf);
1068 #endif
1069 return pixbuf;
1070 }
1071
1072
1073 G_MODULE_EXPORT
1074 GdkPixbuf *
mime_preview(const population_t * population_p)1075 mime_preview (const population_t * population_p) {
1076 GdkPixbuf *pixbuf = mime_preview_at_size(population_p);
1077 return pixbuf;
1078 }
1079
1080
1081 ////////////////////////////////////////////////////////////////////////
1082