mirror of
https://git.pwmt.org/pwmt/zathura.git
synced 2024-12-28 21:55:59 +01:00
030a8c65c1
The plan is to put in adjustment.c every piece of code that has to do with document positioning, either computing it from data on the document side, or talking to GTK. We want to have at our disposal functions to compute sizes and positions without having to ask for it to a GTK widget. The new functions are: - move page_calc_height_width to adjustment.c - add page_calc_position that rotates a position relative to a page according to the rotation settings. - add position_to_page_number that computes the number of a page sitting at a given position (given in document-relative coordinates) - add page_number_to_position that computes the position (in document relative coordinates) that will be placed at the center of the viewport. - add page_is_visible that checks whether the given page intersects the viewport.
872 lines
24 KiB
C
872 lines
24 KiB
C
/* See LICENSE file for license and copyright information */
|
|
|
|
#include <math.h>
|
|
#include <girara/datastructures.h>
|
|
#include <girara/utils.h>
|
|
#include "glib-compat.h"
|
|
|
|
#include "render.h"
|
|
#include "adjustment.h"
|
|
#include "zathura.h"
|
|
#include "document.h"
|
|
#include "page.h"
|
|
#include "page-widget.h"
|
|
#include "utils.h"
|
|
|
|
/* define the two types */
|
|
G_DEFINE_TYPE(ZathuraRenderer, zathura_renderer, G_TYPE_OBJECT)
|
|
G_DEFINE_TYPE(ZathuraRenderRequest, zathura_render_request, G_TYPE_OBJECT)
|
|
|
|
/* private methods for ZathuraRenderer */
|
|
static void renderer_finalize(GObject* object);
|
|
/* private methods for ZathuraRenderRequest */
|
|
static void render_request_finalize(GObject* object);
|
|
|
|
static void render_job(void* data, void* user_data);
|
|
static gint render_thread_sort(gconstpointer a, gconstpointer b, gpointer data);
|
|
static void color2double(const GdkColor* col, double* v);
|
|
static ssize_t page_cache_lru_invalidate(ZathuraRenderer* renderer);
|
|
static void page_cache_invalidate_all(ZathuraRenderer* renderer);
|
|
static bool page_cache_is_full(ZathuraRenderer* renderer, bool* result);
|
|
|
|
|
|
/* private data for ZathuraRenderer */
|
|
typedef struct private_s {
|
|
GThreadPool* pool; /**< Pool of threads */
|
|
mutex mutex; /**< Render lock */
|
|
volatile bool about_to_close; /**< Render thread is to be freed */
|
|
|
|
/**
|
|
* recolor information
|
|
*/
|
|
struct {
|
|
bool enabled;
|
|
bool hue;
|
|
|
|
double light[3];
|
|
GdkColor light_gdk;
|
|
double dark[3];
|
|
GdkColor dark_gdk;
|
|
} recolor;
|
|
|
|
/*
|
|
* page cache
|
|
*/
|
|
struct {
|
|
int* cache;
|
|
size_t size;
|
|
size_t num_cached_pages;
|
|
} page_cache;
|
|
|
|
/* render requests */
|
|
girara_list_t* requests;
|
|
} private_t;
|
|
|
|
/* private data for ZathuraRenderRequest */
|
|
typedef struct request_private_s {
|
|
ZathuraRenderer* renderer;
|
|
zathura_page_t* page;
|
|
bool requested;
|
|
bool aborted;
|
|
gint64 last_view_time;
|
|
} request_private_t;
|
|
|
|
#define GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_RENDERER, private_t))
|
|
#define REQUEST_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE((obj), ZATHURA_TYPE_RENDER_REQUEST, \
|
|
request_private_t))
|
|
|
|
/* init, new and free for ZathuraRenderer */
|
|
|
|
static void
|
|
zathura_renderer_class_init(ZathuraRendererClass* class)
|
|
{
|
|
/* add private members */
|
|
g_type_class_add_private(class, sizeof(private_t));
|
|
|
|
/* overwrite methods */
|
|
GObjectClass* object_class = G_OBJECT_CLASS(class);
|
|
object_class->finalize = renderer_finalize;
|
|
}
|
|
|
|
static void
|
|
zathura_renderer_init(ZathuraRenderer* renderer)
|
|
{
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
priv->pool = g_thread_pool_new(render_job, renderer, 1, TRUE, NULL);
|
|
priv->about_to_close = false;
|
|
g_thread_pool_set_sort_function(priv->pool, render_thread_sort, NULL);
|
|
mutex_init(&priv->mutex);
|
|
|
|
/* recolor */
|
|
priv->recolor.enabled = false;
|
|
priv->recolor.hue = true;
|
|
|
|
/* page cache */
|
|
priv->page_cache.size = 0;
|
|
priv->page_cache.cache = NULL;
|
|
priv->page_cache.num_cached_pages = 0;
|
|
|
|
zathura_renderer_set_recolor_colors_str(renderer, "#000000", "#FFFFFF");
|
|
|
|
priv->requests = girara_list_new();
|
|
}
|
|
|
|
static void
|
|
page_cache_init(ZathuraRenderer* renderer, size_t cache_size)
|
|
{
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
|
|
priv->page_cache.size = cache_size;
|
|
priv->page_cache.cache = g_malloc(cache_size * sizeof(int));
|
|
page_cache_invalidate_all(renderer);
|
|
}
|
|
|
|
ZathuraRenderer*
|
|
zathura_renderer_new(size_t cache_size)
|
|
{
|
|
g_return_val_if_fail(cache_size > 0, NULL);
|
|
|
|
GObject* obj = g_object_new(ZATHURA_TYPE_RENDERER, NULL);
|
|
ZathuraRenderer* ret = ZATHURA_RENDERER(obj);
|
|
page_cache_init(ret, cache_size);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
renderer_finalize(GObject* object)
|
|
{
|
|
ZathuraRenderer* renderer = ZATHURA_RENDERER(object);
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
|
|
zathura_renderer_stop(renderer);
|
|
if (priv->pool) {
|
|
g_thread_pool_free(priv->pool, TRUE, TRUE);
|
|
}
|
|
mutex_free(&(priv->mutex));
|
|
|
|
free(priv->page_cache.cache);
|
|
girara_list_free(priv->requests);
|
|
}
|
|
|
|
/* (un)register requests at the renderer */
|
|
|
|
static void
|
|
renderer_unregister_request(ZathuraRenderer* renderer,
|
|
ZathuraRenderRequest* request)
|
|
{
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
girara_list_remove(priv->requests, request);
|
|
}
|
|
|
|
static void
|
|
renderer_register_request(ZathuraRenderer* renderer,
|
|
ZathuraRenderRequest* request)
|
|
{
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
if (girara_list_contains(priv->requests, request) == false) {
|
|
girara_list_append(priv->requests, request);
|
|
}
|
|
}
|
|
|
|
/* init, new and free for ZathuraRenderRequest */
|
|
|
|
enum {
|
|
REQUEST_COMPLETED,
|
|
REQUEST_CACHE_ADDED,
|
|
REQUEST_CACHE_INVALIDATED,
|
|
REQUEST_LAST_SIGNAL
|
|
};
|
|
|
|
static guint request_signals[REQUEST_LAST_SIGNAL] = { 0 };
|
|
|
|
static void
|
|
zathura_render_request_class_init(ZathuraRenderRequestClass* class)
|
|
{
|
|
/* add private members */
|
|
g_type_class_add_private(class, sizeof(request_private_t));
|
|
|
|
/* overwrite methods */
|
|
GObjectClass* object_class = G_OBJECT_CLASS(class);
|
|
object_class->finalize = render_request_finalize;
|
|
|
|
request_signals[REQUEST_COMPLETED] = g_signal_new("completed",
|
|
ZATHURA_TYPE_RENDER_REQUEST,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
1,
|
|
G_TYPE_POINTER);
|
|
|
|
request_signals[REQUEST_CACHE_ADDED] = g_signal_new("cache-added",
|
|
ZATHURA_TYPE_RENDER_REQUEST,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
0);
|
|
|
|
request_signals[REQUEST_CACHE_INVALIDATED] = g_signal_new("cache-invalidated",
|
|
ZATHURA_TYPE_RENDER_REQUEST,
|
|
G_SIGNAL_RUN_LAST,
|
|
0,
|
|
NULL,
|
|
NULL,
|
|
g_cclosure_marshal_generic,
|
|
G_TYPE_NONE,
|
|
0);
|
|
}
|
|
|
|
static void
|
|
zathura_render_request_init(ZathuraRenderRequest* request)
|
|
{
|
|
request_private_t* priv = REQUEST_GET_PRIVATE(request);
|
|
priv->renderer = NULL;
|
|
priv->page = NULL;
|
|
}
|
|
|
|
ZathuraRenderRequest*
|
|
zathura_render_request_new(ZathuraRenderer* renderer, zathura_page_t* page)
|
|
{
|
|
g_return_val_if_fail(renderer != NULL && page != NULL, NULL);
|
|
|
|
GObject* obj = g_object_new(ZATHURA_TYPE_RENDER_REQUEST, NULL);
|
|
if (obj == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(obj);
|
|
request_private_t* priv = REQUEST_GET_PRIVATE(request);
|
|
/* we want to make sure that renderer lives long enough */
|
|
priv->renderer = g_object_ref(renderer);
|
|
priv->page = page;
|
|
priv->aborted = false;
|
|
priv->requested = false;
|
|
|
|
/* register the request with the renderer */
|
|
renderer_register_request(renderer, request);
|
|
|
|
return request;
|
|
}
|
|
|
|
static void
|
|
render_request_finalize(GObject* object)
|
|
{
|
|
ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
|
|
request_private_t* priv = REQUEST_GET_PRIVATE(request);
|
|
|
|
if (priv->renderer) {
|
|
/* unregister the request */
|
|
renderer_unregister_request(priv->renderer, request);
|
|
/* release our private reference to the renderer */
|
|
g_object_unref(priv->renderer);
|
|
}
|
|
}
|
|
|
|
/* renderer methods */
|
|
|
|
bool
|
|
zathura_renderer_recolor_enabled(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
|
|
|
|
return GET_PRIVATE(renderer)->recolor.enabled;
|
|
}
|
|
|
|
void
|
|
zathura_renderer_enable_recolor(ZathuraRenderer* renderer, bool enable)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
GET_PRIVATE(renderer)->recolor.enabled = enable;
|
|
}
|
|
|
|
bool
|
|
zathura_renderer_recolor_hue_enabled(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
|
|
|
|
return GET_PRIVATE(renderer)->recolor.hue;
|
|
}
|
|
|
|
void
|
|
zathura_renderer_enable_recolor_hue(ZathuraRenderer* renderer, bool enable)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
GET_PRIVATE(renderer)->recolor.hue = enable;
|
|
}
|
|
|
|
void
|
|
zathura_renderer_set_recolor_colors(ZathuraRenderer* renderer,
|
|
const GdkColor* light, const GdkColor* dark)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
if (light != NULL) {
|
|
priv->recolor.light_gdk.red = light->red;
|
|
priv->recolor.light_gdk.blue = light->blue;
|
|
priv->recolor.light_gdk.green = light->green;
|
|
color2double(light, priv->recolor.light);
|
|
}
|
|
if (dark != NULL) {
|
|
priv->recolor.dark_gdk.red = dark->red;
|
|
priv->recolor.dark_gdk.blue = dark->blue;
|
|
priv->recolor.dark_gdk.green = dark->green;
|
|
color2double(dark, priv->recolor.dark);
|
|
}
|
|
}
|
|
|
|
void
|
|
zathura_renderer_set_recolor_colors_str(ZathuraRenderer* renderer,
|
|
const char* light, const char* dark)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
if (dark != NULL) {
|
|
GdkColor color;
|
|
gdk_color_parse(dark, &color);
|
|
zathura_renderer_set_recolor_colors(renderer, NULL, &color);
|
|
}
|
|
if (light != NULL) {
|
|
GdkColor color;
|
|
gdk_color_parse(light, &color);
|
|
zathura_renderer_set_recolor_colors(renderer, &color, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
zathura_renderer_get_recolor_colors(ZathuraRenderer* renderer,
|
|
GdkColor* light, GdkColor* dark)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
if (light != NULL) {
|
|
light->red = priv->recolor.light_gdk.red;
|
|
light->blue = priv->recolor.light_gdk.blue;
|
|
light->green = priv->recolor.light_gdk.green;
|
|
}
|
|
if (dark != NULL) {
|
|
dark->red = priv->recolor.dark_gdk.red;
|
|
dark->blue = priv->recolor.dark_gdk.blue;
|
|
dark->green = priv->recolor.dark_gdk.green;
|
|
}
|
|
}
|
|
|
|
void
|
|
zathura_renderer_lock(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
mutex_lock(&priv->mutex);
|
|
}
|
|
|
|
void
|
|
zathura_renderer_unlock(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
mutex_unlock(&priv->mutex);
|
|
}
|
|
|
|
void
|
|
zathura_renderer_stop(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
GET_PRIVATE(renderer)->about_to_close = true;
|
|
}
|
|
|
|
|
|
/* ZathuraRenderRequest methods */
|
|
|
|
void
|
|
zathura_render_request(ZathuraRenderRequest* request, gint64 last_view_time)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
|
|
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
if (request_priv->requested == false) {
|
|
request_priv->requested = true;
|
|
request_priv->aborted = false;
|
|
request_priv->last_view_time = last_view_time;
|
|
|
|
private_t* priv = GET_PRIVATE(request_priv->renderer);
|
|
g_thread_pool_push(priv->pool, request, NULL);
|
|
}
|
|
}
|
|
|
|
void
|
|
zathura_render_request_abort(ZathuraRenderRequest* request)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
|
|
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
if (request_priv->requested == true) {
|
|
request_priv->aborted = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
zathura_render_request_update_view_time(ZathuraRenderRequest* request)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
|
|
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
request_priv->last_view_time = g_get_real_time();
|
|
}
|
|
|
|
/* render job */
|
|
|
|
typedef struct emit_completed_signal_s
|
|
{
|
|
ZathuraRenderer* renderer;
|
|
ZathuraRenderRequest* request;
|
|
cairo_surface_t* surface;
|
|
} emit_completed_signal_t;
|
|
|
|
static gboolean
|
|
emit_completed_signal(void* data)
|
|
{
|
|
emit_completed_signal_t* ecs = data;
|
|
private_t* priv = GET_PRIVATE(ecs->renderer);
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(ecs->request);
|
|
|
|
if (priv->about_to_close == false && request_priv->aborted == false) {
|
|
/* emit the signal */
|
|
g_signal_emit(ecs->request, request_signals[REQUEST_COMPLETED], 0, ecs->surface);
|
|
}
|
|
/* mark the request as done */
|
|
request_priv->requested = false;
|
|
|
|
/* clean up the data */
|
|
cairo_surface_destroy(ecs->surface);
|
|
g_object_unref(ecs->renderer);
|
|
g_object_unref(ecs->request);
|
|
g_free(ecs);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
color2double(const GdkColor* col, double* v)
|
|
{
|
|
v[0] = (double) col->red / 65535.;
|
|
v[1] = (double) col->green / 65535.;
|
|
v[2] = (double) col->blue / 65535.;
|
|
}
|
|
|
|
/* Returns the maximum possible saturation for given h and l.
|
|
Assumes that l is in the interval l1, l2 and corrects the value to
|
|
force u=0 on l1 and l2 */
|
|
static double
|
|
colorumax(const double* h, double l, double l1, double l2)
|
|
{
|
|
double u, uu, v, vv, lv;
|
|
if (h[0] == 0 && h[1] == 0 && h[2] == 0) {
|
|
return 0;
|
|
}
|
|
|
|
lv = (l - l1)/(l2 - l1); /* Remap l to the whole interval 0,1 */
|
|
u = v = 1000000;
|
|
for (int k = 0; k < 3; k++) {
|
|
if (h[k] > 0) {
|
|
uu = fabs((1-l)/h[k]);
|
|
vv = fabs((1-lv)/h[k]);
|
|
|
|
if (uu < u) {
|
|
u = uu;
|
|
}
|
|
if (vv < v) {
|
|
v = vv;
|
|
}
|
|
} else if (h[k] < 0) {
|
|
uu = fabs(l/h[k]);
|
|
vv = fabs(lv/h[k]);
|
|
|
|
if (uu < u) {
|
|
u = uu;
|
|
}
|
|
if (vv < v) {
|
|
v = vv;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* rescale v according to the length of the interval [l1, l2] */
|
|
v = fabs(l2 - l1) * v;
|
|
|
|
/* forces the returned value to be 0 on l1 and l2, trying not to distort colors too much */
|
|
return fmin(u, v);
|
|
}
|
|
|
|
static void
|
|
recolor(private_t* priv, unsigned int page_width, unsigned int page_height,
|
|
cairo_surface_t* surface)
|
|
{
|
|
/* uses a representation of a rgb color as follows:
|
|
- a lightness scalar (between 0,1), which is a weighted average of r, g, b,
|
|
- a hue vector, which indicates a radian direction from the grey axis,
|
|
inside the equal lightness plane.
|
|
- a saturation scalar between 0,1. It is 0 when grey, 1 when the color is
|
|
in the boundary of the rgb cube.
|
|
*/
|
|
|
|
const int rowstride = cairo_image_surface_get_stride(surface);
|
|
unsigned char* image = cairo_image_surface_get_data(surface);
|
|
|
|
/* RGB weights for computing lightness. Must sum to one */
|
|
static const double a[] = {0.30, 0.59, 0.11};
|
|
|
|
#define rgb1 priv->recolor.dark
|
|
#define rgb2 priv->recolor.light
|
|
const double l1 = (a[0]*rgb1[0] + a[1]*rgb1[1] + a[2]*rgb1[2]);
|
|
const double l2 = (a[0]*rgb2[0] + a[1]*rgb2[1] + a[2]*rgb2[2]);
|
|
|
|
const double rgb_diff[] = {
|
|
rgb2[0] - rgb1[0],
|
|
rgb2[1] - rgb1[1],
|
|
rgb2[2] - rgb1[2]
|
|
};
|
|
|
|
for (unsigned int y = 0; y < page_height; y++) {
|
|
unsigned char* data = image + y * rowstride;
|
|
|
|
for (unsigned int x = 0; x < page_width; x++, data += 4) {
|
|
/* Careful. data color components blue, green, red. */
|
|
const double rgb[3] = {
|
|
(double) data[2] / 256.,
|
|
(double) data[1] / 256.,
|
|
(double) data[0] / 256.
|
|
};
|
|
|
|
/* compute h, s, l data */
|
|
double l = a[0]*rgb[0] + a[1]*rgb[1] + a[2]*rgb[2];
|
|
|
|
if (priv->recolor.hue == true) {
|
|
/* adjusting lightness keeping hue of current color. white and black
|
|
* go to grays of same ligtness as light and dark colors. */
|
|
const double h[3] = {
|
|
rgb[0] - l,
|
|
rgb[1] - l,
|
|
rgb[2] - l
|
|
};
|
|
|
|
/* u is the maximum possible saturation for given h and l. s is a
|
|
* rescaled saturation between 0 and 1 */
|
|
double u = colorumax(h, l, 0, 1);
|
|
double s = 0;
|
|
if (u != 0) {
|
|
s = 1/u;
|
|
}
|
|
|
|
/* Interpolates lightness between light and dark colors. white goes to
|
|
* light, and black goes to dark. */
|
|
l = l * (l2 - l1) + l1;
|
|
u = colorumax(h, l, l1, l2);
|
|
|
|
data[2] = (unsigned char)round(255.*(l + s*u * h[0]));
|
|
data[1] = (unsigned char)round(255.*(l + s*u * h[1]));
|
|
data[0] = (unsigned char)round(255.*(l + s*u * h[2]));
|
|
} else {
|
|
/* linear interpolation between dark and light with color ligtness as
|
|
* a parameter */
|
|
data[2] = (unsigned char)round(255.*(l * rgb_diff[0] + rgb1[0]));
|
|
data[1] = (unsigned char)round(255.*(l * rgb_diff[1] + rgb1[1]));
|
|
data[0] = (unsigned char)round(255.*(l * rgb_diff[2] + rgb1[2]));
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef rgb1
|
|
#undef rgb2
|
|
}
|
|
|
|
static bool
|
|
render(ZathuraRenderRequest* request, ZathuraRenderer* renderer)
|
|
{
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
zathura_page_t* page = request_priv->page;
|
|
|
|
/* create cairo surface */
|
|
unsigned int page_width = 0;
|
|
unsigned int page_height = 0;
|
|
|
|
zathura_document_t* document = zathura_page_get_document(page);
|
|
double height = zathura_page_get_height(page);
|
|
double width = zathura_page_get_width(page);
|
|
|
|
const double real_scale = page_calc_height_width(document, height, width,
|
|
&page_height, &page_width, false);
|
|
|
|
|
|
cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
|
|
page_width, page_height);
|
|
if (surface == NULL) {
|
|
return false;
|
|
}
|
|
|
|
cairo_t* cairo = cairo_create(surface);
|
|
if (cairo == NULL) {
|
|
cairo_surface_destroy(surface);
|
|
return false;
|
|
}
|
|
|
|
cairo_save(cairo);
|
|
cairo_set_source_rgb(cairo, 1, 1, 1);
|
|
cairo_rectangle(cairo, 0, 0, page_width, page_height);
|
|
cairo_fill(cairo);
|
|
cairo_restore(cairo);
|
|
cairo_save(cairo);
|
|
|
|
if (fabs(real_scale - 1.0f) > FLT_EPSILON) {
|
|
cairo_scale(cairo, real_scale, real_scale);
|
|
}
|
|
|
|
zathura_renderer_lock(renderer);
|
|
if (zathura_page_render(page, cairo, false) != ZATHURA_ERROR_OK) {
|
|
zathura_renderer_unlock(renderer);
|
|
cairo_destroy(cairo);
|
|
cairo_surface_destroy(surface);
|
|
return false;
|
|
}
|
|
|
|
zathura_renderer_unlock(renderer);
|
|
cairo_restore(cairo);
|
|
cairo_destroy(cairo);
|
|
|
|
/* before recoloring, check if we've been aborted */
|
|
if (priv->about_to_close == true || request_priv->aborted == true) {
|
|
request_priv->requested = false;
|
|
cairo_surface_destroy(surface);
|
|
return true;
|
|
}
|
|
|
|
/* recolor */
|
|
if (priv->recolor.enabled == true) {
|
|
recolor(priv, page_width, page_height, surface);
|
|
}
|
|
|
|
emit_completed_signal_t* ecs = g_malloc(sizeof(ecs));
|
|
ecs->renderer = renderer;
|
|
ecs->request = request;
|
|
ecs->surface = surface;
|
|
g_object_ref(renderer);
|
|
g_object_ref(request);
|
|
cairo_surface_reference(surface);
|
|
|
|
/* emit signal from the main context, i.e. the main thread */
|
|
g_main_context_invoke(NULL, emit_completed_signal, ecs);
|
|
|
|
cairo_surface_destroy(surface);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
render_job(void* data, void* user_data)
|
|
{
|
|
ZathuraRenderRequest* request = data;
|
|
ZathuraRenderer* renderer = user_data;
|
|
g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
if (priv->about_to_close == true || request_priv->aborted == true) {
|
|
/* back out early */
|
|
request_priv->requested = false;
|
|
return;
|
|
}
|
|
|
|
girara_debug("Rendering page %d ...",
|
|
zathura_page_get_index(request_priv->page) + 1);
|
|
if (render(request, renderer) != true) {
|
|
girara_error("Rendering failed (page %d)\n",
|
|
zathura_page_get_index(request_priv->page) + 1);
|
|
request_priv->requested = false;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
render_all(zathura_t* zathura)
|
|
{
|
|
if (zathura == NULL || zathura->document == NULL) {
|
|
return;
|
|
}
|
|
|
|
/* unmark all pages */
|
|
unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
|
|
for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
|
|
zathura_page_t* page = zathura_document_get_page(zathura->document,
|
|
page_id);
|
|
unsigned int page_height = 0, page_width = 0;
|
|
double height = zathura_page_get_height(page);
|
|
double width = zathura_page_get_width(page);
|
|
page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
|
|
|
|
GtkWidget* widget = zathura_page_get_widget(zathura, page);
|
|
gtk_widget_set_size_request(widget, page_width, page_height);
|
|
gtk_widget_queue_resize(widget);
|
|
}
|
|
}
|
|
|
|
static gint
|
|
render_thread_sort(gconstpointer a, gconstpointer b, gpointer UNUSED(data))
|
|
{
|
|
if (a == NULL || b == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
ZathuraRenderRequest* request_a = (ZathuraRenderRequest*) a;
|
|
ZathuraRenderRequest* request_b = (ZathuraRenderRequest*) b;
|
|
request_private_t* priv_a = REQUEST_GET_PRIVATE(request_a);
|
|
request_private_t* priv_b = REQUEST_GET_PRIVATE(request_b);
|
|
if (priv_a->aborted == priv_b->aborted) {
|
|
return priv_a->last_view_time < priv_b->last_view_time ? -1 :
|
|
(priv_a->last_view_time > priv_b->last_view_time ? 1 : 0);
|
|
}
|
|
|
|
/* sort aborted entries earlier so that the are thrown out of the queue */
|
|
return priv_a->aborted ? 1 : -1;
|
|
}
|
|
|
|
|
|
/* cache functions */
|
|
|
|
static bool
|
|
page_cache_is_cached(ZathuraRenderer* renderer, unsigned int page_index)
|
|
{
|
|
g_return_val_if_fail(renderer != NULL, false);
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
|
|
if (priv->page_cache.num_cached_pages != 0) {
|
|
for (size_t i = 0; i < priv->page_cache.size; ++i) {
|
|
if (priv->page_cache.cache[i] >= 0 &&
|
|
page_index == (unsigned int)priv->page_cache.cache[i]) {
|
|
girara_debug("Page %d is a cache hit", page_index + 1);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
girara_debug("Page %d is a cache miss", page_index + 1);
|
|
return false;
|
|
}
|
|
|
|
static int
|
|
find_request_by_page_index(const void* req, const void* data)
|
|
{
|
|
const ZathuraRenderRequest* request = req;
|
|
const unsigned int page_index = *((const int*)data);
|
|
|
|
request_private_t* priv = REQUEST_GET_PRIVATE(request);
|
|
if (zathura_page_get_index(priv->page) == page_index) {
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static ssize_t
|
|
page_cache_lru_invalidate(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_val_if_fail(renderer != NULL, -1);
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
g_return_val_if_fail(priv->page_cache.size != 0, -1);
|
|
|
|
ssize_t lru_index = 0;
|
|
gint64 lru_view_time = G_MAXINT64;
|
|
ZathuraRenderRequest* request = NULL;
|
|
for (size_t i = 0; i < priv->page_cache.size; ++i) {
|
|
ZathuraRenderRequest* tmp_request = girara_list_find(priv->requests,
|
|
find_request_by_page_index, &priv->page_cache.cache[i]);
|
|
g_return_val_if_fail(tmp_request != NULL, -1);
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(tmp_request);
|
|
|
|
if (request_priv->last_view_time < lru_view_time) {
|
|
lru_view_time = request_priv->last_view_time;
|
|
lru_index = i;
|
|
request = tmp_request;
|
|
}
|
|
}
|
|
|
|
request_private_t* request_priv = REQUEST_GET_PRIVATE(request);
|
|
|
|
/* emit the signal */
|
|
g_signal_emit(request, request_signals[REQUEST_CACHE_INVALIDATED], 0);
|
|
girara_debug("Invalidated page %d at cache index %zd",
|
|
zathura_page_get_index(request_priv->page) + 1, lru_index);
|
|
priv->page_cache.cache[lru_index] = -1;
|
|
--priv->page_cache.num_cached_pages;
|
|
|
|
return lru_index;
|
|
}
|
|
|
|
static bool
|
|
page_cache_is_full(ZathuraRenderer* renderer, bool* result)
|
|
{
|
|
g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer) && result != NULL, false);
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
*result = priv->page_cache.num_cached_pages == priv->page_cache.size;
|
|
|
|
return true;
|
|
}
|
|
|
|
static void
|
|
page_cache_invalidate_all(ZathuraRenderer* renderer)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
for (size_t i = 0; i < priv->page_cache.size; ++i) {
|
|
priv->page_cache.cache[i] = -1;
|
|
}
|
|
priv->page_cache.num_cached_pages = 0;
|
|
}
|
|
|
|
void
|
|
zathura_renderer_page_cache_add(ZathuraRenderer* renderer,
|
|
unsigned int page_index)
|
|
{
|
|
g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
|
|
if (page_cache_is_cached(renderer, page_index) == true) {
|
|
return;
|
|
}
|
|
|
|
private_t* priv = GET_PRIVATE(renderer);
|
|
bool full = false;
|
|
if (page_cache_is_full(renderer, &full) == false) {
|
|
return;
|
|
} else if (full == true) {
|
|
const ssize_t idx = page_cache_lru_invalidate(renderer);
|
|
if (idx == -1) {
|
|
return;
|
|
}
|
|
|
|
priv->page_cache.cache[idx] = page_index;
|
|
++priv->page_cache.num_cached_pages;
|
|
girara_debug("Page %d is cached at cache index %zd", page_index + 1, idx);
|
|
} else {
|
|
priv->page_cache.cache[priv->page_cache.num_cached_pages++] = page_index;
|
|
girara_debug("Page %d is cached at cache index %zu", page_index + 1,
|
|
priv->page_cache.num_cached_pages - 1);
|
|
}
|
|
|
|
ZathuraRenderRequest* request = girara_list_find(priv->requests,
|
|
find_request_by_page_index, &page_index);
|
|
g_return_if_fail(request != NULL);
|
|
g_signal_emit(request, request_signals[REQUEST_CACHE_ADDED], 0);
|
|
}
|