]> www.fi.muni.cz Git - evince.git/blob - backend/dvi/dvi-document.c
863276d76d5e2259084beeb0ec8cf0d84e6cbad8
[evince.git] / backend / dvi / dvi-document.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; c-indent-level: 8 -*- */
2 /*
3  * Copyright (C) 2005, Nickolay V. Shmyrev <nshmyrev@yandex.ru>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2, or (at your option)
8  * any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "dvi-document.h"
23 #include "ev-document-thumbnails.h"
24 #include "ev-document-misc.h"
25 #include "ev-file-exporter.h"
26 #include "ev-file-helpers.h"
27
28 #include "mdvi.h"
29 #include "fonts.h"
30 #include "color.h"
31 #include "cairo-device.h"
32
33 #include <glib/gi18n-lib.h>
34 #include <ctype.h>
35 #include <sys/wait.h>
36 #include <stdlib.h>
37
38 GMutex *dvi_context_mutex = NULL;
39
40 enum {
41         PROP_0,
42         PROP_TITLE
43 };
44
45 struct _DviDocumentClass
46 {
47         EvDocumentClass parent_class;
48 };
49
50 struct _DviDocument
51 {
52         EvDocument parent_instance;
53
54         DviContext *context;
55         DviPageSpec *spec;
56         DviParams *params;
57         
58         /* To let document scale we should remember width and height */
59         double base_width;
60         double base_height;
61         
62         gchar *uri;
63
64         /* PDF exporter */
65         gchar            *exporter_filename;
66         GString          *exporter_opts;
67 };
68
69 typedef struct _DviDocumentClass DviDocumentClass;
70
71 static void dvi_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface);
72 static void dvi_document_file_exporter_iface_init       (EvFileExporterIface       *iface);
73 static void dvi_document_do_color_special               (DviContext                *dvi,
74                                                          const char                *prefix,
75                                                          const char                *arg);
76
77 EV_BACKEND_REGISTER_WITH_CODE (DviDocument, dvi_document,
78      {
79       EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_DOCUMENT_THUMBNAILS, dvi_document_document_thumbnails_iface_init);
80       EV_BACKEND_IMPLEMENT_INTERFACE (EV_TYPE_FILE_EXPORTER, dvi_document_file_exporter_iface_init);
81      });
82
83 static gboolean
84 dvi_document_load (EvDocument  *document,
85                    const char  *uri,
86                    GError     **error)
87 {
88         gchar *filename;
89         DviDocument *dvi_document = DVI_DOCUMENT(document);
90         
91         filename = g_filename_from_uri (uri, NULL, error);
92         if (!filename)
93                 return FALSE;
94         
95         g_mutex_lock (dvi_context_mutex);
96         if (dvi_document->context)
97                 mdvi_destroy_context (dvi_document->context);
98
99         dvi_document->context = mdvi_init_context(dvi_document->params, dvi_document->spec, filename);
100         g_mutex_unlock (dvi_context_mutex);
101         g_free (filename);
102         
103         if (!dvi_document->context) {
104                 g_set_error_literal (error,
105                                      EV_DOCUMENT_ERROR,
106                                      EV_DOCUMENT_ERROR_INVALID,
107                                      _("DVI document has incorrect format"));
108                 return FALSE;
109         }
110         
111         mdvi_cairo_device_init (&dvi_document->context->device);
112         
113         
114         dvi_document->base_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv 
115                 + 2 * unit2pix(dvi_document->params->dpi, MDVI_HMARGIN) / dvi_document->params->hshrink;
116         
117         dvi_document->base_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv 
118                 + 2 * unit2pix(dvi_document->params->vdpi, MDVI_VMARGIN) / dvi_document->params->vshrink;
119         
120         g_free (dvi_document->uri);
121         dvi_document->uri = g_strdup (uri);
122         
123         return TRUE;
124 }
125
126
127 static gboolean
128 dvi_document_save (EvDocument  *document,
129                       const char  *uri,
130                       GError     **error)
131 {
132         DviDocument *dvi_document = DVI_DOCUMENT (document);
133
134         return ev_xfer_uri_simple (dvi_document->uri, uri, error);
135 }
136
137 static int
138 dvi_document_get_n_pages (EvDocument *document)
139 {
140         DviDocument *dvi_document = DVI_DOCUMENT (document);
141         
142         return dvi_document->context->npages;
143 }
144
145 static void
146 dvi_document_get_page_size (EvDocument *document,
147                             EvPage     *page,
148                             double     *width,
149                             double     *height)
150 {
151         DviDocument *dvi_document = DVI_DOCUMENT (document);    
152
153         *width = dvi_document->base_width;
154         *height = dvi_document->base_height;;
155 }
156
157 static cairo_surface_t *
158 dvi_document_render (EvDocument      *document,
159                      EvRenderContext *rc)
160 {
161         cairo_surface_t *surface;
162         cairo_surface_t *rotated_surface;
163         DviDocument *dvi_document = DVI_DOCUMENT(document);
164         gint required_width, required_height;
165         gint proposed_width, proposed_height;
166         gint xmargin = 0, ymargin = 0;
167
168         /* We should protect our context since it's not 
169          * thread safe. The work to the future - 
170          * let context render page independently
171          */
172         g_mutex_lock (dvi_context_mutex);
173         
174         mdvi_setpage (dvi_document->context, rc->page->index);
175         
176         mdvi_set_shrink (dvi_document->context, 
177                          (int)((dvi_document->params->hshrink - 1) / rc->scale) + 1,
178                          (int)((dvi_document->params->vshrink - 1) / rc->scale) + 1);
179
180         required_width = dvi_document->base_width * rc->scale + 0.5;
181         required_height = dvi_document->base_height * rc->scale + 0.5;
182         proposed_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv;
183         proposed_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv;
184         
185         if (required_width >= proposed_width)
186             xmargin = (required_width - proposed_width) / 2;
187         if (required_height >= proposed_height)
188             ymargin = (required_height - proposed_height) / 2;
189             
190         mdvi_cairo_device_set_margins (&dvi_document->context->device, xmargin, ymargin);
191         mdvi_cairo_device_set_scale (&dvi_document->context->device, rc->scale);
192         mdvi_cairo_device_render (dvi_document->context);
193         surface = mdvi_cairo_device_get_surface (&dvi_document->context->device);
194
195         g_mutex_unlock (dvi_context_mutex);
196
197         rotated_surface = ev_document_misc_surface_rotate_and_scale (surface,
198                                                                      required_width,
199                                                                      required_height, 
200                                                                      rc->rotation);
201         cairo_surface_destroy (surface);
202         
203         return rotated_surface;
204 }
205
206 static void
207 dvi_document_finalize (GObject *object)
208 {       
209         DviDocument *dvi_document = DVI_DOCUMENT(object);
210         
211         g_mutex_lock (dvi_context_mutex);
212         if (dvi_document->context) {
213                 mdvi_cairo_device_free (&dvi_document->context->device);
214                 mdvi_destroy_context (dvi_document->context);
215         }
216         g_mutex_unlock (dvi_context_mutex);
217
218         if (dvi_document->params)
219                 g_free (dvi_document->params);
220
221         if (dvi_document->exporter_filename)
222                 g_free (dvi_document->exporter_filename);
223         
224         if (dvi_document->exporter_opts)
225                 g_string_free (dvi_document->exporter_opts, TRUE);
226
227         g_free (dvi_document->uri);
228                 
229         G_OBJECT_CLASS (dvi_document_parent_class)->finalize (object);
230 }
231
232 static void
233 dvi_document_class_init (DviDocumentClass *klass)
234 {
235         GObjectClass    *gobject_class = G_OBJECT_CLASS (klass);
236         EvDocumentClass *ev_document_class = EV_DOCUMENT_CLASS (klass);
237
238         gobject_class->finalize = dvi_document_finalize;
239
240         mdvi_init_kpathsea ("evince", MDVI_MFMODE, MDVI_FALLBACK_FONT, MDVI_DPI);
241         mdvi_register_special ("Color", "color", NULL, dvi_document_do_color_special, 1);
242         mdvi_register_fonts ();
243
244         dvi_context_mutex = g_mutex_new ();
245
246         ev_document_class->load = dvi_document_load;
247         ev_document_class->save = dvi_document_save;
248         ev_document_class->get_n_pages = dvi_document_get_n_pages;
249         ev_document_class->get_page_size = dvi_document_get_page_size;
250         ev_document_class->render = dvi_document_render;
251 }
252
253 static void
254 dvi_document_thumbnails_get_dimensions (EvDocumentThumbnails *document,
255                                         EvRenderContext      *rc, 
256                                         gint                 *width,
257                                         gint                 *height)
258 {       
259         DviDocument *dvi_document = DVI_DOCUMENT (document);
260         gdouble page_width = dvi_document->base_width;
261         gdouble page_height = dvi_document->base_height;
262
263         if (rc->rotation == 90 || rc->rotation == 270) {
264                 *width = (gint) (page_height * rc->scale);
265                 *height = (gint) (page_width * rc->scale);
266         } else {
267                 *width = (gint) (page_width * rc->scale);
268                 *height = (gint) (page_height * rc->scale);
269         }
270 }
271
272 static GdkPixbuf *
273 dvi_document_thumbnails_get_thumbnail (EvDocumentThumbnails *document,
274                                        EvRenderContext      *rc,   
275                                        gboolean              border)
276 {
277         DviDocument *dvi_document = DVI_DOCUMENT (document);
278         GdkPixbuf *pixbuf;
279         GdkPixbuf *rotated_pixbuf;
280         cairo_surface_t *surface;
281         gint thumb_width, thumb_height;
282         gint proposed_width, proposed_height;
283
284         thumb_width = (gint) (dvi_document->base_width * rc->scale);
285         thumb_height = (gint) (dvi_document->base_height * rc->scale);
286
287         g_mutex_lock (dvi_context_mutex);
288         
289         mdvi_setpage (dvi_document->context, rc->page->index);
290
291         mdvi_set_shrink (dvi_document->context, 
292                           (int)dvi_document->base_width * dvi_document->params->hshrink / thumb_width,
293                           (int)dvi_document->base_height * dvi_document->params->vshrink / thumb_height);
294
295         proposed_width = dvi_document->context->dvi_page_w * dvi_document->context->params.conv;
296         proposed_height = dvi_document->context->dvi_page_h * dvi_document->context->params.vconv;
297                           
298         if (border) {
299                 mdvi_cairo_device_set_margins (&dvi_document->context->device, 
300                                                MAX (thumb_width - proposed_width, 0) / 2,
301                                                MAX (thumb_height - proposed_height, 0) / 2);    
302         } else {
303                 mdvi_cairo_device_set_margins (&dvi_document->context->device, 
304                                                MAX (thumb_width - proposed_width - 2, 0) / 2,
305                                                MAX (thumb_height - proposed_height - 2, 0) / 2);        
306         }
307
308         mdvi_cairo_device_set_scale (&dvi_document->context->device, rc->scale);
309         mdvi_cairo_device_render (dvi_document->context);
310         surface = mdvi_cairo_device_get_surface (&dvi_document->context->device);
311         g_mutex_unlock (dvi_context_mutex);
312
313         pixbuf = ev_document_misc_pixbuf_from_surface (surface);
314         cairo_surface_destroy (surface);
315
316         rotated_pixbuf = gdk_pixbuf_rotate_simple (pixbuf, 360 - rc->rotation);
317         g_object_unref (pixbuf);
318
319         if (border) {
320                 GdkPixbuf *tmp_pixbuf = rotated_pixbuf;
321
322                 rotated_pixbuf = ev_document_misc_get_thumbnail_frame (-1, -1, tmp_pixbuf);
323                 g_object_unref (tmp_pixbuf);
324         }
325
326         return rotated_pixbuf;
327 }
328
329 static void
330 dvi_document_document_thumbnails_iface_init (EvDocumentThumbnailsIface *iface)
331 {
332         iface->get_thumbnail = dvi_document_thumbnails_get_thumbnail;
333         iface->get_dimensions = dvi_document_thumbnails_get_dimensions;
334 }
335
336 /* EvFileExporterIface */
337 static void
338 dvi_document_file_exporter_begin (EvFileExporter        *exporter,
339                                   EvFileExporterContext *fc)
340 {
341         DviDocument *dvi_document = DVI_DOCUMENT(exporter);
342         
343         if (dvi_document->exporter_filename)
344                 g_free (dvi_document->exporter_filename);       
345         dvi_document->exporter_filename = g_strdup (fc->filename);
346         
347         if (dvi_document->exporter_opts) {
348                 g_string_free (dvi_document->exporter_opts, TRUE);
349         }
350         dvi_document->exporter_opts = g_string_new ("-s ");
351 }
352
353 static void
354 dvi_document_file_exporter_do_page (EvFileExporter  *exporter,
355                                     EvRenderContext *rc)
356 {
357        DviDocument *dvi_document = DVI_DOCUMENT(exporter);
358
359        g_string_append_printf (dvi_document->exporter_opts, "%d,", (rc->page->index) + 1);
360 }
361
362 static void
363 dvi_document_file_exporter_end (EvFileExporter *exporter)
364 {
365         gchar *command_line;
366         gint exit_stat;
367         GError *err = NULL;
368         gboolean success;
369         
370         DviDocument *dvi_document = DVI_DOCUMENT(exporter);
371         
372         command_line = g_strdup_printf ("dvipdfm %s -o %s \"%s\"", /* dvipdfm -s 1,2,.., -o exporter_filename dvi_filename */
373                                         dvi_document->exporter_opts->str,
374                                         dvi_document->exporter_filename,
375                                         dvi_document->context->filename);
376         
377         success = g_spawn_command_line_sync (command_line,
378                                              NULL,
379                                              NULL,
380                                              &exit_stat,
381                                              &err);
382
383         g_free (command_line);
384
385         if (success == FALSE) {
386                 g_warning ("Error: %s", err->message);
387         } else if (!WIFEXITED(exit_stat) || WEXITSTATUS(exit_stat) != EXIT_SUCCESS){
388                 g_warning ("Error: dvipdfm does not end normally or exit with a failure status.");
389         }
390
391         if (err)
392                 g_error_free (err);
393 }
394
395 static EvFileExporterCapabilities
396 dvi_document_file_exporter_get_capabilities (EvFileExporter *exporter)
397 {
398         return  EV_FILE_EXPORTER_CAN_PAGE_SET |
399                 EV_FILE_EXPORTER_CAN_COPIES |
400                 EV_FILE_EXPORTER_CAN_COLLATE |
401                 EV_FILE_EXPORTER_CAN_REVERSE |
402                 EV_FILE_EXPORTER_CAN_GENERATE_PDF;
403 }
404
405 static void
406 dvi_document_file_exporter_iface_init (EvFileExporterIface *iface)
407 {
408         iface->begin = dvi_document_file_exporter_begin;
409         iface->do_page = dvi_document_file_exporter_do_page;
410         iface->end = dvi_document_file_exporter_end;
411         iface->get_capabilities = dvi_document_file_exporter_get_capabilities;
412 }
413
414 #define RGB2ULONG(r,g,b) ((0xFF<<24)|(r<<16)|(g<<8)|(b))
415
416 static gboolean
417 hsb2rgb (float h, float s, float v, guchar *red, guchar *green, guchar *blue)
418 {
419         float i, f, p, q, t, r, g, b;
420
421         if (h == 360)
422                 h = 0;
423         else if ((h > 360) || (h < 0))
424                 return FALSE;
425
426         s /= 100;
427         v /= 100;
428         h /= 60;
429         i = floor (h);
430         f = h - i;
431         p = v * (1 - s);
432         q = v * (1 - (s * f));
433         t = v * (1 - (s * (1 - f)));
434
435         if (i == 0) {
436                 r = v;
437                 g = t;
438                 b = p;
439         } else if (i == 1) {
440                 r = q;
441                 g = v;
442                 b = p;
443         } else if (i == 2) {
444                 r = p;
445                 g = v;
446                 b = t;
447         } else if (i == 3) {
448                 r = p;
449                 g = q;
450                 b = v;
451         } else if (i == 4) {
452                 r = t;
453                 g = p;
454                 b = v;
455         } else if (i == 5) {
456                 r = v;
457                 g = p;
458                 b = q;
459         }
460
461         *red   = (guchar)floor(r * 255.0);
462         *green = (guchar)floor(g * 255.0);
463         *blue  = (guchar)floor(b * 255.0);
464         
465         return TRUE;
466 }
467
468 static void
469 parse_color (const gchar *ptr,
470              gdouble     *color,
471              gint         n_color)
472 {
473         gchar *p = (gchar *)ptr;
474         gint   i;
475
476         for (i = 0; i < n_color; i++) {
477                 while (isspace (*p)) p++;
478                 color[i] = g_ascii_strtod (p, NULL);
479                 while (!isspace (*p) && *p != '\0') p++;
480                 if (*p == '\0')
481                         break;
482         }
483 }
484
485 static void
486 dvi_document_do_color_special (DviContext *dvi, const char *prefix, const char *arg)
487 {
488         if (strncmp (arg, "pop", 3) == 0) {
489                 mdvi_pop_color (dvi);
490         } else if (strncmp (arg, "push", 4) == 0) {
491                 /* Find color source: Named, CMYK or RGB */
492                 const char *tmp = arg + 4;
493                 
494                 while (isspace (*tmp)) tmp++;
495
496                 if (!strncmp ("rgb", tmp, 3)) {
497                         gdouble rgb[3];
498                         guchar red, green, blue;
499
500                         parse_color (tmp + 4, rgb, 3);
501                         
502                         red = 255 * rgb[0];
503                         green = 255 * rgb[1];
504                         blue = 255 * rgb[2];
505
506                         mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF);
507                 } else if (!strncmp ("hsb", tmp, 4)) {
508                         gdouble hsb[3];
509                         guchar red, green, blue;
510
511                         parse_color (tmp + 4, hsb, 3);
512                         
513                         if (hsb2rgb (hsb[0], hsb[1], hsb[2], &red, &green, &blue))
514                                 mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF);
515                 } else if (!strncmp ("cmyk", tmp, 4)) {
516                         gdouble cmyk[4];
517                         double r, g, b;
518                         guchar red, green, blue;
519                         
520                         parse_color (tmp + 5, cmyk, 4);
521
522                         r = 1.0 - cmyk[0] - cmyk[3];
523                         if (r < 0.0)
524                                 r = 0.0;
525                         g = 1.0 - cmyk[1] - cmyk[3];
526                         if (g < 0.0)
527                                 g = 0.0;
528                         b = 1.0 - cmyk[2] - cmyk[3];
529                         if (b < 0.0)
530                                 b = 0.0;
531
532                         red = r * 255 + 0.5;
533                         green = g * 255 + 0.5;
534                         blue = b * 255 + 0.5;
535                         
536                         mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF);
537                 } else if (!strncmp ("gray ", tmp, 5)) {
538                         gdouble gray;
539                         guchar rgb;
540
541                         parse_color (tmp + 5, &gray, 1);
542
543                         rgb = gray * 255 + 0.5;
544
545                         mdvi_push_color (dvi, RGB2ULONG (rgb, rgb, rgb), 0xFFFFFFFF);
546                 } else {
547                         GdkColor color;
548                         
549                         if (gdk_color_parse (tmp, &color)) {
550                                 guchar red, green, blue;
551
552                                 red = color.red * 255 / 65535.;
553                                 green = color.green * 255 / 65535.;
554                                 blue = color.blue * 255 / 65535.;
555
556                                 mdvi_push_color (dvi, RGB2ULONG (red, green, blue), 0xFFFFFFFF);
557                         }
558                 }
559         }
560 }
561
562 static void
563 dvi_document_init_params (DviDocument *dvi_document)
564 {       
565         dvi_document->params = g_new0 (DviParams, 1);   
566
567         dvi_document->params->dpi      = MDVI_DPI;
568         dvi_document->params->vdpi     = MDVI_VDPI;
569         dvi_document->params->mag      = MDVI_MAGNIFICATION;
570         dvi_document->params->density  = MDVI_DEFAULT_DENSITY;
571         dvi_document->params->gamma    = MDVI_DEFAULT_GAMMA;
572         dvi_document->params->flags    = MDVI_PARAM_ANTIALIASED;
573         dvi_document->params->hdrift   = 0;
574         dvi_document->params->vdrift   = 0;
575         dvi_document->params->hshrink  =  MDVI_SHRINK_FROM_DPI(dvi_document->params->dpi);
576         dvi_document->params->vshrink  =  MDVI_SHRINK_FROM_DPI(dvi_document->params->vdpi);
577         dvi_document->params->orientation = MDVI_ORIENT_TBLR;
578
579         dvi_document->spec = NULL;
580         
581         dvi_document->params->bg = 0xffffffff;
582         dvi_document->params->fg = 0xff000000;
583 }
584
585 static void
586 dvi_document_init (DviDocument *dvi_document)
587 {
588         dvi_document->context = NULL;
589         dvi_document_init_params (dvi_document);
590
591         dvi_document->exporter_filename = NULL;
592         dvi_document->exporter_opts = NULL;
593 }