2007-09-19 [colin] 3.0.1cvs9
[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_widget_set_size_request(GTK_WIDGET(da), 400, 500);
303   gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window),
304                                         da);
305   gtk_widget_realize(da);
306   preview_data->area = da;
307
308   /* cairo context */
309   cr = gdk_cairo_create(da->window);
310   gtk_print_context_set_cairo_context(context, cr, PREVIEW_SCALE, PREVIEW_SCALE);
311   cairo_destroy(cr);
312
313   /* signals */
314   g_signal_connect(dialog, "key_press_event",
315                    G_CALLBACK(cb_preview_close), preview_data);
316   g_signal_connect(dialog, "size_allocate",
317                    G_CALLBACK(cb_preview_size_allocate), NULL);
318   g_signal_connect(dialog, "destroy",G_CALLBACK(cb_preview_destroy),
319                    preview_data);
320   g_signal_connect_swapped(preview_data->close, "clicked",
321                            G_CALLBACK(gtk_widget_destroy), dialog);
322   g_signal_connect(preview_data->first, "clicked",
323                    G_CALLBACK(cb_preview_go_first), preview_data);
324   g_signal_connect(preview_data->previous, "clicked",
325                    G_CALLBACK(cb_preview_go_previous), preview_data);
326   g_signal_connect(preview_data->next, "clicked",
327                    G_CALLBACK(cb_preview_go_next), preview_data);
328   g_signal_connect(preview_data->last, "clicked",
329                    G_CALLBACK(cb_preview_go_last), preview_data);
330   g_signal_connect(preview, "ready", G_CALLBACK(cb_preview_ready),
331                    preview_data);
332   g_signal_connect(preview, "got-page-size",
333                    G_CALLBACK(cb_preview_got_page_size), preview_data);
334   gtk_widget_show_all(dialog);
335   return TRUE;
336 }
337
338 static void cb_preview_destroy(GtkWindow *window, gpointer data)
339 {
340   PreviewData *preview_data;
341   preview_data = (PreviewData*) data;
342
343   if (preview_data->rendering)
344     return;
345   debug_print("Preview window destroyed\n");
346
347   gtk_print_operation_preview_end_preview(preview_data->preview);
348   g_object_unref(preview_data->op);
349   g_list_free(preview_data->pages_to_print);
350
351   g_free(preview_data);
352 }
353
354 static gboolean cb_preview_close(GtkWidget *widget, GdkEventAny *event,
355                                  gpointer data)
356 {
357   PreviewData *preview_data = (PreviewData *)data;
358   if(event->type == GDK_KEY_PRESS)
359     if(((GdkEventKey *)event)->keyval != GDK_Escape)
360       return FALSE;
361   if (preview_data->rendering)
362     return FALSE; 
363   gtk_widget_destroy(widget);
364   return FALSE;
365 }
366
367 static void cb_preview_size_allocate(GtkWidget *widget,
368                                      GtkAllocation *allocation)
369 {
370   g_return_if_fail(allocation != NULL);
371
372   prefs_common.print_previewwin_width = allocation->width;
373   prefs_common.print_previewwin_height = allocation->height;
374 }
375
376 static void cb_preview_ready(GtkPrintOperationPreview *preview,
377                              GtkPrintContext          *context,
378                              gpointer                  data)
379 {
380   PreviewData *preview_data;
381   gint iPage;
382   preview_data = (PreviewData*) data;
383   debug_print("preview_ready %d\n", preview_data->print_data->npages);
384   
385   for(iPage = 0; iPage < (preview_data->print_data->npages); iPage++) {
386     if(gtk_print_operation_preview_is_selected(preview_data->preview, iPage)) {
387       preview_data->pages_to_print = 
388         g_list_prepend(preview_data->pages_to_print, GINT_TO_POINTER(iPage));
389       debug_print("want to print page %d\n",iPage+1);
390     }
391   }
392
393   preview_data->pages_to_print = g_list_reverse(preview_data->pages_to_print);
394   preview_data->current_page = preview_data->pages_to_print;
395
396   g_signal_connect(preview_data->area, "expose_event",
397                    G_CALLBACK(cb_preview_expose),
398                    preview_data);
399
400   gtk_widget_queue_draw(preview_data->area);
401 }
402
403 static void cb_preview_got_page_size(GtkPrintOperationPreview *preview,
404                                      GtkPrintContext          *context,
405                                      GtkPageSetup             *page_setup,
406                                      gpointer                  data)
407 {
408   PreviewData *preview_data;
409   GtkPageOrientation orientation;
410   GtkPaperSize *paper_size;
411   gdouble preview_width;
412   gdouble preview_height;
413   gdouble paper_width;
414   gdouble paper_height;
415   cairo_t *cr;
416   gdouble dpi_x;
417   gdouble dpi_y;
418
419   preview_data = (PreviewData*) data;
420   debug_print("got_page_size\n");
421   orientation  = gtk_page_setup_get_orientation(page_setup);
422   paper_size   = gtk_page_setup_get_paper_size(page_setup);
423   paper_width  = gtk_paper_size_get_width(paper_size, GTK_UNIT_INCH);
424   paper_height = gtk_paper_size_get_height(paper_size,  GTK_UNIT_INCH);
425
426   if((orientation == GTK_PAGE_ORIENTATION_PORTRAIT) ||
427      (orientation == GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT)) {
428     preview_width  = paper_width;
429     preview_height = paper_height;
430   }
431   else {
432     preview_width  = paper_height;
433     preview_height = paper_width;
434   }
435
436   cr = gdk_cairo_create(preview_data->area->window);  
437
438   dpi_x = preview_data->area->allocation.width/preview_width;
439   dpi_y = preview_data->area->allocation.height/preview_height;
440
441   if((fabs(dpi_x - preview_data->dpi_x) > 0.001) ||
442      (fabs (dpi_y - preview_data->dpi_y) > 0.001))   {
443     gtk_print_context_set_cairo_context(context, cr, dpi_x, dpi_y);
444     preview_data->dpi_x = dpi_x;
445     preview_data->dpi_y = dpi_y;
446   }
447   pango_cairo_update_layout(cr, preview_data->print_data->layout);
448   cairo_destroy(cr);
449 }
450
451 static gboolean cb_preview_expose(GtkWidget *widget, GdkEventExpose *event,
452                                   gpointer data)
453 {
454   PreviewData *preview_data = data;
455   debug_print("preview_expose (current %p)\n", preview_data->current_page);
456   gdk_window_clear(preview_data->area->window);
457   if(preview_data->current_page) {
458     preview_data->rendering = TRUE;
459     gtk_widget_set_sensitive(preview_data->close, FALSE);
460     int cur = GPOINTER_TO_INT(preview_data->current_page->data);
461     gchar *str;
462     str = g_strdup_printf(_("Page %d"), cur+1);
463     gtk_label_set_text(GTK_LABEL(preview_data->page_nr_label), str);
464     g_free(str);
465     gtk_print_operation_preview_render_page(preview_data->preview,
466                                             GPOINTER_TO_INT
467                                             (preview_data->current_page->data));
468
469     gtk_widget_set_sensitive(preview_data->first, preview_data->current_page->prev != NULL);
470     gtk_widget_set_sensitive(preview_data->previous, preview_data->current_page->prev != NULL);
471     gtk_widget_set_sensitive(preview_data->next, preview_data->current_page->next != NULL);
472     gtk_widget_set_sensitive(preview_data->last, preview_data->current_page->next != NULL);
473     gtk_widget_set_sensitive(preview_data->close, TRUE);
474     preview_data->rendering = FALSE;
475
476   }
477   return TRUE;
478 }
479
480 static void cb_preview_go_first(GtkButton *button, gpointer data)
481 {
482   PreviewData *preview_data = (PreviewData*) data;
483   preview_data->current_page = preview_data->pages_to_print;
484   gtk_widget_queue_draw(preview_data->area);
485 }
486
487 static void cb_preview_go_previous(GtkButton *button, gpointer data)
488 {
489   GList *next;
490   PreviewData *preview_data = (PreviewData*) data;
491   next = g_list_previous(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_next(GtkButton *button, gpointer data)
498 {
499   GList *next;
500   PreviewData *preview_data = (PreviewData*) data;
501   next = g_list_next(preview_data->current_page);
502   if(next)
503     preview_data->current_page = next;
504   gtk_widget_queue_draw(preview_data->area);
505 }
506
507 static void cb_preview_go_last(GtkButton *button, gpointer data)
508 {
509   PreviewData *preview_data = (PreviewData*) data;
510   preview_data->current_page = g_list_last(preview_data->current_page);
511   gtk_widget_queue_draw(preview_data->area);
512 }
513
514 static void cb_begin_print(GtkPrintOperation *op, GtkPrintContext *context,
515                            gpointer user_data)
516 {
517   double width, height;
518   int num_lines;
519   double page_height;
520   GList *page_breaks;
521   PrintData *print_data;
522   PangoFontDescription *desc;
523   int start, ii;
524   PangoLayoutIter *iter;
525   double start_pos;
526   double line_height =0.;
527
528   print_data = (PrintData*) user_data;
529
530   debug_print("Preparing print job...\n");
531         
532   width  = gtk_print_context_get_width(context);
533   height = gtk_print_context_get_height(context);
534
535   if (print_data->layout == NULL)
536     print_data->layout = gtk_print_context_create_pango_layout(context);
537         
538   if(prefs_common.use_different_print_font)
539     desc = pango_font_description_from_string(prefs_common.printfont);
540   else
541     desc = pango_font_description_copy(
542                 pango_context_get_font_description(print_data->pango_context));
543
544   pango_layout_set_font_description(print_data->layout, desc);
545   pango_font_description_free(desc);
546
547   pango_layout_set_width(print_data->layout, width * PANGO_SCALE);
548   pango_layout_set_text(print_data->layout, print_data->text, -1);
549
550   printing_layout_set_text_attributes(print_data, context);
551
552   num_lines = pango_layout_get_line_count(print_data->layout);
553
554   page_breaks = NULL;
555   page_height = 0;
556   start = 0;
557   ii = 0;
558   start_pos = 0.;
559   iter = pango_layout_get_iter(print_data->layout);
560   do {
561     PangoRectangle logical_rect;
562     PangoLayoutLine *line;
563     PangoAttrShape *attr = NULL;
564     int baseline;
565
566     if(ii >= start) {
567       line = pango_layout_iter_get_line(iter);
568
569       pango_layout_iter_get_line_extents(iter, NULL, &logical_rect);
570       baseline = pango_layout_iter_get_baseline(iter);
571
572       if ((attr = g_hash_table_lookup(print_data->images, GINT_TO_POINTER(pango_layout_iter_get_index(iter)))) != NULL) {
573         line_height = (double)gdk_pixbuf_get_height(GDK_PIXBUF(attr->data));
574       } else {
575         line_height = ((double)logical_rect.height) / PANGO_SCALE;
576       }
577     }
578     if((page_height + line_height) > height) {
579       page_breaks = g_list_prepend(page_breaks, GINT_TO_POINTER(ii));
580       page_height = 0;
581     }
582
583     page_height += line_height;
584     ii++;
585   } while(ii < num_lines && pango_layout_iter_next_line(iter));
586   pango_layout_iter_free(iter);
587
588   page_breaks = g_list_reverse(page_breaks);
589   print_data->npages = g_list_length(page_breaks) + 1;  
590   print_data->page_breaks = page_breaks;
591
592   gtk_print_operation_set_n_pages(op, print_data->npages);
593
594   debug_print("Starting print job...\n");
595 }
596
597 static cairo_surface_t *pixbuf_to_surface(GdkPixbuf *pixbuf)
598 {
599           cairo_surface_t *surface;
600           cairo_format_t format;
601           static const cairo_user_data_key_t key;
602           guchar *pixels = g_malloc(
603                                 4*
604                                 gdk_pixbuf_get_width(pixbuf)*
605                                 gdk_pixbuf_get_height(pixbuf));
606           guchar *src_pixels = gdk_pixbuf_get_pixels (pixbuf);
607           gint width = gdk_pixbuf_get_width(pixbuf);
608           gint height = gdk_pixbuf_get_height(pixbuf);
609           gint nchans = gdk_pixbuf_get_n_channels (pixbuf);
610           gint stride = gdk_pixbuf_get_rowstride (pixbuf);
611           gint j;
612
613           if (nchans == 3)
614             format = CAIRO_FORMAT_RGB24;
615           else
616             format = CAIRO_FORMAT_ARGB32;
617           surface = cairo_image_surface_create_for_data (pixels,         
618                                                 format, width, height, 4*width);
619           cairo_surface_set_user_data (surface, &key, 
620                 pixels, (cairo_destroy_func_t)g_free);
621
622           for (j = height; j; j--) {
623                 guchar *p = src_pixels;
624                 guchar *q = pixels;
625
626                 if (nchans == 3) {
627                         guchar *end = p + 3 * width;
628
629                         while (p < end) {
630 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
631                                 q[0] = p[2];
632                                 q[1] = p[1];
633                                 q[2] = p[0];
634 #else
635                                 q[1] = p[0];
636                                 q[2] = p[1];
637                                 q[3] = p[2];
638 #endif
639                                 p += 3;
640                                 q += 4;
641                         }
642                 } else {
643                         guchar *end = p + 4 * width;
644                         guint t1,t2,t3;
645
646 #define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END
647
648                         while (p < end) {
649 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
650                                 MULT(q[0], p[2], p[3], t1);
651                                 MULT(q[1], p[1], p[3], t2);
652                                 MULT(q[2], p[0], p[3], t3);
653                                 q[3] = p[3];
654 #else
655                                 q[0] = p[3];
656                                 MULT(q[1], p[0], p[3], t1);
657                                 MULT(q[2], p[1], p[3], t2);
658                                 MULT(q[3], p[2], p[3], t3);
659 #endif
660
661                                 p += 4;
662                                 q += 4;
663                         }
664
665 #undef MULT
666                 }
667
668                 src_pixels += stride;
669                 pixels += 4 * width;
670         }
671                 
672         return surface;
673 }
674
675 static void cb_draw_page(GtkPrintOperation *op, GtkPrintContext *context,
676                          int page_nr, gpointer user_data)
677 {
678   cairo_t *cr;
679   PrintData *print_data;
680   int start, end, ii;
681   GList *pagebreak;
682   PangoLayoutIter *iter;
683   double start_pos;
684   gboolean notlast = TRUE;
685
686   print_data = (PrintData*) user_data;
687
688   if (page_nr == 0) {
689     start = 0;
690   } else {
691     pagebreak = g_list_nth(print_data->page_breaks, page_nr - 1);
692     start = GPOINTER_TO_INT(pagebreak->data);
693   }
694
695   pagebreak = g_list_nth(print_data->page_breaks, page_nr);
696   if(pagebreak == NULL)
697     end = pango_layout_get_line_count(print_data->layout);
698   else
699     end = GPOINTER_TO_INT(pagebreak->data);
700
701   cr = gtk_print_context_get_cairo_context(context);
702   if (print_data->is_preview) {
703     cairo_set_source_rgb(cr, 1., 1., 1.);
704     cairo_paint(cr);
705   }
706   cairo_set_source_rgb(cr, 0., 0., 0.);
707
708   ii = 0;
709   start_pos = 0.;
710   iter = pango_layout_get_iter(print_data->layout);
711   do {
712     PangoRectangle logical_rect;
713     PangoLayoutLine *line;
714     PangoAttrShape *attr = NULL;
715     int baseline;
716
717     if(ii >= start) {
718       line = pango_layout_iter_get_line(iter);
719
720       pango_layout_iter_get_line_extents(iter, NULL, &logical_rect);
721       baseline = pango_layout_iter_get_baseline(iter);
722
723       if(ii == start)
724         start_pos = ((double)logical_rect.y) / PANGO_SCALE;
725       
726       cairo_move_to(cr,
727                     ((double)logical_rect.x) / PANGO_SCALE,
728                     ((double)baseline) / PANGO_SCALE - start_pos);
729                     
730       if ((attr = g_hash_table_lookup(print_data->images, GINT_TO_POINTER(pango_layout_iter_get_index(iter)))) != NULL) {
731           cairo_surface_t *surface;
732
733           surface = pixbuf_to_surface(GDK_PIXBUF(attr->data));
734           cairo_set_source_surface (cr, surface, 
735                 ((double)logical_rect.x) / PANGO_SCALE, 
736                 ((double)baseline) / PANGO_SCALE - start_pos);
737           cairo_paint (cr);
738           cairo_surface_destroy (surface);
739       } else {
740         pango_cairo_show_layout_line(cr, line);
741       }
742     }
743     ii++;
744   } while(ii < end && (notlast = pango_layout_iter_next_line(iter)));
745   pango_layout_iter_free(iter);
746   debug_print("Sent page %d to printer\n", page_nr+1);
747 }
748
749 static void printing_layout_set_text_attributes(PrintData *print_data, GtkPrintContext *context)
750 {
751   GtkTextIter iter;
752   PangoAttrList *attr_list;
753   PangoAttribute *attr;
754   GSList *open_attrs, *attr_walk;
755
756   attr_list = pango_attr_list_new();
757   if (print_data->sel_start < 0 || print_data->sel_end <= print_data->sel_start) {
758     gtk_text_buffer_get_start_iter(print_data->buffer, &iter);
759   } else {
760     gtk_text_buffer_get_iter_at_offset(print_data->buffer, &iter, print_data->sel_start);
761   }
762
763   open_attrs = NULL;
764   do {
765     gboolean fg_set, bg_set, under_set, strike_set;
766     GSList *tags, *tag_walk;
767     GtkTextTag *tag;
768     GdkColor *color;
769     PangoUnderline underline;
770     gboolean strikethrough;
771     GdkPixbuf *image;
772
773     if (prefs_common.print_imgs && (image = gtk_text_iter_get_pixbuf(&iter)) != NULL) {
774       PangoRectangle rect = {0, 0, 0, 0};
775       gint startpos = printing_text_iter_get_offset_bytes(print_data, &iter);
776       gint h = gdk_pixbuf_get_height(image);
777       gint w = gdk_pixbuf_get_width(image);
778       gint a_h = gtk_print_context_get_height(context);
779       gint a_w = gtk_print_context_get_width(context);
780       gint r_h, r_w;
781       GdkPixbuf *scaled = NULL;
782       image_viewer_get_resized_size(w, h, a_w, a_h, &r_w, &r_h);
783       rect.x = 0;
784       rect.y = 0;
785       rect.width = r_w * PANGO_SCALE;
786       rect.height = r_h * PANGO_SCALE;
787       
788       scaled = gdk_pixbuf_scale_simple(image, r_w, r_h, GDK_INTERP_BILINEAR);
789       attr = pango_attr_shape_new_with_data (&rect, &rect,
790                scaled, NULL, NULL);
791       attr->start_index = startpos;
792       attr->end_index = startpos+1;
793       pango_attr_list_insert(attr_list, attr);
794       g_hash_table_insert(print_data->images, GINT_TO_POINTER(startpos), attr);
795       print_data->img_cnt++;
796     }
797
798     if(gtk_text_iter_ends_tag(&iter, NULL)) {
799       PangoAttrColor *attr_color;
800       PangoAttrInt   *attr_int;
801
802       tags = gtk_text_iter_get_toggled_tags(&iter, FALSE);
803       for(tag_walk = tags; tag_walk != NULL; tag_walk = tag_walk->next) {
804         gboolean found;
805
806         tag = GTK_TEXT_TAG(tag_walk->data);
807         g_object_get(G_OBJECT(tag),
808                      "background-set", &bg_set,
809                      "foreground-set", &fg_set,
810                      "underline-set",&under_set,
811                      "strikethrough-set", &strike_set,
812                      NULL);
813
814         if(fg_set) {
815           found = FALSE;
816           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
817             attr = (PangoAttribute*)attr_walk->data;
818             if(attr->klass->type == PANGO_ATTR_FOREGROUND) {
819               attr_color = (PangoAttrColor*) attr;
820               g_object_get(G_OBJECT(tag), "foreground_gdk", &color, NULL);
821               if(printing_is_pango_gdk_color_equal(&(attr_color->color), color)) {
822                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
823                 pango_attr_list_insert(attr_list, attr);
824                 found = TRUE;
825                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
826                 break;
827               }
828             }
829           }
830           if(!found)
831             debug_print("Error generating attribute list.\n");
832         }
833
834         if(bg_set) {
835           found = FALSE;
836           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
837             attr = (PangoAttribute*)attr_walk->data;
838             if(attr->klass->type == PANGO_ATTR_BACKGROUND) {
839               attr_color = (PangoAttrColor*) attr;
840               g_object_get(G_OBJECT(tag), "background-gdk", &color, NULL);
841               if(printing_is_pango_gdk_color_equal(&(attr_color->color), color)) {
842                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
843                 pango_attr_list_insert(attr_list, attr);
844                 found = TRUE;
845                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
846                 break;
847               }
848             }
849           }
850           if(!found)
851             debug_print("Error generating attribute list.\n");
852         }
853
854         if(under_set) {
855           found = FALSE;
856           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
857             attr = (PangoAttribute*)attr_walk->data;
858             if(attr->klass->type == PANGO_ATTR_UNDERLINE) {
859               attr_int = (PangoAttrInt*)attr;
860               g_object_get(G_OBJECT(tag), "underline", &underline, NULL);
861               if(attr_int->value == underline) {
862                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
863                 pango_attr_list_insert(attr_list, attr);
864                 found = TRUE;
865                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
866                 break;
867               }
868             }
869           }
870           if(!found)
871             debug_print("Error generating attribute list.\n");
872         }
873
874         if(strike_set) {
875           found = FALSE;
876           for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
877             attr = (PangoAttribute*)attr_walk->data;
878             if(attr->klass->type == PANGO_ATTR_STRIKETHROUGH) {
879               attr_int = (PangoAttrInt*)attr;
880               g_object_get(G_OBJECT(tag), "strikethrough", &strikethrough, NULL);
881               if(attr_int->value == strikethrough) {
882                 attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
883                 pango_attr_list_insert(attr_list, attr);
884                 found = TRUE;
885                 open_attrs = g_slist_delete_link(open_attrs, attr_walk);
886                 break;
887               }
888             }
889           }
890           if(!found)
891             debug_print("Error generating attribute list.\n");
892         }
893
894       }
895       g_slist_free(tags);
896     }
897
898     if(gtk_text_iter_begins_tag(&iter, NULL)) {
899       tags = gtk_text_iter_get_toggled_tags(&iter, TRUE);
900       for(tag_walk = tags; tag_walk != NULL; tag_walk = tag_walk->next) {
901         tag = GTK_TEXT_TAG(tag_walk->data);
902         g_object_get(G_OBJECT(tag),
903                      "background-set", &bg_set,
904                      "foreground-set", &fg_set,
905                      "underline-set", &under_set,
906                      "strikethrough-set", &strike_set,
907                      NULL);
908         if(fg_set) {
909           g_object_get(G_OBJECT(tag), "foreground-gdk", &color, NULL);
910           attr = pango_attr_foreground_new(color->red,color->green,color->blue);
911           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
912           open_attrs = g_slist_prepend(open_attrs, attr);
913         }
914         if(bg_set) {
915           g_object_get(G_OBJECT(tag), "background-gdk", &color, NULL);
916           attr = pango_attr_background_new(color->red,color->green,color->blue);
917           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
918           open_attrs = g_slist_prepend(open_attrs, attr);
919         }
920         if(under_set) {
921           g_object_get(G_OBJECT(tag), "underline", &underline, NULL);
922           attr = pango_attr_underline_new(underline);
923           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
924           open_attrs = g_slist_prepend(open_attrs, attr);         
925         }
926         if(strike_set) {
927           g_object_get(G_OBJECT(tag), "strikethrough", &strikethrough, NULL);
928           attr = pango_attr_strikethrough_new(strikethrough);
929           attr->start_index = printing_text_iter_get_offset_bytes(print_data, &iter);
930           open_attrs = g_slist_prepend(open_attrs, attr);         
931         }
932       }
933       g_slist_free(tags);
934     }
935     
936   } while(!gtk_text_iter_is_end(&iter) && gtk_text_iter_forward_to_tag_toggle(&iter, NULL));
937           
938   /* close all open attributes */
939   for(attr_walk = open_attrs; attr_walk != NULL; attr_walk = attr_walk->next) {
940     attr = (PangoAttribute*) attr_walk->data;
941     attr->end_index = printing_text_iter_get_offset_bytes(print_data, &iter);
942     pango_attr_list_insert(attr_list, attr);
943   }
944   g_slist_free(open_attrs);
945
946   pango_layout_set_attributes(print_data->layout, attr_list);
947   pango_attr_list_unref(attr_list);
948 }
949
950 static gboolean printing_is_pango_gdk_color_equal(PangoColor *p, GdkColor *g)
951 {
952   return ((p->red == g->red) && (p->green == g->green) && (p->blue == g->blue));
953 }
954
955 /* Pango has it's attribute in bytes, but GtkTextIter gets only an offset
956  * in characters, so here we're returning an offset in bytes. 
957  */
958 static gint printing_text_iter_get_offset_bytes(PrintData *print_data, const GtkTextIter *iter)
959 {
960   gint off_chars;
961   gint off_bytes;
962   gchar *text;
963   GtkTextIter start;
964
965   off_chars = gtk_text_iter_get_offset(iter);
966   if (print_data->sel_start < 0 || print_data->sel_end <= print_data->sel_start) {
967     gtk_text_buffer_get_start_iter(gtk_text_iter_get_buffer(iter), &start);
968   } else {
969     gtk_text_buffer_get_iter_at_offset(gtk_text_iter_get_buffer(iter), &start, print_data->sel_start);
970   }
971   text = gtk_text_iter_get_text(&start, iter);
972   off_bytes = strlen(text);
973   g_free(text);
974   return off_bytes;
975 }
976
977 #endif /* GTK+ >= 2.10.0 */