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