blob: 5d2e285fb3668df012d68b6e37e834b3cc71cd06 [file] [log] [blame]
#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 <math.h>
static int page = 0;
static gboolean cairo_output = FALSE;
static gboolean splash_output = FALSE;
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, &page, "Page number", "PAGE" },
{ 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);
virtual ~GDKSplashOutputDev();
//----- 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 = new GlobalParams();
for (int i = 0; file_arguments[i]; i++) {
View *view;
GFile *file;
PopplerDocument *doc;
GError *error = nullptr;
file = g_file_new_for_commandline_arg (file_arguments[i]);
doc = poppler_document_new_from_gfile (file, nullptr, nullptr, &error);
if (!doc) {
gchar *uri;
uri = g_file_get_uri (file);
g_printerr ("Error opening document %s: %s\n", uri, error->message);
g_error_free (error);
g_free (uri);
g_object_unref (file);
continue;
}
g_object_unref (file);
view = view_new (doc);
view_list = g_list_prepend (view_list, view);
view_set_page (view, CLAMP (page, 0, poppler_document_get_n_pages (doc) - 1));
}
gtk_main ();
delete globalParams;
return 0;
}