1#include <config.h>
2
3#include <goo/gmem.h>
4#include <splash/SplashTypes.h>
5#include <splash/SplashBitmap.h>
6#include "Object.h"
7#include "SplashOutputDev.h"
8#include "GfxState.h"
9
10#include <gdk/gdk.h>
11
12#include "PDFDoc.h"
13#include "GlobalParams.h"
14#include "ErrorCodes.h"
15#include <poppler.h>
16#include <poppler-private.h>
17#include <gtk/gtk.h>
18#include <cerrno>
19#include <cmath>
20
21static int requested_page = 0;
22static gboolean cairo_output = FALSE;
23static gboolean splash_output = FALSE;
24#ifndef G_OS_WIN32
25static gboolean args_are_fds = FALSE;
26#endif
27static const char **file_arguments = nullptr;
28static const GOptionEntry options[] = { { .long_name: "cairo", .short_name: 'c', .flags: 0, .arg: G_OPTION_ARG_NONE, .arg_data: &cairo_output, .description: "Cairo Output Device", .arg_description: nullptr },
29 { .long_name: "splash", .short_name: 's', .flags: 0, .arg: G_OPTION_ARG_NONE, .arg_data: &splash_output, .description: "Splash Output Device", .arg_description: nullptr },
30 { .long_name: "page", .short_name: 'p', .flags: 0, .arg: G_OPTION_ARG_INT, .arg_data: &requested_page, .description: "Page number", .arg_description: "PAGE" },
31#ifndef G_OS_WIN32
32 { .long_name: "fd", .short_name: 'f', .flags: 0, .arg: G_OPTION_ARG_NONE, .arg_data: &args_are_fds, .description: "File descriptors", .arg_description: nullptr },
33#endif
34 { G_OPTION_REMAINING, .short_name: 0, .flags: 0, .arg: G_OPTION_ARG_FILENAME_ARRAY, .arg_data: &file_arguments, .description: nullptr, .arg_description: "PDF-FILES…" },
35 {} };
36
37static GList *view_list = nullptr;
38
39//------------------------------------------------------------------------
40
41#define xOutMaxRGBCube 6 // max size of RGB color cube
42
43//------------------------------------------------------------------------
44// GDKSplashOutputDev
45//------------------------------------------------------------------------
46
47class GDKSplashOutputDev : public SplashOutputDev
48{
49public:
50 GDKSplashOutputDev(GdkScreen *screen, void (*redrawCbkA)(void *data), void *redrawCbkDataA, SplashColor sc);
51
52 ~GDKSplashOutputDev() override;
53
54 //----- initialization and control
55
56 // End a page.
57 void endPage() override;
58
59 // Dump page contents to display.
60 void dump() override;
61
62 //----- update text state
63 void updateFont(GfxState *state) override;
64
65 //----- special access
66
67 // Clear out the document (used when displaying an empty window).
68 void clear();
69
70 // Copy the rectangle (srcX, srcY, width, height) to (destX, destY)
71 // in destDC.
72 void redraw(int srcX, int srcY, cairo_t *cr, int destX, int destY, int width, int height);
73
74private:
75 int incrementalUpdate;
76 void (*redrawCbk)(void *data);
77 void *redrawCbkData;
78};
79
80typedef struct
81{
82 PopplerDocument *doc;
83 GtkWidget *drawing_area;
84 GtkWidget *spin_button;
85 cairo_surface_t *surface;
86 GDKSplashOutputDev *out;
87} View;
88
89//------------------------------------------------------------------------
90// Constants and macros
91//------------------------------------------------------------------------
92
93#define xoutRound(x) ((int)(x + 0.5))
94
95//------------------------------------------------------------------------
96// GDKSplashOutputDev
97//------------------------------------------------------------------------
98
99GDKSplashOutputDev::GDKSplashOutputDev(GdkScreen *screen, void (*redrawCbkA)(void *data), void *redrawCbkDataA, SplashColor sc) : SplashOutputDev(splashModeRGB8, 4, false, sc), incrementalUpdate(1)
100{
101 redrawCbk = redrawCbkA;
102 redrawCbkData = redrawCbkDataA;
103}
104
105GDKSplashOutputDev::~GDKSplashOutputDev() { }
106
107void GDKSplashOutputDev::clear()
108{
109 startDoc(docA: nullptr);
110 startPage(pageNum: 0, state: nullptr, xref: nullptr);
111}
112
113void GDKSplashOutputDev::endPage()
114{
115 SplashOutputDev::endPage();
116 if (!incrementalUpdate) {
117 (*redrawCbk)(redrawCbkData);
118 }
119}
120
121void GDKSplashOutputDev::dump()
122{
123 if (incrementalUpdate && redrawCbk) {
124 (*redrawCbk)(redrawCbkData);
125 }
126}
127
128void GDKSplashOutputDev::updateFont(GfxState *state)
129{
130 SplashOutputDev::updateFont(state);
131}
132
133void GDKSplashOutputDev::redraw(int srcX, int srcY, cairo_t *cr, int destX, int destY, int width, int height)
134{
135 GdkPixbuf *pixbuf;
136 int gdk_rowstride;
137
138 gdk_rowstride = getBitmap()->getRowSize();
139 pixbuf = gdk_pixbuf_new_from_data(data: getBitmap()->getDataPtr() + srcY * gdk_rowstride + srcX * 3, colorspace: GDK_COLORSPACE_RGB, FALSE, bits_per_sample: 8, width, height, rowstride: gdk_rowstride, destroy_fn: nullptr, destroy_fn_data: nullptr);
140
141 gdk_cairo_set_source_pixbuf(cr, pixbuf, pixbuf_x: 0, pixbuf_y: 0);
142 cairo_paint(cr);
143
144 g_object_unref(object: pixbuf);
145}
146
147static gboolean drawing_area_draw(GtkWidget *drawing_area, cairo_t *cr, View *view)
148{
149 GdkRectangle document;
150 GdkRectangle clip;
151 GdkRectangle draw;
152
153 document.x = 0;
154 document.y = 0;
155 if (cairo_output) {
156 document.width = cairo_image_surface_get_width(surface: view->surface);
157 document.height = cairo_image_surface_get_height(surface: view->surface);
158 } else {
159 document.width = view->out->getBitmapWidth();
160 document.height = view->out->getBitmapHeight();
161 }
162
163 if (!gdk_cairo_get_clip_rectangle(cr, rect: &clip)) {
164 return FALSE;
165 }
166
167 if (!gdk_rectangle_intersect(src1: &document, src2: &clip, dest: &draw)) {
168 return FALSE;
169 }
170
171 if (cairo_output) {
172 cairo_set_source_surface(cr, surface: view->surface, x: 0, y: 0);
173 cairo_paint(cr);
174 } else {
175 view->out->redraw(srcX: draw.x, srcY: draw.y, cr, destX: draw.x, destY: draw.y, width: draw.width, height: draw.height);
176 }
177
178 return TRUE;
179}
180
181static void view_set_page(View *view, int page)
182{
183 int w, h;
184
185 if (cairo_output) {
186 cairo_t *cr;
187 double width, height;
188 PopplerPage *poppler_page;
189
190 poppler_page = poppler_document_get_page(document: view->doc, index: page);
191 poppler_page_get_size(page: poppler_page, width: &width, height: &height);
192 w = (int)ceil(x: width);
193 h = (int)ceil(x: height);
194
195 if (view->surface) {
196 cairo_surface_destroy(surface: view->surface);
197 }
198 view->surface = cairo_image_surface_create(format: CAIRO_FORMAT_ARGB32, width: w, height: h);
199
200 cr = cairo_create(target: view->surface);
201 poppler_page_render(page: poppler_page, cairo: cr);
202
203 cairo_set_operator(cr, op: CAIRO_OPERATOR_DEST_OVER);
204 cairo_set_source_rgb(cr, red: 1., green: 1., blue: 1.);
205 cairo_paint(cr);
206
207 cairo_destroy(cr);
208 g_object_unref(object: poppler_page);
209 } else {
210 view->doc->doc->displayPage(out: view->out, page: page + 1, hDPI: 72, vDPI: 72, rotate: 0, useMediaBox: false, crop: true, printing: true);
211 w = view->out->getBitmapWidth();
212 h = view->out->getBitmapHeight();
213 }
214
215 gtk_widget_set_size_request(widget: view->drawing_area, width: w, height: h);
216 gtk_widget_queue_draw(widget: view->drawing_area);
217 gtk_spin_button_set_value(GTK_SPIN_BUTTON(view->spin_button), value: page);
218}
219
220static void redraw_callback(void *data)
221{
222 View *view = (View *)data;
223
224 gtk_widget_queue_draw(widget: view->drawing_area);
225}
226
227static void view_free(View *view)
228{
229 if (G_UNLIKELY(!view)) {
230 return;
231 }
232
233 g_object_unref(object: view->doc);
234 delete view->out;
235 cairo_surface_destroy(surface: view->surface);
236 g_slice_free(View, view);
237}
238
239static void destroy_window_callback(GtkWindow *window, View *view)
240{
241 view_list = g_list_remove(list: view_list, data: view);
242 view_free(view);
243
244 if (!view_list) {
245 gtk_main_quit();
246 }
247}
248
249static void page_changed_callback(GtkSpinButton *button, View *view)
250{
251 int page;
252
253 page = gtk_spin_button_get_value_as_int(spin_button: button);
254 view_set_page(view, page);
255}
256
257static View *view_new(PopplerDocument *doc)
258{
259 View *view;
260 GtkWidget *window;
261 GtkWidget *sw;
262 GtkWidget *vbox, *hbox;
263 guint n_pages;
264 PopplerPage *page;
265
266 view = g_slice_new0(View);
267
268 view->doc = doc;
269
270 window = gtk_window_new(type: GTK_WINDOW_TOPLEVEL);
271 g_signal_connect(window, "destroy", G_CALLBACK(destroy_window_callback), view);
272
273 page = poppler_document_get_page(document: doc, index: 0);
274 if (page) {
275 double width, height;
276
277 poppler_page_get_size(page, width: &width, height: &height);
278 gtk_window_set_default_size(GTK_WINDOW(window), width: (gint)width, height: (gint)height);
279 g_object_unref(object: page);
280 }
281
282 vbox = gtk_box_new(orientation: GTK_ORIENTATION_VERTICAL, spacing: 5);
283
284 view->drawing_area = gtk_drawing_area_new();
285 sw = gtk_scrolled_window_new(hadjustment: nullptr, vadjustment: nullptr);
286 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), hscrollbar_policy: GTK_POLICY_AUTOMATIC, vscrollbar_policy: GTK_POLICY_AUTOMATIC);
287#if GTK_CHECK_VERSION(3, 7, 8)
288 gtk_container_add(GTK_CONTAINER(sw), widget: view->drawing_area);
289#else
290 gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), view->drawing_area);
291#endif
292 gtk_widget_show(widget: view->drawing_area);
293
294 gtk_box_pack_end(GTK_BOX(vbox), child: sw, TRUE, TRUE, padding: 0);
295 gtk_widget_show(widget: sw);
296
297 hbox = gtk_box_new(orientation: GTK_ORIENTATION_HORIZONTAL, spacing: 5);
298
299 n_pages = poppler_document_get_n_pages(document: doc);
300 view->spin_button = gtk_spin_button_new_with_range(min: 0, max: n_pages - 1, step: 1);
301 g_signal_connect(view->spin_button, "value-changed", G_CALLBACK(page_changed_callback), view);
302
303 gtk_box_pack_end(GTK_BOX(hbox), child: view->spin_button, FALSE, TRUE, padding: 0);
304 gtk_widget_show(widget: view->spin_button);
305
306 gtk_box_pack_end(GTK_BOX(vbox), child: hbox, FALSE, TRUE, padding: 0);
307 gtk_widget_show(widget: hbox);
308
309 gtk_container_add(GTK_CONTAINER(window), widget: vbox);
310 gtk_widget_show(widget: vbox);
311
312 gtk_widget_show(widget: window);
313
314 if (!cairo_output) {
315 SplashColor sc = { 255, 255, 255 };
316
317 view->out = new GDKSplashOutputDev(gtk_widget_get_screen(widget: window), redraw_callback, (void *)view, sc);
318 view->out->startDoc(docA: view->doc->doc);
319 }
320
321 g_signal_connect(view->drawing_area, "draw", G_CALLBACK(drawing_area_draw), view);
322
323 return view;
324}
325
326int main(int argc, char *argv[])
327{
328 GOptionContext *ctx;
329
330 if (argc == 1) {
331 char *basename = g_path_get_basename(file_name: argv[0]);
332
333 g_printerr(format: "usage: %s PDF-FILES…\n", basename);
334 g_free(mem: basename);
335
336 return -1;
337 }
338
339 ctx = g_option_context_new(parameter_string: nullptr);
340 g_option_context_add_main_entries(context: ctx, entries: options, translation_domain: "main");
341 g_option_context_parse(context: ctx, argc: &argc, argv: &argv, error: nullptr);
342 g_option_context_free(context: ctx);
343
344 gtk_init(argc: &argc, argv: &argv);
345
346 globalParams = std::make_unique<GlobalParams>();
347
348 for (int i = 0; file_arguments[i]; i++) {
349 View *view;
350 GFile *file;
351 PopplerDocument *doc = nullptr;
352 GError *error = nullptr;
353 const char *arg;
354
355 arg = file_arguments[i];
356#ifndef G_OS_WIN32
357 if (args_are_fds) {
358 char *end;
359 gint64 v;
360
361 errno = 0;
362 end = nullptr;
363 v = g_ascii_strtoll(nptr: arg, endptr: &end, base: 10);
364 if (errno || end == arg || v == -1 || v < G_MININT || v > G_MAXINT) {
365 g_set_error(err: &error, G_OPTION_ERROR, code: G_OPTION_ERROR_BAD_VALUE, format: "Failed to parse \"%s\" as file descriptor number", arg);
366 } else {
367 doc = poppler_document_new_from_fd(fd: int(v), password: nullptr, error: &error);
368 }
369 } else
370#endif /* !G_OS_WIN32 */
371 {
372 file = g_file_new_for_commandline_arg(arg);
373 doc = poppler_document_new_from_gfile(file, password: nullptr, cancellable: nullptr, error: &error);
374 if (!doc) {
375 gchar *uri;
376
377 uri = g_file_get_uri(file);
378 g_prefix_error(err: &error, format: "%s: ", uri);
379 g_free(mem: uri);
380 }
381 g_object_unref(object: file);
382 }
383
384 if (doc) {
385 view = view_new(doc);
386 view_list = g_list_prepend(list: view_list, data: view);
387 view_set_page(view, CLAMP(requested_page, 0, poppler_document_get_n_pages(doc) - 1));
388 } else {
389 g_printerr(format: "Error opening document: %s\n", error->message);
390 g_error_free(error);
391 }
392 }
393
394 if (view_list != nullptr) {
395 gtk_main();
396 }
397
398 return 0;
399}
400

source code of poppler/test/gtk-test.cc