c50a3c5d7e8f5363fd06195ca6621f18fe505a94
[colloquium.git] / src / print.c
1 /*
2  * print.c
3  *
4  * Copyright © 2016-2018 Thomas White <taw@bitwiz.org.uk>
5  *
6  * This file is part of Colloquium.
7  *
8  * Colloquium is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <gtk/gtk.h>
29 #include <assert.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "presentation.h"
34 #include "narrative_window.h"
35 #include "render.h"
36 #include "utils.h"
37
38
39 static GtkPrintSettings *print_settings = NULL;
40
41 struct print_stuff
42 {
43         struct presentation *p;
44
45         /* Printing config */
46         GtkWidget *combo;
47         int slides_only;
48
49         /* When printing slides only */
50         SCBlock *slide;
51
52         /* When printing narrative */
53         int nar_line;
54         struct frame *top;
55         int start_paras[256];
56         int slide_number;
57
58         ImageStore *is;
59         const char *storename;
60 };
61
62
63 static void print_widget_apply(GtkPrintOperation *op, GtkWidget *widget,
64                                void *vp)
65 {
66         const char *id;
67         struct print_stuff *ps = vp;
68
69         id = gtk_combo_box_get_active_id(GTK_COMBO_BOX(ps->combo));
70         if ( strcmp(id, "slides") == 0 ) {
71                 ps->slides_only = 1;
72         } else {
73                 ps->slides_only = 0;
74         }
75 }
76
77
78 static GObject *print_widget(GtkPrintOperation *op, void *vp)
79 {
80         GtkWidget *vbox;
81         GtkWidget *cbox;
82         struct print_stuff *ps = vp;
83
84         vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10);
85         gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
86
87         /* What do you want to print? */
88         cbox = gtk_combo_box_text_new();
89         gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(cbox), "slides",
90                                   _("Print the slides only"));
91         gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(cbox), "narrative",
92                                   _("Print the narrative"));
93         gtk_box_pack_start(GTK_BOX(vbox), cbox, FALSE, FALSE, 10);
94         gtk_combo_box_set_active(GTK_COMBO_BOX(cbox), 1);
95         ps->combo = cbox;
96
97         gtk_widget_show_all(vbox);
98         return G_OBJECT(vbox);
99
100 }
101
102
103 static void print_slide_only(GtkPrintOperation *op, GtkPrintContext *ctx,
104                              struct print_stuff *ps, int page)
105 {
106         cairo_t *cr;
107         PangoContext *pc;
108         double w, h;
109         struct frame *top;
110         const double sw = ps->p->slide_width;
111         const double sh = ps->p->slide_height;
112         double slide_width, slide_height;
113
114         cr = gtk_print_context_get_cairo_context(ctx);
115         pc = gtk_print_context_create_pango_context(ctx);
116         w = gtk_print_context_get_width(ctx);
117         h = gtk_print_context_get_height(ctx);
118
119         cairo_rectangle(cr, 0.0, 0.0, w, h);
120         cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
121         cairo_fill(cr);
122
123         if ( sw/sh > w/h ) {
124                 /* Slide is too wide.  Letterboxing top/bottom */
125                 slide_width = w;
126                 slide_height = w * sh/sw;
127         } else {
128                 /* Letterboxing at sides */
129                 slide_width = h * sw/sh;
130                 slide_height = h;
131         }
132
133         printf("%f x %f ---> %f x %f\n", w, h, slide_width, slide_height);
134
135         top = interp_and_shape(ps->slide, ps->p->stylesheet, NULL,
136                                ps->p->is, page+1, pc, sw, sh, ps->p->lang);
137
138         recursive_wrap(top, pc);
139
140         cairo_scale(cr, slide_width/sw, slide_width/sw);
141
142         recursive_draw(top, cr, ps->p->is,
143                        0.0, ps->p->slide_height);
144
145         ps->slide = next_slide(ps->p, ps->slide);
146 }
147
148
149 static int print_create_thumbnail(SCInterpreter *scin, SCBlock *bl,
150                                   double *w, double *h, void **bvp, void *vp)
151 {
152         struct print_stuff *ps = vp;
153         struct presentation *p = ps->p;
154         SCBlock *b;
155
156         *w = 270.0*(p->slide_width / p->slide_height);
157         *h = 270.0;
158         b = bl;
159
160         *bvp = b;
161
162         return 1;
163 }
164
165
166 static cairo_surface_t *print_render_thumbnail(int w, int h, void *bvp, void *vp)
167 {
168         struct print_stuff *ps = vp;
169         struct presentation *p = ps->p;
170         SCBlock *scblocks = bvp;
171         cairo_surface_t *surf;
172         struct frame *top;
173
174         surf = render_sc(scblocks, w, h, p->slide_width, p->slide_height,
175                          p->stylesheet, NULL, p->is, ps->slide_number++,
176                          &top, p->lang);
177         frame_free(top);
178
179         return surf;
180 }
181
182
183 static void begin_narrative_print(GtkPrintOperation *op, GtkPrintContext *ctx,
184                                   struct print_stuff *ps)
185 {
186         SCCallbackList *cbl;
187         PangoContext *pc;
188         int i, n_pages;
189         double h, page_height;
190         SCBlock *dummy_top;
191
192         cbl = sc_callback_list_new();
193         ps->slide_number = 1;
194         sc_callback_list_add_callback(cbl, "slide", print_create_thumbnail,
195                                       print_render_thumbnail, NULL, ps);
196
197         ps->is = imagestore_new(ps->storename);
198
199         pc = gtk_print_context_create_pango_context(ctx);
200
201         dummy_top = sc_block_new_parent(ps->p->scblocks, "presentation");
202         ps->top = interp_and_shape(dummy_top, ps->p->stylesheet, cbl,
203                                    ps->is, 0, pc,
204                                    gtk_print_context_get_width(ctx),
205                                    gtk_print_context_get_height(ctx),
206                                    ps->p->lang);
207         recursive_wrap(ps->top, pc);
208
209         /* Count pages */
210         page_height = gtk_print_context_get_height(ctx);
211         h = 0.0;
212         n_pages = 1;
213         ps->start_paras[0] = 0;
214         for ( i=0; i<ps->top->n_paras; i++ ) {
215                 if ( h + paragraph_height(ps->top->paras[i]) > page_height ) {
216                         /* Paragraph does not fit on page */
217                         ps->start_paras[n_pages] = i;
218                         n_pages++;
219                         h = 0.0;
220                 }
221                 h += paragraph_height(ps->top->paras[i]);
222         }
223         gtk_print_operation_set_n_pages(op, n_pages);
224         g_object_unref(pc);
225 }
226
227
228 static void print_narrative(GtkPrintOperation *op, GtkPrintContext *ctx,
229                             struct print_stuff *ps, gint page)
230 {
231         int i;
232         double h, page_height;
233         cairo_t *cr;
234
235         page_height = gtk_print_context_get_height(ctx);
236         cr = gtk_print_context_get_cairo_context(ctx);
237
238         h = 0.0;
239         for ( i=ps->start_paras[page]; i<ps->top->n_paras; i++ ) {
240
241                 /* Will this paragraph fit? */
242                 h += paragraph_height(ps->top->paras[i]);
243                 if ( h > page_height ) return;
244
245                 render_paragraph(cr, ps->top->paras[i], ps->is);
246                 cairo_translate(cr, 0.0, paragraph_height(ps->top->paras[i]));
247
248         }
249
250
251 }
252
253
254
255 static void print_begin(GtkPrintOperation *op, GtkPrintContext *ctx, void *vp)
256 {
257         struct print_stuff *ps = vp;
258
259         if ( ps->slides_only ) {
260                 gtk_print_operation_set_n_pages(op, num_slides(ps->p));
261                 ps->slide = first_slide(ps->p);
262         } else {
263                 begin_narrative_print(op, ctx, ps);
264         }
265 }
266
267
268 static void print_draw(GtkPrintOperation *op, GtkPrintContext *ctx, gint page,
269                        void *vp)
270 {
271         struct print_stuff *ps = vp;
272         if ( ps->slides_only ) {
273                 print_slide_only(op, ctx, ps, page);
274         } else {
275                 print_narrative(op, ctx, ps, page);
276         }
277 }
278
279
280 void run_printing(struct presentation *p, GtkWidget *parent)
281 {
282         GtkPrintOperation *print;
283         GtkPrintOperationResult res;
284         struct print_stuff *ps;
285
286         ps = malloc(sizeof(struct print_stuff));
287         if ( ps == NULL ) return;
288         ps->p = p;
289         ps->nar_line = 0;
290
291         print = gtk_print_operation_new();
292         if ( print_settings != NULL ) {
293                 gtk_print_operation_set_print_settings(print, print_settings);
294         }
295
296         g_signal_connect(print, "create-custom-widget",
297                          G_CALLBACK(print_widget), ps);
298         g_signal_connect(print, "custom-widget-apply",
299                          G_CALLBACK(print_widget_apply), ps);
300         g_signal_connect(print, "begin_print", G_CALLBACK(print_begin), ps);
301         g_signal_connect(print, "draw_page", G_CALLBACK(print_draw), ps);
302
303         res = gtk_print_operation_run(print,
304                                       GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG,
305                                       GTK_WINDOW(parent), NULL);
306
307         if ( res == GTK_PRINT_OPERATION_RESULT_APPLY ) {
308                 if ( print_settings != NULL ) {
309                         g_object_unref(print_settings);
310                 }
311                 print_settings = g_object_ref(
312                                  gtk_print_operation_get_print_settings(print));
313         }
314         g_object_unref(print);
315 }
316