e8c38ddb58d8129cbae2896c06dd28e00cbeff42
[claws.git] / src / printing.c
1 /* Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
2  * Copyright (C) 2007 Holger Berndt <hb@claws-mail.org>,
3  * Colin Leroy <colin@colino.net>, and the Claws Mail team
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 3 of the License, or
8  * (at your option) 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, see <http://www.gnu.org/licenses/>.
17  * 
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "printing.h"
25 #include "image_viewer.h"
26
27 #if GTK_CHECK_VERSION(2,10,0) && !defined(USE_GNOMEPRINT)
28
29 #include "gtkutils.h"
30 #include "prefs_common.h"
31
32 #include <glib/gi18n.h>
33 #include <gdk/gdkkeysyms.h>
34 #include <gtk/gtk.h>
35 #include <pango/pango.h>
36 #include <string.h>
37 #include <math.h>
38
39 typedef struct {
40   PangoLayout *layout;
41   PangoContext *pango_context;
42   char *text;
43   GList *page_breaks;
44   guint npages;
45   GtkTextBuffer *buffer;
46   gint sel_start;
47   gint sel_end;
48   GHashTable *images;
49   gint img_cnt;
50   gboolean is_preview;
51 } PrintData;
52
53 typedef struct {
54   GtkPrintOperation *op;
55   GtkPrintOperationPreview *preview;
56   GtkWidget         *area;
57   PrintData         *print_data;
58   gdouble           dpi_x;
59   gdouble           dpi_y;
60   GtkWidget         *page_nr_label;
61   GList             *pages_to_print;
62   GList             *current_page;
63   GtkWidget *first;
64   GtkWidget *next;
65   GtkWidget *previous;
66   GtkWidget *last;
67   GtkWidget *close;
68   gboolean rendering;
69 } PreviewData;
70
71 /* callbacks */
72 static void     cb_begin_print(GtkPrintOperation*, GtkPrintContext*, gpointer);
73 static void     cb_draw_page(GtkPrintOperation*, GtkPrintContext*, gint,
74                              gpointer);
75 static gboolean cb_preview(GtkPrintOperation*, GtkPrintOperationPreview*,
76                            GtkPrintContext*, GtkWindow*, gpointer);
77 static void     cb_preview_destroy(GtkWindow*, gpointer);
78 static gboolean cb_preview_close(GtkWidget*, GdkEventAny*, gpointer);
79 static void     cb_preview_size_allocate(GtkWidget*, GtkAllocation*);
80 static void     cb_preview_ready(GtkPrintOperationPreview*,
81                                  GtkPrintContext*, gpointer);
82 static gboolean cb_preview_expose(GtkWidget*, GdkEventExpose*, gpointer);
83 static void     cb_preview_got_page_size(GtkPrintOperationPreview*,
84                                          GtkPrintContext*,
85                                          GtkPageSetup*, gpointer);
86 static void     cb_preview_go_first(GtkButton*, gpointer);
87 static void     cb_preview_go_previous(GtkButton*, gpointer);
88 static void     cb_preview_go_next(GtkButton*, gpointer);
89 static void     cb_preview_go_last(GtkButton*, gpointer);
90
91 /* variables */
92 static GtkPrintSettings *settings   = NULL;
93 static GtkPageSetup     *page_setup = NULL;
94
95 /* other static functions */
96 static void     printing_layout_set_text_attributes(PrintData*, GtkPrintContext *);
97 static gboolean printing_is_pango_gdk_color_equal(PangoColor*, GdkColor*); 
98 static gint     printing_text_iter_get_offset_bytes(PrintData *, const GtkTextIter*);
99
100 #define PREVIEW_SCALE 72
101
102 static void free_pixbuf(gpointer key, gpointer value, gpointer data)
103 {
104   PangoAttrShape *attr = (PangoAttrShape *) value;
105   g_object_unref(G_OBJECT(attr->data));
106 }
107
108 void printing_print(GtkTextView *text_view, GtkWindow *parent, gint sel_start, gint sel_end)
109 {
110   GtkPrintOperation *op;
111   GtkPrintOperationResult res;
112   PrintData *print_data;
113   GtkTextIter start, end;
114   GtkTextBuffer *buffer;
115
116   op = gtk_print_operation_new();
117
118   print_data = g_new0(PrintData,1);
119
120   print_data->images = g_hash_table_new(g_direct_hash, g_direct_equal);
121   print_data->pango_context=gtk_widget_get_pango_context(GTK_WIDGET(text_view));
122
123   /* get text */
124   buffer = gtk_text_view_get_buffer(text_view);
125   print_data->buffer = buffer;
126   print_data->sel_start = sel_start;
127   print_data->sel_end = sel_end;
128   if (print_data->sel_start < 0 || print_data->sel_end <= print_data->sel_start) {
129     gtk_text_buffer_get_start_iter(buffer, &start);
130     gtk_text_buffer_get_end_iter(buffer, &end);
131   } else {
132     gtk_text_buffer_get_iter_at_offset(buffer, &start, print_data->sel_start);
133     gtk_text_buffer_get_iter_at_offset(buffer, &end, print_data->sel_end);
134   }
135
136   print_data->text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
137
138   if (settings == NULL) {
139     settings = gtk_print_settings_new();
140     gtk_print_settings_set_use_color(settings, prefs_common.print_use_color);
141     gtk_print_settings_set_collate(settings, prefs_common.print_use_collate);
142     gtk_print_settings_set_reverse(settings, prefs_common.print_use_reverse);
143     gtk_print_settings_set_duplex(settings, prefs_common.print_use_duplex);
144   }
145   if (page_setup == NULL) {
146     page_setup = gtk_page_setup_new();
147     if (prefs_common.print_paper_type && *prefs_common.print_paper_type) {
148       GtkPaperSize *paper = gtk_paper_size_new(prefs_common.print_paper_type);
149       gtk_page_setup_set_paper_size(page_setup, paper);
150       gtk_paper_size_free(paper);
151     }
152     gtk_page_setup_set_orientation(page_setup, prefs_common.print_paper_orientation);
153   }
154   
155   /* Config for printing */
156   gtk_print_operation_set_print_settings(op, settings);
157   gtk_print_operation_set_default_page_setup(op, page_setup);
158
159   /* signals */
160   g_signal_connect(op, "begin_print", G_CALLBACK(cb_begin_print), print_data);
161   g_signal_connect(op, "draw_page", G_CALLBACK(cb_draw_page), print_data);
162   g_signal_connect(op, "preview", G_CALLBACK(cb_preview), print_data);
163
164   /* Start printing process */
165   res = gtk_print_operation_run(op, GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
166                                 parent, NULL);
167
168   if(res == GTK_PRINT_OPERATION_RESULT_ERROR) {
169     GError *error = NULL;
170     gtk_print_operation_get_error(op, &error);
171     debug_print("Error printing message: %s",
172             error ? error->message : "no details");
173   }
174   else if(res == GTK_PRINT_OPERATION_RESULT_APPLY) {
175     /* store settings for next printing session */
176     if(settings != NULL)
177       g_object_unref(settings);
178     settings = g_object_ref(gtk_print_operation_get_print_settings(op));
179     prefs_common.print_use_color = gtk_print_settings_get_use_color(settings);
180     prefs_common.print_use_collate = gtk_print_settings_get_collate(settings);
181     prefs_common.print_use_reverse = gtk_print_settings_get_reverse(settings);
182     prefs_common.print_use_duplex = gtk_print_settings_get_duplex(settings);
183   }
184
185   g_hash_table_foreach(print_data->images, free_pixbuf, NULL);
186   g_hash_table_destroy(print_data->images);
187   if(print_data->text)
188     g_free(print_data->text);
189   g_list_free(print_data->page_breaks);
190   if(print_data->layout)
191     g_object_unref(print_data->layout);
192
193   g_free(print_data);
194
195   g_object_unref(op);
196   debug_print("printing_print finished\n");
197 }
198
199 void printing_page_setup(GtkWindow *parent)
200 {
201   GtkPageSetup *new_page_setup;
202
203   if(settings == NULL) {
204     settings = gtk_print_settings_new();
205     gtk_print_settings_set_use_color(settings, prefs_common.print_use_color);
206     gtk_print_settings_set_collate(settings, prefs_common.print_use_collate);
207     gtk_print_settings_set_reverse(settings, prefs_common.print_use_reverse);
208     gtk_print_settings_set_duplex(settings, prefs_common.print_use_duplex);
209   }
210   if (page_setup == NULL) {
211     page_setup = gtk_page_setup_new();
212     if (prefs_common.print_paper_type && *prefs_common.print_paper_type) {
213       GtkPaperSize *paper = gtk_paper_size_new(prefs_common.print_paper_type);
214       gtk_page_setup_set_paper_size(page_setup, paper);
215       gtk_paper_size_free(paper);
216     }
217     gtk_page_setup_set_orientation(page_setup, prefs_common.print_paper_orientation);
218   }
219
220   new_page_setup = gtk_print_run_page_setup_dialog(parent,page_setup,settings);
221
222   if(page_setup)
223     g_object_unref(page_setup);
224
225   page_setup = new_page_setup;
226   
227   g_free(prefs_common.print_paper_type);
228   prefs_common.print_paper_type = g_strdup(gtk_paper_size_get_name(
229                 gtk_page_setup_get_paper_size(page_setup)));
230   prefs_common.print_paper_orientation = gtk_page_setup_get_orientation(page_setup);
231 }
232
233 static gboolean cb_preview(GtkPrintOperation        *operation,
234                            GtkPrintOperationPreview *preview,
235                            GtkPrintContext          *context,
236                            GtkWindow                *parent,
237                            gpointer                 data)
238 {
239   PrintData *print_data;
240   cairo_t *cr;
241   PreviewData *preview_data;
242   GtkWidget *vbox;
243   GtkWidget *hbox;
244   GtkWidget *scrolled_window;
245   GtkWidget *da;
246   GtkWidget *page;
247   static GdkGeometry geometry;
248   GtkWidget *dialog = NULL;
249
250   debug_print("Creating internal print preview\n");
251
252   print_data = (PrintData*) data;
253   print_data->is_preview = TRUE;
254
255   preview_data = g_new0(PreviewData,1);
256   preview_data->print_data = print_data;
257   preview_data->op = g_object_ref(operation);
258   preview_data->preview = preview;
259
260   /* Window */
261   dialog = gtkut_window_new(GTK_WINDOW_TOPLEVEL, "print_preview");
262   if(!geometry.min_height) {
263     geometry.min_width = 600;
264     geometry.min_height = 400;
265   }
266   gtk_window_set_geometry_hints(GTK_WINDOW(dialog), NULL, &geometry,
267                                 GDK_HINT_MIN_SIZE);
268   gtk_widget_set_size_request(dialog, prefs_common.print_previewwin_width,
269                               prefs_common.print_previewwin_height);
270   gtk_window_set_title(GTK_WINDOW(dialog), _("Print preview"));
271
272   /* vbox */
273   vbox = gtk_vbox_new(FALSE, 0);
274   gtk_container_add(GTK_CONTAINER(dialog), vbox);
275   
276   /* toolbar */
277   hbox = gtk_hbox_new (FALSE, 0);
278   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
279   preview_data->first = gtk_button_new_from_stock(GTK_STOCK_GOTO_FIRST);
280   gtk_box_pack_start(GTK_BOX(hbox), preview_data->first, FALSE, FALSE, 0);
281   preview_data->previous = gtk_button_new_from_stock(GTK_STOCK_GO_BACK);
282   gtk_box_pack_start(GTK_BOX(hbox), preview_data->previous, FALSE, FALSE, 0);
283   page = gtk_label_new("");
284   gtk_widget_set_size_request(page, 100, -1);
285   preview_data->page_nr_label = page;
286   gtk_box_pack_start(GTK_BOX(hbox), page, FALSE, FALSE, 0);
287   preview_data->next = gtk_button_new_from_stock(GTK_STOCK_GO_FORWARD);
288   gtk_box_pack_start(GTK_BOX(hbox), preview_data->next, FALSE, FALSE, 0);
289   preview_data->last = gtk_button_new_from_stock(GTK_STOCK_GOTO_LAST);
290   gtk_box_pack_start(GTK_BOX(hbox), preview_data->last, FALSE, FALSE, 0);
291   preview_data->close = gtk_button_new_from_stock(GTK_STOCK_CLOSE);
292   gtk_box_pack_start(GTK_BOX(hbox), preview_data->close, FALSE, FALSE, 0);
293
294   /* Drawing area */
295   scrolled_window = gtk_scrolled_window_new(NULL, NULL);
296   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
297                                  GTK_POLICY_AUTOMATIC,
298                                  GTK_POLICY_AUTOMATIC);
299   gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
300   da = gtk_drawing_area_new();
301   gtk_widget_set_double_buffered(da, FALSE);
302   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
303                                         da);
304   gtk_widget_realize(da);
305   preview_data->area = da;
306
307   /* cairo context */
308   cr = gdk_cairo_create(da->window);
309   gtk_print_context_set_cairo_context(context, cr, PREVIEW_SCALE, PREVIEW_SCALE);
310   cairo_destroy(cr);
311
312   /* signals */
313   g_signal_connect(dialog, "key_press_event",
314                    G_CALLBACK(cb_preview_close), preview_data);
315   g_signal_connect(dialog, "size_allocate",
316                    G_CALLBACK(cb_preview_size_allocate), NULL);
317   g_signal_connect(dialog, "destroy",G_CALLBACK(cb_preview_destroy),
318                    preview_data);
319   g_signal_connect_swapped(preview_data->close, "clicked",
320                            G_CALLBACK(gtk_widget_destroy), dialog);
321   g_signal_connect(preview_data->first, "clicked",
322                    G_CALLBACK(cb_preview_go_first), preview_data);
323   g_signal_connect(preview_data->previous, "clicked",
324                    G_CALLBACK(cb_preview_go_previous), preview_data);
325   g_signal_connect(preview_data->next, "clicked",
326                    G_CALLBACK(cb_preview_go_next), preview_data);
327   g_signal_connect(preview_data->last, "clicked",
328                    G_CALLBACK(cb_preview_go_last), preview_data);
329   g_signal_connect(preview, "ready", G_CALLBACK(cb_preview_ready),
330                    preview_data);
331   g_signal_connect(preview, "got-page-size",
332                    G_CALLBACK(cb_preview_got_page_size), preview_data);
333   gtk_widget_show_all(dialog);
334   return TRUE;
335 }
336
337 static void cb_preview_destroy(GtkWindow *window, gpointer data)
338 {
339   PreviewData *preview_data;
340   preview_data = (PreviewData*) data;
341
342   if (preview_data->rendering)
343     return;
344   debug_print("Preview window destroyed\n");
345
346   gtk_print_operation_preview_end_preview(preview_data->preview);
347   g_object_unref(preview_data->op);
348   g_list_free(preview_data->pages_to_print);
349
350   g_free(preview_data);
351 }
352
353 static gboolean cb_preview_close(GtkWidget *widget, GdkEventAny *event,
354                                  gpointer data)
355 {
356   PreviewData *preview_data = (PreviewData *)data;
357   if(event->type == GDK_KEY_PRESS)
358     if(((GdkEventKey *)event)->keyval != GDK_Escape)
359       return FALSE;
360   if (preview_data->rendering)
361     return FALSE; 
362   gtk_widget_destroy(widget);
363   return FALSE;
364 }
365
366 static void cb_preview_size_allocate(GtkWidget *widget,
367                                      GtkAllocation *allocation)
368 {
369   g_return_if_fail(allocation != NULL);
370
371   prefs_common.print_previewwin_width = allocation->width;
372   prefs_common.print_previewwin_height = allocation->height;
373 }
374
375 static void cb_preview_ready(GtkPrintOperationPreview *preview,
376                              GtkPrintContext          *context,
377                              gpointer                  data)
378 {
379   PreviewData *preview_data;
380   gint iPage;
381   preview_data = (PreviewData*) data;
382   debug_print("preview_ready %d\n", preview_data->print_data->npages);
383   
384   for(iPage = 0; iPage < (preview_data->print_data->npages); iPage++) {
385     if(gtk_print_operation_preview_is_selected(preview_data->preview, iPage)) {
386       preview_data->pages_to_print = 
387         g_list_prepend(preview_data->pages_to_print, GINT_TO_POINTER(iPage));
388       debug_print("want to print page %d\n",iPage+1);
389     }
390   }
391
392   preview_data->pages_to_print = g_list_reverse(preview_data->pages_to_print);
393   preview_data->current_page = preview_data->pages_to_print;
394
395   g_signal_connect(preview_data->area, "expose_event",
396                    G_CALLBACK(cb_preview_expose),
397                    preview_data);
398
399   gtk_widget_queue_draw(preview_data->area);
400 }
401
402 static void cb_preview_got_page_size(GtkPrintOperationPreview *preview,
403                                      GtkPrintContext          *context,
404                                      GtkPageSetup             *page_setup,
405                                      gpointer                  data)
406 {
407   PreviewData *preview_data;
408   GtkPageOrientation orientation;
409   GtkPaperSize *paper_size;
410   gdouble preview_width;
411   gdouble preview_height;
412   gdouble paper_width;
413   gdouble paper_height;
414   cairo_t *cr;
415   gdouble dpi_x;
416   gdouble dpi_y;
417
418   preview_data = (PreviewData*) data;
419   debug_print("got_page_size\n");
420   orientation  = gtk_page_setup_get_orientation(page_setup);
421   paper_size   = gtk_page_setup_get_paper_size(page_setup);
422   paper_width  = gtk_paper_size_get_width(paper_size, GTK_UNIT_INCH);
423   paper_height = gtk_paper_size_get_height(paper_size,  GTK_UNIT_INCH);
424
425   if((orientation == GTK_PAGE_ORIENTATION_PORTRAIT) ||
426      (orientation == GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT)) {
427     preview_width  = paper_width;
428     preview_height = paper_height;
429   }
430   else {
431     preview_width  = paper_height;
432     preview_height = paper_width;
433   }
434
435   debug_print("w/h %f/%f\n", paper_width * PREVIEW_SCALE, paper_height * PREVIEW_SCALE);
436   gtk_widget_set_size_request(GTK_WIDGET(preview_data->area), 
437                   (gint) paper_width * PREVIEW_SCALE, 
438                   (gint) paper_height * PREVIEW_SCALE);
439 }
440
441 static gboolean cb_preview_expose(GtkWidget *widget, GdkEventExpose *event,
442                                   gpointer data)
443 {
444   PreviewData *preview_data = data;
445   debug_print("preview_expose (current %p)\n", preview_data->current_page);
446   gdk_window_clear(preview_data->area->window);
447   if(preview_data->current_page) {
448     preview_data->rendering = TRUE;
449     gtk_widget_set_sensitive(preview_data->close, FALSE);
450     int cur = GPOINTER_TO_INT(preview_data->current_page->data);
451     gchar *str;
452     str = g_strdup_printf(_("Page %d"), cur+1);
453     gtk_label_set_text(GTK_LABEL(preview_data->page_nr_label), str);
454     g_free(str);
455     gtk_print_operation_preview_render_page(preview_data->preview,
456                                             GPOINTER_TO_INT
457                                             (preview_data->current_page->data));
458
459     gtk_widget_set_sensitive(preview_data->first, preview_data->current_page->prev != NULL);
460     gtk_widget_set_sensitive(preview_data->previous, preview_data->current_page->prev != NULL);
461     gtk_widget_set_sensitive(preview_data->next, preview_data->current_page->next != NULL);
462     gtk_widget_set_sensitive(preview_data->last, preview_data->current_page->next != NULL);
463     gtk_widget_set_sensitive(preview_data->close, TRUE);
464     preview_data->rendering = FALSE;
465
466   }
467   return TRUE;
468 }
469
470 static void cb_preview_go_first(GtkButton *button, gpointer data)
471 {
472   PreviewData *preview_data = (PreviewData*) data;
473   preview_data->current_page = preview_data->pages_to_print;
474   gtk_widget_queue_draw(preview_data->area);
475 }
476
477 static void cb_preview_go_previous(GtkButton *button, gpointer data)
478 {
479   GList *next;
480   PreviewData *preview_data = (PreviewData*) data;
481   next = g_list_previous(preview_data->current_page);
482   if(next)
483     preview_data->current_page = next;
484   gtk_widget_queue_draw(preview_data->area);
485 }
486
487 static void cb_preview_go_next(GtkButton *button, gpointer data)
488 {
489   GList *next;
490   PreviewData *preview_data = (PreviewData*) data;
491   next = g_list_next(preview_data->current_page);
492   if(next)
493     preview_data->current_page = next;
494   gtk_widget_queue_draw(preview_data->area);
495 }
496
497 static void cb_preview_go_last(GtkButton *button, gpointer data)
498 {
499   PreviewData *preview_data = (PreviewData*) data;
500   preview_data->current_page = g_list_last(preview_data->current_page);
501   gtk_widget_queue_draw(preview_data->area);
502 }
503
504 static void cb_begin_print(GtkPrintOperation *op, GtkPrintContext *context,
505                            gpointer user_data)
506 {
507   double width, height;
508   int num_lines;
509   double page_height;
510   GList *page_breaks;
511   PrintData *print_data;
512   PangoFontDescription *desc;
513   int start, ii;
514   PangoLayoutIter *iter;
515   double start_pos;
516   double line_height =0.;
517
518   print_data = (PrintData*) user_data;
519
520   debug_print("Preparing print job...\n");
521         
522   width  = gtk_print_context_get_width(context);
523   height = gtk_print_context_get_height(context);
524
525   if (print_data->layout == NULL)
526     print_data->layout = gtk_print_context_create_pango_layout(context);
527         
528   if(prefs_common.use_different_print_font)
529     desc = pango_font_description_from_string(prefs_common.printfont);
530   else
531     desc = pango_font_description_copy(
532                 pango_context_get_font_description(print_data->pango_context));
533
534   pango_layout_set_font_description(print_data->layout, desc);
535   pango_font_description_free(desc);
536
537   pango_layout_set_width(print_data->layout, width * PANGO_SCALE);
538   pango_layout_set_text(print_data->layout, print_data->text, -1);
539
540   printing_layout_set_text_attributes(print_data, context);
541
542   num_lines = pango_layout_get_line_count(print_data->layout);
543
544   page_breaks = NULL;
545   page_height = 0;
546   start = 0;
547   ii = 0;
548   start_pos = 0.;
549   iter = pango_layout_get_iter(print_data->layout);
550   do {
551     PangoRectangle logical_rect;
552     PangoLayoutLine *line;
553     PangoAttrShape *attr = NULL;
554     int baseline;
555
556     if(ii >= start) {
557       line = pango_layout_iter_get_line(iter);
558
559       pango_layout_iter_get_line_extents(iter, NULL, &logical_rect);
560       baseline = pango_layout_iter_get_baseline(iter);
561
562       if ((attr = g_hash_table_lookup(print_data->images, GINT_TO_POINTER(pango_layout_iter_get_index(iter)))) != NULL) {
563         line_height = (double)gdk_pixbuf_get_height(GDK_PIXBUF(attr->data));
564       } else {
565         line_height = ((double)logical_rect.height) / PANGO_SCALE;
566       }
567     }
568     if((page_height + line_height) > height) {
569       page_breaks = g_list_prepend(page_breaks, GINT_TO_POINTER(ii));
570       page_height = 0;
571     }
572
573     page_height += line_height;
574     ii++;
575   } while(ii < num_lines && pango_layout_iter_next_line(iter));
576   pango_layout_iter_free(iter);
577
578   page_breaks = g_list_reverse(page_breaks);
579   print_data->npages = g_list_length(page_breaks) + 1;  
580   print_data->page_breaks = page_breaks;
581
582   gtk_print_operation_set_n_pages(op, print_data->npages);
583
584   debug_print("Starting print job...\n");
585 }
586
587 static cairo_surface_t *pixbuf_to_surface(GdkPixbuf *pixbuf)
588 {
589           cairo_surface_t *surface;
590           cairo_format_t format;
591           static const cairo_user_data_key_t key;
592           guchar *pixels = g_malloc(
593                                 4*
594                                 gdk_pixbuf_get_width(pixbuf)*
595                                 gdk_pixbuf_get_height(pixbuf));
596           guchar *src_pixels = gdk_pixbuf_get_pixels (pixbuf);
597           gint width = gdk_pixbuf_get_width(pixbuf);
598           gint height = gdk_pixbuf_get_height(pixbuf);
599           gint nchans = gdk_pixbuf_get_n_channels (pixbuf);
600           gint stride = gdk_pixbuf_get_rowstride (pixbuf);
601           gint j;
602
603           if (nchans == 3)
604             format = CAIRO_FORMAT_RGB24;
605           else
606             format = CAIRO_FORMAT_ARGB32;
607           surface = cairo_image_surface_create_for_data (pixels,         
608                                                 format, width, height, 4*width);
609           cairo_surface_set_user_data (surface, &key, 
610                 pixels, (cairo_destroy_func_t)g_free);
611
612           for (j = height; j; j--) {
613                 guchar *p = src_pixels;
614                 guchar *q = pixels;
615
616                 if (nchans == 3) {
617                         guchar *end = p + 3 * width;
618
619                         while (p < end) {
620 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
621                                 q[0] = p[2];
622                                 q[1] = p[1];
623                                 q[2] = p[0];
624 #else
625                                 q[1] = p[0];
626                                 q[2] = p[1];
627                                 q[3] = p[2];
628 #endif
629                                 p += 3;
630                                 q += 4;
631                         }
632                 } else {
633                         guchar *end = p + 4 * width;
634                         guint t1,t2,t3;
635
636 #define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END
637
638                         while (p < end) {
639 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
640                                 MULT(q[0], p[2], p[3], t1);
641                                 MULT(q[1], p[1], p[3], t2);
642                                 MULT(q[2], p[0], p[3], t3);
643                                 q[3] = p[3];
644 #else
645                                 q[0] = p[3];
646                                 MULT(q[1], p[0], p[3], t1);
647                                 MULT(q[2], p[1], p[3], t2);
648                                 MULT(q[3], p[2], p[3], t3);
649 #endif
650
651                                 p += 4;
652                                 q += 4;
653                         }
654
655 #undef MULT
656                 }
657
658                 src_pixels += stride;
659                 pixels += 4 * width;
660         }
661                 
662         return surface;
663 }
664
665 static void cb_draw_page(GtkPrintOperation *op, GtkPrintContext *context,
666                          int page_nr, gpointer user_data)
667 {
668   cairo_t *cr;
669   PrintData *print_data;
670   int start, end, ii;
671   GList *pagebreak;
672   PangoLayoutIter *iter;
673   double start_pos;
674   gboolean notlast = TRUE;
675
676   print_data = (PrintData*) user_data;
677
678   if (page_nr == 0) {
679     start = 0;
680   } else {
681     pagebreak = g_list_nth(print_data->page_breaks, page_nr - 1);
682     start = GPOINTER_TO_INT(pagebreak->data);
683   }
684
685   pagebreak = g_list_nth(print_data->page_breaks, page_nr);
686   if(pagebreak == NULL)
687     end = pango_layout_get_line_count(print_data->layout);
688   else
689     end = GPOINTER_TO_INT(pagebreak->data);
690
691   cr = gtk_print_context_get_cairo_context(context);
692   if (print_data->is_preview) {
693     cairo_set_source_rgb(cr, 1., 1., 1.);
694     cairo_paint(cr);
695   }
696   cairo_set_source_rgb(cr, 0., 0., 0.);
697
698   ii = 0;
699   start_pos = 0.;
700   iter = pango_layout_get_iter(print_data->layout);
701   do {
702     PangoRectangle logical_rect;
703     PangoLayoutLine *line;
704     PangoAttrShape *attr = NULL;
705     int baseline;
706
707     if(ii >= start) {
708       line = pango_layout_iter_get_line(iter);
709
710       pango_layout_iter_get_line_extents(iter, NULL, &logical_rect);
711       baseline = pango_layout_iter_get_baseline(iter);
712
713       if(ii == start)
714         start_pos = ((double)logical_rect.y) / PANGO_SCALE;
715       
716       cairo_move_to(cr,
717                     ((double)logical_rect.x) / PANGO_SCALE,
718                     ((double)baseline) / PANGO_SCALE - start_pos);
719                     
720       if ((attr = g_hash_table_lookup(print_data->images, GINT_TO_POINTER(pango_layout_iter_get_index(iter)))) != NULL) {
721           cairo_surface_t *surface;
722
723           surface = pixbuf_to_surface(GDK_PIXBUF(attr->data));
724           cairo_set_source_surface (cr, surface, 
725                 ((double)logical_rect.x) / PANGO_SCALE, 
726                 ((double)baseline) / PANGO_SCALE - start_pos);
727           cairo_paint (cr);
728           cairo_surface_destroy (surface);
729       } else {
730         pango_cairo_show_layout_line(cr, line);
731       }
732     }
733     ii++;
734   } while(ii < end && (notlast = pango_layout_iter_next_line(iter)));
735   pango_layout_iter_free(iter);
736   debug_print("Sent page %d to printer\n", page_nr+1);
737 }
738
739 static void printing_layout_set_text_attributes(PrintData *print_data, GtkPrintContext *context)
740 {
741   GtkTextIter iter;
742   PangoAttrList *attr_list;
743   PangoAttribute *attr;
744   GSList *open_attrs, *attr_walk;
745
746   attr_list = pango_attr_list_new();
747   if (print_data->sel_start < 0 || print_data->sel_end <= print_data->sel_start) {
748     gtk_text_buffer_get_start_iter(print_data->buffer, &iter);
749   } else {
750     gtk_text_buffer_get_iter_at_offset(print_data->buffer, &iter, print_data->sel_start);
751   }
752
753   open_attrs = NULL;
754   do {
755     gboolean fg_set, bg_set, under_set, strike_set;
756     GSList *tags, *tag_walk;
757     GtkTextTag *tag;
758     GdkColor *color;
759     PangoUnderline underline;
760     gboolean strikethrough;
761     GdkPixbuf *image;
762
763     if (prefs_common.print_imgs && (image = gtk_text_iter_get_pixbuf(&iter)) != NULL) {
764       PangoRectangle rect = {0, 0, 0, 0};
765       gint startpos = printing_text_iter_get_offset_bytes(print_data, &iter);
766       gint h = gdk_pixbuf_get_height(image);
767       gint w = gdk_pixbuf_get_width(image);
768       gint a_h = gtk_print_context_get_height(context);
769       gint a_w = gtk_print_context_get_width(context);
770       gint r_h, r_w;
771       GdkPixbuf *scaled = NULL;
772       image_viewer_get_resized_size(w, h, a_w, a_h, &r_w, &r_h);
773       rect.x = 0;
774       rect.y = 0;
775       rect.width = r_w * PANGO_SCALE;
776       rect.height = r_h * PANGO_SCALE;
777       
778       scaled = gdk_pixbuf_scale_simple(image, r_w, r_h, GDK_INTERP_BILINEAR);
779       attr = pango_attr_shape_new_with_data (&rect, &rect,
780                scaled, NULL, NULL);
781       attr->start_index = startpos;
782       attr->end_index = startpos+1;
783       pango_attr_list_insert(attr_list, attr);
784       g_hash_table_insert(print_data->images, GINT_TO_POINTER(startpos), attr);
785       print_data->img_cnt++;
786     }
787
788     if(gtk_text_iter_ends_tag(&iter, NULL)) {
789       PangoAttrColor *attr_color;
790       PangoAttrInt   *attr_int;
791
792       tags = gtk_text_iter_get_toggled_tags(&iter, FALSE);
793       for(tag_walk = tags; tag_walk != NULL; tag_walk = tag_walk->next) {
794         gboolean found;
795
796         tag = GTK_TEXT_TAG(tag_walk->data);
797         g_object_get(G_OBJECT(tag),
798                      "background-set", &bg_set,
799                      "foreground-set", &fg_set,
800                      "underline-set",&under_set,
801                      "strikethrough-set", &strike_set,
802                      NULL);
803
804         if(fg_set) {
805           found = FALSE;
806           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
807             attr = (PangoAttribute*)attr_walk->data;
808             if(attr->klass->type == PANGO_ATTR_FOREGROUND) {
809               attr_color = (PangoAttrColor*) attr;
810               g_object_get(G_OBJECT(tag), "foreground_gdk", &color, NULL);
811               if(printing_is_pango_gdk_color_equal(&(attr_color->color), color)) {
812                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
813                 pango_attr_list_insert(attr_list, attr);
814                 found = TRUE;
815                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
816                 break;
817               }
818             }
819           }
820           if(!found)
821             debug_print("Error generating attribute list.\n");
822         }
823
824         if(bg_set) {
825           found = FALSE;
826           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
827             attr = (PangoAttribute*)attr_walk->data;
828             if(attr->klass->type == PANGO_ATTR_BACKGROUND) {
829               attr_color = (PangoAttrColor*) attr;
830               g_object_get(G_OBJECT(tag), "background-gdk", &color, NULL);
831               if(printing_is_pango_gdk_color_equal(&(attr_color->color), color)) {
832                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
833                 pango_attr_list_insert(attr_list, attr);
834                 found = TRUE;
835                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
836                 break;
837               }
838             }
839           }
840           if(!found)
841             debug_print("Error generating attribute list.\n");
842         }
843
844         if(under_set) {
845           found = FALSE;
846           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
847             attr = (PangoAttribute*)attr_walk->data;
848             if(attr->klass->type == PANGO_ATTR_UNDERLINE) {
849               attr_int = (PangoAttrInt*)attr;
850               g_object_get(G_OBJECT(tag), "underline", &underline, NULL);
851               if(attr_int->value == underline) {
852                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
853                 pango_attr_list_insert(attr_list, attr);
854                 found = TRUE;
855                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
856                 break;
857               }
858             }
859           }
860           if(!found)
861             debug_print("Error generating attribute list.\n");
862         }
863
864         if(strike_set) {
865           found = FALSE;
866           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
867             attr = (PangoAttribute*)attr_walk->data;
868             if(attr->klass->type == PANGO_ATTR_STRIKETHROUGH) {
869               attr_int = (PangoAttrInt*)attr;
870               g_object_get(G_OBJECT(tag), "strikethrough", &strikethrough, NULL);
871               if(attr_int->value == strikethrough) {
872                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
873                 pango_attr_list_insert(attr_list, attr);
874                 found = TRUE;
875                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
876                 break;
877               }
878             }
879           }
880           if(!found)
881             debug_print("Error generating attribute list.\n");
882         }
883
884       }
885       g_slist_free(tags);
886     }
887
888     if(gtk_text_iter_begins_tag(&iter, NULL)) {
889       tags = gtk_text_iter_get_toggled_tags(&iter, TRUE);
890       for(tag_walk = tags; tag_walk != NULL; tag_walk = tag_walk->next) {
891         tag = GTK_TEXT_TAG(tag_walk->data);
892         g_object_get(G_OBJECT(tag),
893                      "background-set", &bg_set,
894                      "foreground-set", &fg_set,
895                      "underline-set", &under_set,
896                      "strikethrough-set", &strike_set,
897                      NULL);
898         if(fg_set) {
899           g_object_get(G_OBJECT(tag), "foreground-gdk", &color, NULL);
900           attr = pango_attr_foreground_new(color->red,color->green,color->blue);
901           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
902           open_attrs = g_slist_prepend(open_attrs, attr);
903         }
904         if(bg_set) {
905           g_object_get(G_OBJECT(tag), "background-gdk", &color, NULL);
906           attr = pango_attr_background_new(color->red,color->green,color->blue);
907           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
908           open_attrs = g_slist_prepend(open_attrs, attr);
909         }
910         if(under_set) {
911           g_object_get(G_OBJECT(tag), "underline", &underline, NULL);
912           attr = pango_attr_underline_new(underline);
913           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
914           open_attrs = g_slist_prepend(open_attrs, attr);         
915         }
916         if(strike_set) {
917           g_object_get(G_OBJECT(tag), "strikethrough", &strikethrough, NULL);
918           attr = pango_attr_strikethrough_new(strikethrough);
919           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
920           open_attrs = g_slist_prepend(open_attrs, attr);         
921         }
922       }
923       g_slist_free(tags);
924     }
925     
926   } while(!gtk_text_iter_is_end(&iter) && gtk_text_iter_forward_to_tag_toggle(&iter, NULL));
927           
928   /* close all open attributes */
929   for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
930     attr = (PangoAttribute*) attr_walk->data;
931     attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
932     pango_attr_list_insert(attr_list, attr);
933   }
934   g_slist_free(open_attrs);
935
936   pango_layout_set_attributes(print_data->layout, attr_list);
937   pango_attr_list_unref(attr_list);
938 }
939
940 static gboolean printing_is_pango_gdk_color_equal(PangoColor *p, GdkColor *g)
941 {
942   return ((p->red == g->red) && (p->green == g->green) && (p->blue == g->blue));
943 }
944
945 /* Pango has it's attribute in bytes, but GtkTextIter gets only an offset
946  * in characters, so here we're returning an offset in bytes. 
947  */
948 static gint printing_text_iter_get_offset_bytes(PrintData *print_data, const GtkTextIter *iter)
949 {
950   gint off_chars;
951   gint off_bytes;
952   gchar *text;
953   GtkTextIter start;
954
955   off_chars = gtk_text_iter_get_offset(iter);
956   if (print_data->sel_start < 0 || print_data->sel_end <= print_data->sel_start) {
957     gtk_text_buffer_get_start_iter(gtk_text_iter_get_buffer(iter), &start);
958   } else {
959     gtk_text_buffer_get_iter_at_offset(gtk_text_iter_get_buffer(iter), &start, print_data->sel_start);
960   }
961   text = gtk_text_iter_get_text(&start, iter);
962   off_bytes = strlen(text);
963   g_free(text);
964   return off_bytes;
965 }
966
967 #endif /* GTK+ >= 2.10.0 */