| #include <config.h> |
| |
| #include <goo/gmem.h> |
| #include <splash/SplashTypes.h> |
| #include <splash/SplashBitmap.h> |
| #include "Object.h" |
| #include "SplashOutputDev.h" |
| #include "GfxState.h" |
| |
| #include <gdk/gdk.h> |
| |
| #include "PDFDoc.h" |
| #include "GlobalParams.h" |
| #include "ErrorCodes.h" |
| #include <poppler.h> |
| #include <poppler-private.h> |
| #include <gtk/gtk.h> |
| #include <cerrno> |
| #include <cmath> |
| |
| static int requested_page = 0; |
| static gboolean cairo_output = FALSE; |
| static gboolean splash_output = FALSE; |
| #ifndef G_OS_WIN32 |
| static gboolean args_are_fds = FALSE; |
| #endif |
| static const char **file_arguments = nullptr; |
| static const GOptionEntry options[] = { { "cairo", 'c', 0, G_OPTION_ARG_NONE, &cairo_output, "Cairo Output Device", nullptr }, |
| { "splash", 's', 0, G_OPTION_ARG_NONE, &splash_output, "Splash Output Device", nullptr }, |
| { "page", 'p', 0, G_OPTION_ARG_INT, &requested_page, "Page number", "PAGE" }, |
| #ifndef G_OS_WIN32 |
| { "fd", 'f', 0, G_OPTION_ARG_NONE, &args_are_fds, "File descriptors", nullptr }, |
| #endif |
| { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file_arguments, nullptr, "PDF-FILES…" }, |
| {} }; |
| |
| static GList *view_list = nullptr; |
| |
| //------------------------------------------------------------------------ |
| |
| #define xOutMaxRGBCube 6 // max size of RGB color cube |
| |
| //------------------------------------------------------------------------ |
| // GDKSplashOutputDev |
| //------------------------------------------------------------------------ |
| |
| class GDKSplashOutputDev : public SplashOutputDev |
| { |
| public: |
| GDKSplashOutputDev(GdkScreen *screen, void (*redrawCbkA)(void *data), void *redrawCbkDataA, SplashColor sc); |
| |
| ~GDKSplashOutputDev() override; |
| |
| //----- initialization and control |
| |
| // End a page. |
| void endPage() override; |
| |
| // Dump page contents to display. |
| void dump() override; |
| |
| //----- update text state |
| void updateFont(GfxState *state) override; |
| |
| //----- special access |
| |
| // Clear out the document (used when displaying an empty window). |
| void clear(); |
| |
| // Copy the rectangle (srcX, srcY, width, height) to (destX, destY) |
| // in destDC. |
| void redraw(int srcX, int srcY, cairo_t *cr, int destX, int destY, int width, int height); |
| |
| private: |
| int incrementalUpdate; |
| void (*redrawCbk)(void *data); |
| void *redrawCbkData; |
| }; |
| |
| typedef struct |
| { |
| PopplerDocument *doc; |
| GtkWidget *drawing_area; |
| GtkWidget *spin_button; |
| cairo_surface_t *surface; |
| GDKSplashOutputDev *out; |
| } View; |
| |
| //------------------------------------------------------------------------ |
| // Constants and macros |
| //------------------------------------------------------------------------ |
| |
| #define xoutRound(x) ((int)(x + 0.5)) |
| |
| //------------------------------------------------------------------------ |
| // GDKSplashOutputDev |
| //------------------------------------------------------------------------ |
| |
| GDKSplashOutputDev::GDKSplashOutputDev(GdkScreen *screen, void (*redrawCbkA)(void *data), void *redrawCbkDataA, SplashColor sc) : SplashOutputDev(splashModeRGB8, 4, false, sc), incrementalUpdate(1) |
| { |
| redrawCbk = redrawCbkA; |
| redrawCbkData = redrawCbkDataA; |
| } |
| |
| GDKSplashOutputDev::~GDKSplashOutputDev() { } |
| |
| void GDKSplashOutputDev::clear() |
| { |
| startDoc(nullptr); |
| startPage(0, nullptr, nullptr); |
| } |
| |
| void GDKSplashOutputDev::endPage() |
| { |
| SplashOutputDev::endPage(); |
| if (!incrementalUpdate) { |
| (*redrawCbk)(redrawCbkData); |
| } |
| } |
| |
| void GDKSplashOutputDev::dump() |
| { |
| if (incrementalUpdate && redrawCbk) { |
| (*redrawCbk)(redrawCbkData); |
| } |
| } |
| |
| void GDKSplashOutputDev::updateFont(GfxState *state) |
| { |
| SplashOutputDev::updateFont(state); |
| } |
| |
| void GDKSplashOutputDev::redraw(int srcX, int srcY, cairo_t *cr, int destX, int destY, int width, int height) |
| { |
| GdkPixbuf *pixbuf; |
| int gdk_rowstride; |
| |
| gdk_rowstride = getBitmap()->getRowSize(); |
| pixbuf = gdk_pixbuf_new_from_data(getBitmap()->getDataPtr() + srcY * gdk_rowstride + srcX * 3, GDK_COLORSPACE_RGB, FALSE, 8, width, height, gdk_rowstride, nullptr, nullptr); |
| |
| gdk_cairo_set_source_pixbuf(cr, pixbuf, 0, 0); |
| cairo_paint(cr); |
| |
| g_object_unref(pixbuf); |
| } |
| |
| static gboolean drawing_area_draw(GtkWidget *drawing_area, cairo_t *cr, View *view) |
| { |
| GdkRectangle document; |
| GdkRectangle clip; |
| GdkRectangle draw; |
| |
| document.x = 0; |
| document.y = 0; |
| if (cairo_output) { |
| document.width = cairo_image_surface_get_width(view->surface); |
| document.height = cairo_image_surface_get_height(view->surface); |
| } else { |
| document.width = view->out->getBitmapWidth(); |
| document.height = view->out->getBitmapHeight(); |
| } |
| |
| if (!gdk_cairo_get_clip_rectangle(cr, &clip)) { |
| return FALSE; |
| } |
| |
| if (!gdk_rectangle_intersect(&document, &clip, &draw)) { |
| return FALSE; |
| } |
| |
| if (cairo_output) { |
| cairo_set_source_surface(cr, view->surface, 0, 0); |
| cairo_paint(cr); |
| } else { |
| view->out->redraw(draw.x, draw.y, cr, draw.x, draw.y, draw.width, draw.height); |
| } |
| |
| return TRUE; |
| } |
| |
| static void view_set_page(View *view, int page) |
| { |
| int w, h; |
| |
| if (cairo_output) { |
| cairo_t *cr; |
| double width, height; |
| PopplerPage *poppler_page; |
| |
| poppler_page = poppler_document_get_page(view->doc, page); |
| poppler_page_get_size(poppler_page, &width, &height); |
| w = (int)ceil(width); |
| h = (int)ceil(height); |
| |
| if (view->surface) { |
| cairo_surface_destroy(view->surface); |
| } |
| view->surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); |
| |
| cr = cairo_create(view->surface); |
| poppler_page_render(poppler_page, cr); |
| |
| cairo_set_operator(cr, CAIRO_OPERATOR_DEST_OVER); |
| cairo_set_source_rgb(cr, 1., 1., 1.); |
| cairo_paint(cr); |
| |
| cairo_destroy(cr); |
| g_object_unref(poppler_page); |
| } else { |
| view->doc->doc->displayPage(view->out, page + 1, 72, 72, 0, false, true, true); |
| w = view->out->getBitmapWidth(); |
| h = view->out->getBitmapHeight(); |
| } |
| |
| gtk_widget_set_size_request(view->drawing_area, w, h); |
| gtk_widget_queue_draw(view->drawing_area); |
| gtk_spin_button_set_value(GTK_SPIN_BUTTON(view->spin_button), page); |
| } |
| |
| static void redraw_callback(void *data) |
| { |
| View *view = (View *)data; |
| |
| gtk_widget_queue_draw(view->drawing_area); |
| } |
| |
| static void view_free(View *view) |
| { |
| if (G_UNLIKELY(!view)) { |
| return; |
| } |
| |
| g_object_unref(view->doc); |
| delete view->out; |
| cairo_surface_destroy(view->surface); |
| g_slice_free(View, view); |
| } |
| |
| static void destroy_window_callback(GtkWindow *window, View *view) |
| { |
| view_list = g_list_remove(view_list, view); |
| view_free(view); |
| |
| if (!view_list) { |
| gtk_main_quit(); |
| } |
| } |
| |
| static void page_changed_callback(GtkSpinButton *button, View *view) |
| { |
| int page; |
| |
| page = gtk_spin_button_get_value_as_int(button); |
| view_set_page(view, page); |
| } |
| |
| static View *view_new(PopplerDocument *doc) |
| { |
| View *view; |
| GtkWidget *window; |
| GtkWidget *sw; |
| GtkWidget *vbox, *hbox; |
| guint n_pages; |
| PopplerPage *page; |
| |
| view = g_slice_new0(View); |
| |
| view->doc = doc; |
| |
| window = gtk_window_new(GTK_WINDOW_TOPLEVEL); |
| g_signal_connect(window, "destroy", G_CALLBACK(destroy_window_callback), view); |
| |
| page = poppler_document_get_page(doc, 0); |
| if (page) { |
| double width, height; |
| |
| poppler_page_get_size(page, &width, &height); |
| gtk_window_set_default_size(GTK_WINDOW(window), (gint)width, (gint)height); |
| g_object_unref(page); |
| } |
| |
| vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); |
| |
| view->drawing_area = gtk_drawing_area_new(); |
| sw = gtk_scrolled_window_new(nullptr, nullptr); |
| gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); |
| #if GTK_CHECK_VERSION(3, 7, 8) |
| gtk_container_add(GTK_CONTAINER(sw), view->drawing_area); |
| #else |
| gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), view->drawing_area); |
| #endif |
| gtk_widget_show(view->drawing_area); |
| |
| gtk_box_pack_end(GTK_BOX(vbox), sw, TRUE, TRUE, 0); |
| gtk_widget_show(sw); |
| |
| hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); |
| |
| n_pages = poppler_document_get_n_pages(doc); |
| view->spin_button = gtk_spin_button_new_with_range(0, n_pages - 1, 1); |
| g_signal_connect(view->spin_button, "value-changed", G_CALLBACK(page_changed_callback), view); |
| |
| gtk_box_pack_end(GTK_BOX(hbox), view->spin_button, FALSE, TRUE, 0); |
| gtk_widget_show(view->spin_button); |
| |
| gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0); |
| gtk_widget_show(hbox); |
| |
| gtk_container_add(GTK_CONTAINER(window), vbox); |
| gtk_widget_show(vbox); |
| |
| gtk_widget_show(window); |
| |
| if (!cairo_output) { |
| SplashColor sc = { 255, 255, 255 }; |
| |
| view->out = new GDKSplashOutputDev(gtk_widget_get_screen(window), redraw_callback, (void *)view, sc); |
| view->out->startDoc(view->doc->doc); |
| } |
| |
| g_signal_connect(view->drawing_area, "draw", G_CALLBACK(drawing_area_draw), view); |
| |
| return view; |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| GOptionContext *ctx; |
| |
| if (argc == 1) { |
| char *basename = g_path_get_basename(argv[0]); |
| |
| g_printerr("usage: %s PDF-FILES…\n", basename); |
| g_free(basename); |
| |
| return -1; |
| } |
| |
| ctx = g_option_context_new(nullptr); |
| g_option_context_add_main_entries(ctx, options, "main"); |
| g_option_context_parse(ctx, &argc, &argv, nullptr); |
| g_option_context_free(ctx); |
| |
| gtk_init(&argc, &argv); |
| |
| globalParams = std::make_unique<GlobalParams>(); |
| |
| for (int i = 0; file_arguments[i]; i++) { |
| View *view; |
| GFile *file; |
| PopplerDocument *doc = nullptr; |
| GError *error = nullptr; |
| const char *arg; |
| |
| arg = file_arguments[i]; |
| #ifndef G_OS_WIN32 |
| if (args_are_fds) { |
| char *end; |
| gint64 v; |
| |
| errno = 0; |
| end = nullptr; |
| v = g_ascii_strtoll(arg, &end, 10); |
| if (errno || end == arg || v == -1 || v < G_MININT || v > G_MAXINT) { |
| g_set_error(&error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE, "Failed to parse \"%s\" as file descriptor number", arg); |
| } else { |
| doc = poppler_document_new_from_fd(int(v), nullptr, &error); |
| } |
| } else |
| #endif /* !G_OS_WIN32 */ |
| { |
| file = g_file_new_for_commandline_arg(arg); |
| doc = poppler_document_new_from_gfile(file, nullptr, nullptr, &error); |
| if (!doc) { |
| gchar *uri; |
| |
| uri = g_file_get_uri(file); |
| g_prefix_error(&error, "%s: ", uri); |
| g_free(uri); |
| } |
| g_object_unref(file); |
| } |
| |
| if (doc) { |
| view = view_new(doc); |
| view_list = g_list_prepend(view_list, view); |
| view_set_page(view, CLAMP(requested_page, 0, poppler_document_get_n_pages(doc) - 1)); |
| } else { |
| g_printerr("Error opening document: %s\n", error->message); |
| g_error_free(error); |
| } |
| } |
| |
| if (view_list != nullptr) { |
| gtk_main(); |
| } |
| |
| return 0; |
| } |