1
0
mirror of https://gitlab.gnome.org/GNOME/evince synced 2024-06-30 22:54:23 +00:00
evince/libview/ev-pixbuf-cache.c
Pablo Correa Gómez 33b251b6e1
libview: simplify freeing memory by better exploiting glib functions
Mostly g_clear_object and g_clear_pointer. But also some small
simplifications depending on the context.
2023-03-24 09:58:48 +01:00

1341 lines
39 KiB
C

#include <config.h>
#include "ev-pixbuf-cache.h"
#include "ev-job-scheduler.h"
#include "ev-view-private.h"
typedef enum {
SCROLL_DIRECTION_DOWN,
SCROLL_DIRECTION_UP
} ScrollDirection;
typedef struct _CacheJobInfo
{
EvJob *job;
gboolean page_ready;
/* Region of the page that needs to be drawn */
cairo_region_t *region;
/* Data we get from rendering */
cairo_surface_t *surface;
/* Device scale factor of target widget */
int device_scale;
/* Selection data.
* Selection_points are the coordinates encapsulated in selection.
* target_points is the target selection size. */
EvRectangle target_points;
EvSelectionStyle selection_style;
gboolean points_set;
cairo_surface_t *selection;
gdouble selection_scale;
EvRectangle selection_points;
cairo_region_t *selection_region;
gdouble selection_region_scale;
EvRectangle selection_region_points;
} CacheJobInfo;
struct _EvPixbufCache
{
GObject parent;
/* We keep a link to our containing view just for style information. */
GtkWidget *view;
EvDocument *document;
EvDocumentModel *model;
int start_page;
int end_page;
ScrollDirection scroll_direction;
gboolean inverted_colors;
gsize max_size;
/* preload_cache_size is the number of pages prior to the current
* visible area that we cache. It's normally 1, but could be 2 in the
* case of twin pages.
*/
int preload_cache_size;
guint job_list_len;
CacheJobInfo *prev_job;
CacheJobInfo *job_list;
CacheJobInfo *next_job;
};
struct _EvPixbufCacheClass
{
GObjectClass parent_class;
void (* job_finished) (EvPixbufCache *pixbuf_cache);
};
enum
{
JOB_FINISHED,
N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };
static void ev_pixbuf_cache_init (EvPixbufCache *pixbuf_cache);
static void ev_pixbuf_cache_class_init (EvPixbufCacheClass *pixbuf_cache);
static void ev_pixbuf_cache_finalize (GObject *object);
static void ev_pixbuf_cache_dispose (GObject *object);
static void job_finished_cb (EvJob *job,
EvPixbufCache *pixbuf_cache);
static CacheJobInfo *find_job_cache (EvPixbufCache *pixbuf_cache,
int page);
static gboolean new_selection_surface_needed(EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gfloat scale);
/* These are used for iterating through the prev and next arrays */
#define FIRST_VISIBLE_PREV(pixbuf_cache) \
(MAX (0, pixbuf_cache->preload_cache_size - pixbuf_cache->start_page))
#define VISIBLE_NEXT_LEN(pixbuf_cache) \
(MIN(pixbuf_cache->preload_cache_size, ev_document_get_n_pages (pixbuf_cache->document) - (1 + pixbuf_cache->end_page)))
#define PAGE_CACHE_LEN(pixbuf_cache) \
((pixbuf_cache->end_page - pixbuf_cache->start_page) + 1)
#define MAX_PRELOADED_PAGES 3
G_DEFINE_TYPE (EvPixbufCache, ev_pixbuf_cache, G_TYPE_OBJECT)
static void
ev_pixbuf_cache_init (EvPixbufCache *pixbuf_cache)
{
pixbuf_cache->start_page = -1;
pixbuf_cache->end_page = -1;
}
static void
ev_pixbuf_cache_class_init (EvPixbufCacheClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->finalize = ev_pixbuf_cache_finalize;
object_class->dispose = ev_pixbuf_cache_dispose;
signals[JOB_FINISHED] =
g_signal_new ("job-finished",
G_OBJECT_CLASS_TYPE (object_class),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_STRUCT_OFFSET (EvPixbufCacheClass, job_finished),
NULL, NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE, 1,
G_TYPE_POINTER);
}
static void
ev_pixbuf_cache_finalize (GObject *object)
{
EvPixbufCache *pixbuf_cache;
pixbuf_cache = EV_PIXBUF_CACHE (object);
if (pixbuf_cache->job_list) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->job_list_len,
pixbuf_cache->job_list);
pixbuf_cache->job_list = NULL;
}
if (pixbuf_cache->prev_job) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->preload_cache_size,
pixbuf_cache->prev_job);
pixbuf_cache->prev_job = NULL;
}
if (pixbuf_cache->next_job) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->preload_cache_size,
pixbuf_cache->next_job);
pixbuf_cache->next_job = NULL;
}
g_object_unref (pixbuf_cache->model);
G_OBJECT_CLASS (ev_pixbuf_cache_parent_class)->finalize (object);
}
static void
end_job (CacheJobInfo *job_info,
gpointer data)
{
g_signal_handlers_disconnect_by_func (job_info->job,
G_CALLBACK (job_finished_cb),
data);
ev_job_cancel (job_info->job);
g_clear_object (&job_info->job);
}
static void
dispose_cache_job_info (CacheJobInfo *job_info,
gpointer data)
{
if (job_info == NULL)
return;
if (job_info->job)
end_job (job_info, data);
g_clear_pointer (&job_info->surface, cairo_surface_destroy);
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
g_clear_pointer (&job_info->region, cairo_region_destroy);
g_clear_pointer (&job_info->selection_region, cairo_region_destroy);
job_info->points_set = FALSE;
}
static void
ev_pixbuf_cache_dispose (GObject *object)
{
EvPixbufCache *pixbuf_cache;
int i;
pixbuf_cache = EV_PIXBUF_CACHE (object);
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
dispose_cache_job_info (pixbuf_cache->prev_job + i, pixbuf_cache);
dispose_cache_job_info (pixbuf_cache->next_job + i, pixbuf_cache);
}
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
dispose_cache_job_info (pixbuf_cache->job_list + i, pixbuf_cache);
}
G_OBJECT_CLASS (ev_pixbuf_cache_parent_class)->dispose (object);
}
EvPixbufCache *
ev_pixbuf_cache_new (GtkWidget *view,
EvDocumentModel *model,
gsize max_size)
{
EvPixbufCache *pixbuf_cache;
pixbuf_cache = (EvPixbufCache *) g_object_new (EV_TYPE_PIXBUF_CACHE, NULL);
/* This is a backlink, so we don't ref this */
pixbuf_cache->view = view;
pixbuf_cache->model = g_object_ref (model);
pixbuf_cache->document = ev_document_model_get_document (model);
pixbuf_cache->max_size = max_size;
return pixbuf_cache;
}
void
ev_pixbuf_cache_set_max_size (EvPixbufCache *pixbuf_cache,
gsize max_size)
{
if (pixbuf_cache->max_size == max_size)
return;
if (pixbuf_cache->max_size > max_size)
ev_pixbuf_cache_clear (pixbuf_cache);
pixbuf_cache->max_size = max_size;
}
static int
get_device_scale (EvPixbufCache *pixbuf_cache)
{
#ifdef HAVE_HIDPI_SUPPORT
return gtk_widget_get_scale_factor (pixbuf_cache->view);
#else
return 1;
#endif
}
static void
set_device_scale_on_surface (cairo_surface_t *surface,
int device_scale)
{
#ifdef HAVE_HIDPI_SUPPORT
cairo_surface_set_device_scale (surface, device_scale, device_scale);
#else
g_return_if_fail (device_scale == 1);
#endif
}
static void
copy_job_to_job_info (EvJobRender *job_render,
CacheJobInfo *job_info,
EvPixbufCache *pixbuf_cache)
{
if (job_info->surface) {
cairo_surface_destroy (job_info->surface);
}
job_info->surface = cairo_surface_reference (job_render->surface);
set_device_scale_on_surface (job_info->surface, job_info->device_scale);
if (pixbuf_cache->inverted_colors) {
ev_document_misc_invert_surface (job_info->surface);
}
job_info->points_set = FALSE;
if (job_render->include_selection) {
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
g_clear_pointer (&job_info->selection_region, cairo_region_destroy);
job_info->selection_points = job_render->selection_points;
job_info->selection = cairo_surface_reference (job_render->selection);
if (job_info->selection)
set_device_scale_on_surface (job_info->selection, job_info->device_scale);
job_info->selection_scale = job_render->scale * job_info->device_scale;
g_assert (job_info->selection_points.x1 >= 0);
job_info->selection_region_points = job_render->selection_points;
job_info->selection_region = cairo_region_reference (job_render->selection_region);
job_info->selection_region_scale = job_render->scale;
job_info->points_set = TRUE;
}
if (job_info->job)
end_job (job_info, pixbuf_cache);
job_info->page_ready = TRUE;
}
static void
job_finished_cb (EvJob *job,
EvPixbufCache *pixbuf_cache)
{
CacheJobInfo *job_info;
EvJobRender *job_render = EV_JOB_RENDER (job);
/* If the job is outside of our interest, we silently discard it */
if ((job_render->page < (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size)) ||
(job_render->page > (pixbuf_cache->end_page + pixbuf_cache->preload_cache_size))) {
g_object_unref (job);
return;
}
job_info = find_job_cache (pixbuf_cache, job_render->page);
if (ev_job_is_failed (job)) {
g_clear_object (&job_info->job);
return;
}
copy_job_to_job_info (job_render, job_info, pixbuf_cache);
g_signal_emit (pixbuf_cache, signals[JOB_FINISHED], 0, job_info->region);
}
/* This checks a job to see if the job would generate the right sized pixbuf
* given a scale. If it won't, it removes the job and clears it to NULL.
*/
static void
check_job_size_and_unref (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gfloat scale)
{
gint width, height;
gint device_scale;
g_assert (job_info);
if (job_info->job == NULL)
return;
device_scale = get_device_scale (pixbuf_cache);
if (job_info->device_scale == device_scale) {
_get_page_size_for_scale_and_rotation (job_info->job->document,
EV_JOB_RENDER (job_info->job)->page,
scale,
EV_JOB_RENDER (job_info->job)->rotation,
&width, &height);
if (width * device_scale == EV_JOB_RENDER (job_info->job)->target_width &&
height * device_scale == EV_JOB_RENDER (job_info->job)->target_height)
return;
}
end_job (job_info, pixbuf_cache);
}
/* Do all function that copies a job from an older cache to it's position in the
* new cache. It clears the old job if it doesn't have a place.
*/
static void
move_one_job (CacheJobInfo *job_info,
EvPixbufCache *pixbuf_cache,
int page,
CacheJobInfo *new_job_list,
CacheJobInfo *new_prev_job,
CacheJobInfo *new_next_job,
int new_preload_cache_size,
int start_page,
int end_page,
gint priority)
{
CacheJobInfo *target_page = NULL;
int page_offset;
gint new_priority;
if (page < (start_page - new_preload_cache_size) ||
page > (end_page + new_preload_cache_size)) {
dispose_cache_job_info (job_info, pixbuf_cache);
return;
}
/* find the target page to copy it over to. */
if (page < start_page) {
page_offset = (page - (start_page - new_preload_cache_size));
g_assert (page_offset >= 0 &&
page_offset < new_preload_cache_size);
target_page = new_prev_job + page_offset;
new_priority = EV_JOB_PRIORITY_LOW;
} else if (page > end_page) {
page_offset = (page - (end_page + 1));
g_assert (page_offset >= 0 &&
page_offset < new_preload_cache_size);
target_page = new_next_job + page_offset;
new_priority = EV_JOB_PRIORITY_LOW;
} else {
page_offset = page - start_page;
g_assert (page_offset >= 0 &&
page_offset <= ((end_page - start_page) + 1));
new_priority = EV_JOB_PRIORITY_URGENT;
target_page = new_job_list + page_offset;
}
*target_page = *job_info;
job_info->job = NULL;
job_info->region = NULL;
job_info->surface = NULL;
if (new_priority != priority && target_page->job) {
ev_job_scheduler_update_job (target_page->job, new_priority);
}
}
static gsize
ev_pixbuf_cache_get_page_size (EvPixbufCache *pixbuf_cache,
gint page_index,
gdouble scale,
gint rotation)
{
gint width, height;
_get_page_size_for_scale_and_rotation (pixbuf_cache->document,
page_index, scale, rotation,
&width, &height);
return height * cairo_format_stride_for_width (CAIRO_FORMAT_RGB24, width);
}
static gint
ev_pixbuf_cache_get_preload_size (EvPixbufCache *pixbuf_cache,
gint start_page,
gint end_page,
gdouble scale,
gint rotation)
{
gsize range_size = 0;
gint new_preload_cache_size = 0;
gint i;
guint n_pages = ev_document_get_n_pages (pixbuf_cache->document);
/* Get the size of the current range */
for (i = start_page; i <= end_page; i++) {
range_size += ev_pixbuf_cache_get_page_size (pixbuf_cache, i, scale, rotation);
}
if (range_size >= pixbuf_cache->max_size)
return new_preload_cache_size;
i = 1;
while (((start_page - i > 0) || (end_page + i < n_pages)) &&
new_preload_cache_size < MAX_PRELOADED_PAGES) {
gsize page_size;
gboolean updated = FALSE;
if (end_page + i < n_pages) {
page_size = ev_pixbuf_cache_get_page_size (pixbuf_cache, end_page + i,
scale, rotation);
if (page_size + range_size <= pixbuf_cache->max_size) {
range_size += page_size;
new_preload_cache_size++;
updated = TRUE;
} else {
break;
}
}
if (start_page - i > 0) {
page_size = ev_pixbuf_cache_get_page_size (pixbuf_cache, start_page - i,
scale, rotation);
if (page_size + range_size <= pixbuf_cache->max_size) {
range_size += page_size;
if (!updated)
new_preload_cache_size++;
} else {
break;
}
}
i++;
}
return new_preload_cache_size;
}
static void
ev_pixbuf_cache_update_range (EvPixbufCache *pixbuf_cache,
gint start_page,
gint end_page,
guint rotation,
gdouble scale)
{
CacheJobInfo *new_job_list;
CacheJobInfo *new_prev_job = NULL;
CacheJobInfo *new_next_job = NULL;
gint new_preload_cache_size;
guint new_job_list_len;
int i, page;
new_preload_cache_size = ev_pixbuf_cache_get_preload_size (pixbuf_cache,
start_page,
end_page,
scale,
rotation);
if (pixbuf_cache->start_page == start_page &&
pixbuf_cache->end_page == end_page &&
pixbuf_cache->preload_cache_size == new_preload_cache_size)
return;
new_job_list_len = (end_page - start_page) + 1;
new_job_list = g_slice_alloc0 (sizeof (CacheJobInfo) * new_job_list_len);
if (new_preload_cache_size > 0) {
new_prev_job = g_slice_alloc0 (sizeof (CacheJobInfo) * new_preload_cache_size);
new_next_job = g_slice_alloc0 (sizeof (CacheJobInfo) * new_preload_cache_size);
}
/* We go through each job in the old cache and either clear it or move
* it to a new location. */
/* Start with the prev cache. */
page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size;
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page < 0) {
dispose_cache_job_info (pixbuf_cache->prev_job + i, pixbuf_cache);
} else {
move_one_job (pixbuf_cache->prev_job + i,
pixbuf_cache, page,
new_job_list, new_prev_job, new_next_job,
new_preload_cache_size,
start_page, end_page, EV_JOB_PRIORITY_LOW);
}
page ++;
}
page = pixbuf_cache->start_page;
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache) && page >= 0; i++) {
move_one_job (pixbuf_cache->job_list + i,
pixbuf_cache, page,
new_job_list, new_prev_job, new_next_job,
new_preload_cache_size,
start_page, end_page, EV_JOB_PRIORITY_URGENT);
page ++;
}
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page >= ev_document_get_n_pages (pixbuf_cache->document)) {
dispose_cache_job_info (pixbuf_cache->next_job + i, pixbuf_cache);
} else {
move_one_job (pixbuf_cache->next_job + i,
pixbuf_cache, page,
new_job_list, new_prev_job, new_next_job,
new_preload_cache_size,
start_page, end_page, EV_JOB_PRIORITY_LOW);
}
page ++;
}
if (pixbuf_cache->job_list) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->job_list_len,
pixbuf_cache->job_list);
}
if (pixbuf_cache->prev_job) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->preload_cache_size,
pixbuf_cache->prev_job);
}
if (pixbuf_cache->next_job) {
g_slice_free1 (sizeof (CacheJobInfo) * pixbuf_cache->preload_cache_size,
pixbuf_cache->next_job);
}
pixbuf_cache->preload_cache_size = new_preload_cache_size;
pixbuf_cache->job_list_len = new_job_list_len;
pixbuf_cache->job_list = new_job_list;
pixbuf_cache->prev_job = new_prev_job;
pixbuf_cache->next_job = new_next_job;
pixbuf_cache->start_page = start_page;
pixbuf_cache->end_page = end_page;
}
static CacheJobInfo *
find_job_cache (EvPixbufCache *pixbuf_cache,
int page)
{
int page_offset;
if (page < (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size) ||
page > (pixbuf_cache->end_page + pixbuf_cache->preload_cache_size))
return NULL;
if (page < pixbuf_cache->start_page) {
page_offset = (page - (pixbuf_cache->start_page - pixbuf_cache->preload_cache_size));
g_assert (page_offset >= 0 &&
page_offset < pixbuf_cache->preload_cache_size);
return pixbuf_cache->prev_job + page_offset;
}
if (page > pixbuf_cache->end_page) {
page_offset = (page - (pixbuf_cache->end_page + 1));
g_assert (page_offset >= 0 &&
page_offset < pixbuf_cache->preload_cache_size);
return pixbuf_cache->next_job + page_offset;
}
page_offset = page - pixbuf_cache->start_page;
g_assert (page_offset >= 0 &&
page_offset <= PAGE_CACHE_LEN(pixbuf_cache));
return pixbuf_cache->job_list + page_offset;
}
static void
ev_pixbuf_cache_clear_job_sizes (EvPixbufCache *pixbuf_cache,
gfloat scale)
{
int i;
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
check_job_size_and_unref (pixbuf_cache, pixbuf_cache->job_list + i, scale);
}
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
check_job_size_and_unref (pixbuf_cache, pixbuf_cache->prev_job + i, scale);
check_job_size_and_unref (pixbuf_cache, pixbuf_cache->next_job + i, scale);
}
}
static void
get_selection_colors (EvView *view, GdkColor *text, GdkColor *base)
{
GdkRGBA fg, bg;
_ev_view_get_selection_colors (view, &bg, &fg);
text->pixel = 0;
text->red = CLAMP ((guint) (fg.red * 65535), 0, 65535);
text->green = CLAMP ((guint) (fg.green * 65535), 0, 65535);
text->blue = CLAMP ((guint) (fg.blue * 65535), 0, 65535);
base->pixel = 0;
base->red = CLAMP ((guint) (bg.red * 65535), 0, 65535);
base->green = CLAMP ((guint) (bg.green * 65535), 0, 65535);
base->blue = CLAMP ((guint) (bg.blue * 65535), 0, 65535);
}
static void
add_job (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
cairo_region_t *region,
gint width,
gint height,
gint page,
gint rotation,
gfloat scale,
EvJobPriority priority)
{
job_info->device_scale = get_device_scale (pixbuf_cache);
job_info->page_ready = FALSE;
if (job_info->region)
cairo_region_destroy (job_info->region);
job_info->region = region ? cairo_region_reference (region) : NULL;
if (job_info->job)
end_job (job_info, pixbuf_cache);
job_info->job = ev_job_render_new (pixbuf_cache->document,
page, rotation,
scale * job_info->device_scale,
width * job_info->device_scale,
height * job_info->device_scale);
if (new_selection_surface_needed (pixbuf_cache, job_info, page, scale)) {
GdkColor text, base;
get_selection_colors (EV_VIEW (pixbuf_cache->view), &text, &base);
ev_job_render_set_selection_info (EV_JOB_RENDER (job_info->job),
&(job_info->target_points),
job_info->selection_style,
&text, &base);
}
g_signal_connect (job_info->job, "finished",
G_CALLBACK (job_finished_cb),
pixbuf_cache);
ev_job_scheduler_push_job (job_info->job, priority);
}
static void
add_job_if_needed (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gint rotation,
gfloat scale,
EvJobPriority priority)
{
gint device_scale = get_device_scale (pixbuf_cache);
gint width, height;
if (job_info->job)
return;
_get_page_size_for_scale_and_rotation (pixbuf_cache->document,
page, scale, rotation,
&width, &height);
if (job_info->surface &&
job_info->device_scale == device_scale &&
cairo_image_surface_get_width (job_info->surface) == width * device_scale &&
cairo_image_surface_get_height (job_info->surface) == height * device_scale)
return;
/* Free old surfaces for non visible pages */
if (priority == EV_JOB_PRIORITY_LOW) {
g_clear_pointer (&job_info->surface, cairo_surface_destroy);
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
}
add_job (pixbuf_cache, job_info, NULL,
width, height, page, rotation, scale,
priority);
}
static void
add_prev_jobs_if_needed (EvPixbufCache *pixbuf_cache,
gint rotation,
gfloat scale)
{
CacheJobInfo *job_info;
int page;
int i;
for (i = pixbuf_cache->preload_cache_size - 1; i >= FIRST_VISIBLE_PREV(pixbuf_cache); i--) {
job_info = (pixbuf_cache->prev_job + i);
page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size + i;
add_job_if_needed (pixbuf_cache, job_info,
page, rotation, scale,
EV_JOB_PRIORITY_LOW);
}
}
static void
add_next_jobs_if_needed (EvPixbufCache *pixbuf_cache,
gint rotation,
gfloat scale)
{
CacheJobInfo *job_info;
int page;
int i;
for (i = 0; i < VISIBLE_NEXT_LEN(pixbuf_cache); i++) {
job_info = (pixbuf_cache->next_job + i);
page = pixbuf_cache->end_page + 1 + i;
add_job_if_needed (pixbuf_cache, job_info,
page, rotation, scale,
EV_JOB_PRIORITY_LOW);
}
}
static void
ev_pixbuf_cache_add_jobs_if_needed (EvPixbufCache *pixbuf_cache,
gint rotation,
gfloat scale)
{
CacheJobInfo *job_info;
int page;
int i;
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
job_info = (pixbuf_cache->job_list + i);
page = pixbuf_cache->start_page + i;
add_job_if_needed (pixbuf_cache, job_info,
page, rotation, scale,
EV_JOB_PRIORITY_URGENT);
}
if (pixbuf_cache->scroll_direction == SCROLL_DIRECTION_UP) {
add_prev_jobs_if_needed (pixbuf_cache, rotation, scale);
add_next_jobs_if_needed (pixbuf_cache, rotation, scale);
} else {
add_next_jobs_if_needed (pixbuf_cache, rotation, scale);
add_prev_jobs_if_needed (pixbuf_cache, rotation, scale);
}
}
static ScrollDirection
ev_pixbuf_cache_get_scroll_direction (EvPixbufCache *pixbuf_cache,
gint start_page,
gint end_page)
{
if (start_page < pixbuf_cache->start_page)
return SCROLL_DIRECTION_UP;
if (end_page > pixbuf_cache->end_page)
return SCROLL_DIRECTION_DOWN;
if (start_page > pixbuf_cache->start_page)
return SCROLL_DIRECTION_DOWN;
if (end_page < pixbuf_cache->end_page)
return SCROLL_DIRECTION_UP;
return pixbuf_cache->scroll_direction;
}
void
ev_pixbuf_cache_set_page_range (EvPixbufCache *pixbuf_cache,
gint start_page,
gint end_page,
GList *selection_list)
{
gdouble scale = ev_document_model_get_scale (pixbuf_cache->model);
gint rotation = ev_document_model_get_rotation (pixbuf_cache->model);
g_return_if_fail (EV_IS_PIXBUF_CACHE (pixbuf_cache));
g_return_if_fail (start_page >= 0 && start_page < ev_document_get_n_pages (pixbuf_cache->document));
g_return_if_fail (end_page >= 0 && end_page < ev_document_get_n_pages (pixbuf_cache->document));
g_return_if_fail (end_page >= start_page);
pixbuf_cache->scroll_direction = ev_pixbuf_cache_get_scroll_direction (pixbuf_cache, start_page, end_page);
/* First, resize the page_range as needed. We cull old pages
* mercilessly. */
ev_pixbuf_cache_update_range (pixbuf_cache, start_page, end_page, rotation, scale);
/* Then, we update the current jobs to see if any of them are the wrong
* size, we remove them if we need to. */
ev_pixbuf_cache_clear_job_sizes (pixbuf_cache, scale);
/* Next, we update the target selection for our pages */
ev_pixbuf_cache_set_selection_list (pixbuf_cache, selection_list);
/* Finally, we add the new jobs for all the sizes that don't have a
* pixbuf */
ev_pixbuf_cache_add_jobs_if_needed (pixbuf_cache, rotation, scale);
}
void
ev_pixbuf_cache_set_inverted_colors (EvPixbufCache *pixbuf_cache,
gboolean inverted_colors)
{
gint i;
if (pixbuf_cache->inverted_colors == inverted_colors)
return;
pixbuf_cache->inverted_colors = inverted_colors;
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
CacheJobInfo *job_info;
job_info = pixbuf_cache->prev_job + i;
if (job_info && job_info->surface)
ev_document_misc_invert_surface (job_info->surface);
job_info = pixbuf_cache->next_job + i;
if (job_info && job_info->surface)
ev_document_misc_invert_surface (job_info->surface);
}
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
CacheJobInfo *job_info;
job_info = pixbuf_cache->job_list + i;
if (job_info && job_info->surface)
ev_document_misc_invert_surface (job_info->surface);
}
}
cairo_surface_t *
ev_pixbuf_cache_get_surface (EvPixbufCache *pixbuf_cache,
gint page)
{
CacheJobInfo *job_info;
job_info = find_job_cache (pixbuf_cache, page);
if (job_info == NULL)
return NULL;
if (job_info->page_ready)
return job_info->surface;
/* We don't need to wait for the idle to handle the callback */
if (job_info->job &&
EV_JOB_RENDER (job_info->job)->page_ready) {
copy_job_to_job_info (EV_JOB_RENDER (job_info->job), job_info, pixbuf_cache);
g_signal_emit (pixbuf_cache, signals[JOB_FINISHED], 0, job_info->region);
}
return job_info->surface;
}
static gboolean
new_selection_surface_needed (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gfloat scale)
{
if (job_info->selection)
return job_info->selection_scale != scale;
return job_info->points_set;
}
static gboolean
new_selection_region_needed (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gfloat scale)
{
if (job_info->selection_region)
return job_info->selection_region_scale != scale;
return job_info->points_set;
}
static void
clear_selection_surface_if_needed (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gfloat scale)
{
if (new_selection_surface_needed (pixbuf_cache, job_info, page, scale)) {
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
job_info->selection_points.x1 = -1;
}
}
static void
clear_selection_region_if_needed (EvPixbufCache *pixbuf_cache,
CacheJobInfo *job_info,
gint page,
gfloat scale)
{
if (new_selection_region_needed (pixbuf_cache, job_info, page, scale)) {
g_clear_pointer (&job_info->selection_region, cairo_region_destroy);
job_info->selection_region_points.x1 = -1;
}
}
/* Clears the cache of jobs and pixbufs.
*/
void
ev_pixbuf_cache_clear (EvPixbufCache *pixbuf_cache)
{
int i;
if (!pixbuf_cache->job_list)
return;
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
dispose_cache_job_info (pixbuf_cache->prev_job + i, pixbuf_cache);
dispose_cache_job_info (pixbuf_cache->next_job + i, pixbuf_cache);
}
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
dispose_cache_job_info (pixbuf_cache->job_list + i, pixbuf_cache);
}
}
void
ev_pixbuf_cache_style_changed (EvPixbufCache *pixbuf_cache)
{
gint i;
if (!pixbuf_cache->job_list)
return;
/* FIXME: doesn't update running jobs. */
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
CacheJobInfo *job_info;
job_info = pixbuf_cache->prev_job + i;
if (job_info->selection) {
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
job_info->selection_points.x1 = -1;
}
job_info = pixbuf_cache->next_job + i;
if (job_info->selection) {
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
job_info->selection_points.x1 = -1;
}
}
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
CacheJobInfo *job_info;
job_info = pixbuf_cache->job_list + i;
if (job_info->selection) {
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
job_info->selection_points.x1 = -1;
}
}
}
cairo_surface_t *
ev_pixbuf_cache_get_selection_surface (EvPixbufCache *pixbuf_cache,
gint page,
gfloat scale)
{
CacheJobInfo *job_info;
/* the document does not implement the selection interface */
if (!EV_IS_SELECTION (pixbuf_cache->document))
return NULL;
job_info = find_job_cache (pixbuf_cache, page);
if (job_info == NULL)
return NULL;
/* No selection on this page */
if (!job_info->points_set)
return NULL;
/* If we have a running job, we just return what we have under the
* assumption that it'll be updated later and we can scale it as need
* be */
if (job_info->job && EV_JOB_RENDER (job_info->job)->include_selection)
return job_info->selection;
/* Now, lets see if we need to resize the image. If we do, we clear the
* old one. */
clear_selection_surface_if_needed (pixbuf_cache, job_info, page, scale);
/* Finally, we see if the two scales are the same, and get a new pixbuf
* if needed. We do this synchronously for now. At some point, we
* _should_ be able to get rid of the doc_mutex, so the synchronicity
* doesn't kill us. Rendering a few glyphs should really be fast.
*/
if (ev_rect_cmp (&(job_info->target_points), &(job_info->selection_points))) {
EvRectangle *old_points;
GdkColor text, base;
EvRenderContext *rc;
EvPage *ev_page;
gint width, height;
/* we need to get a new selection pixbuf */
ev_document_doc_mutex_lock ();
if (job_info->selection_points.x1 < 0) {
g_assert (job_info->selection == NULL);
old_points = NULL;
} else {
old_points = &(job_info->selection_points);
}
ev_page = ev_document_get_page (pixbuf_cache->document, page);
_get_page_size_for_scale_and_rotation (pixbuf_cache->document,
page,
scale * job_info->device_scale,
0, &width, &height);
rc = ev_render_context_new (ev_page, 0, scale * job_info->device_scale);
ev_render_context_set_target_size (rc, width, height);
g_object_unref (ev_page);
get_selection_colors (EV_VIEW (pixbuf_cache->view), &text, &base);
ev_selection_render_selection (EV_SELECTION (pixbuf_cache->document),
rc, &(job_info->selection),
&(job_info->target_points),
old_points,
job_info->selection_style,
&text, &base);
if (job_info->selection)
set_device_scale_on_surface (job_info->selection, job_info->device_scale);
job_info->selection_points = job_info->target_points;
job_info->selection_scale = scale * job_info->device_scale;
g_object_unref (rc);
ev_document_doc_mutex_unlock ();
}
return job_info->selection;
}
cairo_region_t *
ev_pixbuf_cache_get_selection_region (EvPixbufCache *pixbuf_cache,
gint page,
gfloat scale)
{
CacheJobInfo *job_info;
/* the document does not implement the selection interface */
if (!EV_IS_SELECTION (pixbuf_cache->document))
return NULL;
job_info = find_job_cache (pixbuf_cache, page);
if (job_info == NULL)
return NULL;
/* No selection on this page */
if (!job_info->points_set)
return NULL;
/* If we have a running job, we just return what we have under the
* assumption that it'll be updated later and we can scale it as need
* be */
if (job_info->job && EV_JOB_RENDER (job_info->job)->include_selection)
return job_info->selection_region && !cairo_region_is_empty(job_info->selection_region) ?
job_info->selection_region : NULL;
/* Now, lets see if we need to resize the region. If we do, we clear the
* old one. */
clear_selection_region_if_needed (pixbuf_cache, job_info, page, scale);
/* Finally, we see if the two scales are the same, and get a new region
* if needed.
*/
if (ev_rect_cmp (&(job_info->target_points), &(job_info->selection_region_points))) {
EvRenderContext *rc;
EvPage *ev_page;
gint width, height;
ev_document_doc_mutex_lock ();
ev_page = ev_document_get_page (pixbuf_cache->document, page);
_get_page_size_for_scale_and_rotation (pixbuf_cache->document,
page, scale, 0,
&width, &height);
rc = ev_render_context_new (ev_page, 0, 0.);
ev_render_context_set_target_size (rc, width, height);
g_object_unref (ev_page);
if (job_info->selection_region)
cairo_region_destroy (job_info->selection_region);
job_info->selection_region =
ev_selection_get_selection_region (EV_SELECTION (pixbuf_cache->document),
rc, job_info->selection_style,
&(job_info->target_points));
job_info->selection_region_points = job_info->target_points;
job_info->selection_region_scale = scale;
g_object_unref (rc);
ev_document_doc_mutex_unlock ();
}
return job_info->selection_region && !cairo_region_is_empty(job_info->selection_region) ?
job_info->selection_region : NULL;
}
static void
update_job_selection (CacheJobInfo *job_info,
EvViewSelection *selection)
{
job_info->points_set = TRUE;
job_info->target_points = selection->rect;
job_info->selection_style = selection->style;
}
static void
clear_job_selection (CacheJobInfo *job_info)
{
job_info->points_set = FALSE;
job_info->selection_points.x1 = -1;
g_clear_pointer (&job_info->selection, cairo_surface_destroy);
g_clear_pointer (&job_info->selection_region, cairo_region_destroy);
}
/* This function will reset the selection on pages that no longer have them, and
* will update the target_selection on those that need it. It will _not_ free
* the previous selection_list -- that's up to caller to do.
*/
void
ev_pixbuf_cache_set_selection_list (EvPixbufCache *pixbuf_cache,
GList *selection_list)
{
EvViewSelection *selection;
GList *list = selection_list;
int page;
int i;
g_return_if_fail (EV_IS_PIXBUF_CACHE (pixbuf_cache));
if (!EV_IS_SELECTION (pixbuf_cache->document))
return;
if (pixbuf_cache->start_page == -1 || pixbuf_cache->end_page == -1)
return;
/* We check each area to see what needs updating, and what needs freeing; */
page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size;
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page < 0) {
page ++;
continue;
}
selection = NULL;
while (list) {
if (((EvViewSelection *)list->data)->page == page) {
selection = list->data;
break;
} else if (((EvViewSelection *)list->data)->page > page)
break;
list = list->next;
}
if (selection)
update_job_selection (pixbuf_cache->prev_job + i, selection);
else
clear_job_selection (pixbuf_cache->prev_job + i);
page ++;
}
page = pixbuf_cache->start_page;
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
selection = NULL;
while (list) {
if (((EvViewSelection *)list->data)->page == page) {
selection = list->data;
break;
} else if (((EvViewSelection *)list->data)->page > page)
break;
list = list->next;
}
if (selection)
update_job_selection (pixbuf_cache->job_list + i, selection);
else
clear_job_selection (pixbuf_cache->job_list + i);
page ++;
}
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page >= ev_document_get_n_pages (pixbuf_cache->document))
break;
selection = NULL;
while (list) {
if (((EvViewSelection *)list->data)->page == page) {
selection = list->data;
break;
} else if (((EvViewSelection *)list->data)->page > page)
break;
list = list->next;
}
if (selection)
update_job_selection (pixbuf_cache->next_job + i, selection);
else
clear_job_selection (pixbuf_cache->next_job + i);
page ++;
}
}
/* Returns what the pixbuf cache thinks is */
GList *
ev_pixbuf_cache_get_selection_list (EvPixbufCache *pixbuf_cache)
{
EvViewSelection *selection;
GList *retval = NULL;
int page;
int i;
g_return_val_if_fail (EV_IS_PIXBUF_CACHE (pixbuf_cache), NULL);
if (pixbuf_cache->start_page == -1 || pixbuf_cache->end_page == -1)
return NULL;
/* We check each area to see what needs updating, and what needs freeing; */
page = pixbuf_cache->start_page - pixbuf_cache->preload_cache_size;
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page < 0) {
page ++;
continue;
}
if (pixbuf_cache->prev_job[i].selection_points.x1 != -1) {
selection = g_slice_new0 (EvViewSelection);
selection->page = page;
selection->rect = pixbuf_cache->prev_job[i].selection_points;
if (pixbuf_cache->prev_job[i].selection_region)
selection->covered_region = cairo_region_reference (pixbuf_cache->prev_job[i].selection_region);
retval = g_list_prepend (retval, selection);
}
page ++;
}
page = pixbuf_cache->start_page;
for (i = 0; i < PAGE_CACHE_LEN (pixbuf_cache); i++) {
if (pixbuf_cache->job_list[i].selection_points.x1 != -1) {
selection = g_slice_new0 (EvViewSelection);
selection->page = page;
selection->rect = pixbuf_cache->job_list[i].selection_points;
if (pixbuf_cache->job_list[i].selection_region)
selection->covered_region = cairo_region_reference (pixbuf_cache->job_list[i].selection_region);
retval = g_list_prepend (retval, selection);
}
page ++;
}
for (i = 0; i < pixbuf_cache->preload_cache_size; i++) {
if (page >= ev_document_get_n_pages (pixbuf_cache->document))
break;
if (pixbuf_cache->next_job[i].selection_points.x1 != -1) {
selection = g_slice_new0 (EvViewSelection);
selection->page = page;
selection->rect = pixbuf_cache->next_job[i].selection_points;
if (pixbuf_cache->next_job[i].selection_region)
selection->covered_region = cairo_region_reference (pixbuf_cache->next_job[i].selection_region);
retval = g_list_prepend (retval, selection);
}
page ++;
}
return g_list_reverse (retval);
}
void
ev_pixbuf_cache_reload_page (EvPixbufCache *pixbuf_cache,
cairo_region_t *region,
gint page,
gint rotation,
gdouble scale)
{
CacheJobInfo *job_info;
gint width, height;
job_info = find_job_cache (pixbuf_cache, page);
if (job_info == NULL)
return;
_get_page_size_for_scale_and_rotation (pixbuf_cache->document,
page, scale, rotation,
&width, &height);
add_job (pixbuf_cache, job_info, region,
width, height, page, rotation, scale,
EV_JOB_PRIORITY_URGENT);
}