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