1 /* SPDX-License-Identifier: Zlib */
2 
3 #include <math.h>
4 #include <string.h>
5 #include <girara/datastructures.h>
6 #include <girara/utils.h>
7 
8 #include "render.h"
9 #include "adjustment.h"
10 #include "zathura.h"
11 #include "document.h"
12 #include "page.h"
13 #include "page-widget.h"
14 #include "utils.h"
15 
16 /* private data for ZathuraRenderer */
17 typedef struct private_s {
18   GThreadPool* pool; /**< Pool of threads */
19   GMutex mutex; /**< Render lock */
20   volatile bool about_to_close; /**< Render thread is to be freed */
21 
22   /**
23    * recolor information
24    */
25   struct {
26     bool enabled;
27     bool hue;
28     bool reverse_video;
29 
30     GdkRGBA light;
31     GdkRGBA dark;
32   } recolor;
33 
34   /*
35    * page cache
36    */
37   struct {
38     int* cache;
39     size_t size;
40     size_t num_cached_pages;
41   } page_cache;
42 
43   /* render requests */
44   girara_list_t* requests;
45 } ZathuraRendererPrivate;
46 
47 /* private data for ZathuraRenderRequest */
48 typedef struct request_private_s {
49   ZathuraRenderer* renderer;
50   zathura_page_t* page;
51   gint64 last_view_time;
52   girara_list_t* active_jobs;
53   GMutex jobs_mutex;
54   bool render_plain;
55 } ZathuraRenderRequestPrivate;
56 
57 /* define the two types */
58 G_DEFINE_TYPE_WITH_CODE(ZathuraRenderer, zathura_renderer, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderer))
59 G_DEFINE_TYPE_WITH_CODE(ZathuraRenderRequest, zathura_render_request, G_TYPE_OBJECT, G_ADD_PRIVATE(ZathuraRenderRequest))
60 
61 /* private methods for ZathuraRenderer  */
62 static void renderer_finalize(GObject* object);
63 /* private methods for ZathuraRenderRequest */
64 static void render_request_dispose(GObject* object);
65 static void render_request_finalize(GObject* object);
66 
67 static void render_job(void* data, void* user_data);
68 static gint render_thread_sort(gconstpointer a, gconstpointer b, gpointer data);
69 static ssize_t page_cache_lru_invalidate(ZathuraRenderer* renderer);
70 static void page_cache_invalidate_all(ZathuraRenderer* renderer);
71 static bool page_cache_is_full(ZathuraRenderer* renderer, bool* result);
72 
73 /* job descritption for render thread */
74 typedef struct render_job_s {
75   ZathuraRenderRequest* request;
76   volatile bool aborted;
77 } render_job_t;
78 
79 /* init, new and free for ZathuraRenderer */
80 
81 static void
zathura_renderer_class_init(ZathuraRendererClass * class)82 zathura_renderer_class_init(ZathuraRendererClass* class)
83 {
84   /* overwrite methods */
85   GObjectClass* object_class = G_OBJECT_CLASS(class);
86   object_class->finalize     = renderer_finalize;
87 }
88 
89 static void
zathura_renderer_init(ZathuraRenderer * renderer)90 zathura_renderer_init(ZathuraRenderer* renderer)
91 {
92   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
93   priv->pool = g_thread_pool_new(render_job, renderer, 1, TRUE, NULL);
94   priv->about_to_close = false;
95   g_thread_pool_set_sort_function(priv->pool, render_thread_sort, NULL);
96   g_mutex_init(&priv->mutex);
97 
98   /* recolor */
99   priv->recolor.enabled = false;
100   priv->recolor.hue = true;
101   priv->recolor.reverse_video = false;
102 
103   /* page cache */
104   priv->page_cache.size = 0;
105   priv->page_cache.cache = NULL;
106   priv->page_cache.num_cached_pages = 0;
107 
108   zathura_renderer_set_recolor_colors_str(renderer, "#000000", "#FFFFFF");
109 
110   priv->requests = girara_list_new();
111 }
112 
113 static bool
page_cache_init(ZathuraRenderer * renderer,size_t cache_size)114 page_cache_init(ZathuraRenderer* renderer, size_t cache_size)
115 {
116   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
117 
118   priv->page_cache.size  = cache_size;
119   priv->page_cache.cache = g_try_malloc0(cache_size * sizeof(int));
120   if (priv->page_cache.cache == NULL) {
121     return false;
122   }
123 
124   page_cache_invalidate_all(renderer);
125   return true;
126 }
127 
128 ZathuraRenderer*
zathura_renderer_new(size_t cache_size)129 zathura_renderer_new(size_t cache_size)
130 {
131   g_return_val_if_fail(cache_size > 0, NULL);
132 
133   GObject* obj = g_object_new(ZATHURA_TYPE_RENDERER, NULL);
134   ZathuraRenderer* ret = ZATHURA_RENDERER(obj);
135 
136   if (page_cache_init(ret, cache_size) == false) {
137     g_object_unref(obj);
138     return NULL;
139   }
140 
141   return ret;
142 }
143 
144 static void
renderer_finalize(GObject * object)145 renderer_finalize(GObject* object)
146 {
147   ZathuraRenderer* renderer = ZATHURA_RENDERER(object);
148   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
149 
150   zathura_renderer_stop(renderer);
151   if (priv->pool != NULL) {
152     g_thread_pool_free(priv->pool, TRUE, TRUE);
153   }
154   g_mutex_clear(&(priv->mutex));
155 
156   g_free(priv->page_cache.cache);
157   girara_list_free(priv->requests);
158 }
159 
160 /* (un)register requests at the renderer */
161 
162 static void
renderer_unregister_request(ZathuraRenderer * renderer,ZathuraRenderRequest * request)163 renderer_unregister_request(ZathuraRenderer* renderer,
164     ZathuraRenderRequest* request)
165 {
166   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
167   girara_list_remove(priv->requests, request);
168 }
169 
170 static void
renderer_register_request(ZathuraRenderer * renderer,ZathuraRenderRequest * request)171 renderer_register_request(ZathuraRenderer* renderer,
172     ZathuraRenderRequest* request)
173 {
174   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
175   if (girara_list_contains(priv->requests, request) == false) {
176     girara_list_append(priv->requests, request);
177   }
178 }
179 
180 /* init, new and free for ZathuraRenderRequest */
181 
182 enum {
183   REQUEST_COMPLETED,
184   REQUEST_CACHE_ADDED,
185   REQUEST_CACHE_INVALIDATED,
186   REQUEST_LAST_SIGNAL
187 };
188 
189 static guint request_signals[REQUEST_LAST_SIGNAL] = { 0 };
190 
191 static void
zathura_render_request_class_init(ZathuraRenderRequestClass * class)192 zathura_render_request_class_init(ZathuraRenderRequestClass* class)
193 {
194   /* overwrite methods */
195   GObjectClass* object_class = G_OBJECT_CLASS(class);
196   object_class->dispose      = render_request_dispose;
197   object_class->finalize     = render_request_finalize;
198 
199   request_signals[REQUEST_COMPLETED] = g_signal_new("completed",
200       ZATHURA_TYPE_RENDER_REQUEST,
201       G_SIGNAL_RUN_LAST,
202       0,
203       NULL,
204       NULL,
205       g_cclosure_marshal_generic,
206       G_TYPE_NONE,
207       1,
208       G_TYPE_POINTER);
209 
210   request_signals[REQUEST_CACHE_ADDED] = g_signal_new("cache-added",
211       ZATHURA_TYPE_RENDER_REQUEST,
212       G_SIGNAL_RUN_LAST,
213       0,
214       NULL,
215       NULL,
216       g_cclosure_marshal_generic,
217       G_TYPE_NONE,
218       0);
219 
220   request_signals[REQUEST_CACHE_INVALIDATED] = g_signal_new("cache-invalidated",
221       ZATHURA_TYPE_RENDER_REQUEST,
222       G_SIGNAL_RUN_LAST,
223       0,
224       NULL,
225       NULL,
226       g_cclosure_marshal_generic,
227       G_TYPE_NONE,
228       0);
229 }
230 
231 static void
zathura_render_request_init(ZathuraRenderRequest * request)232 zathura_render_request_init(ZathuraRenderRequest* request)
233 {
234   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
235   priv->renderer = NULL;
236   priv->page = NULL;
237 }
238 
239 ZathuraRenderRequest*
zathura_render_request_new(ZathuraRenderer * renderer,zathura_page_t * page)240 zathura_render_request_new(ZathuraRenderer* renderer, zathura_page_t* page)
241 {
242   g_return_val_if_fail(renderer != NULL && page != NULL, NULL);
243 
244   GObject* obj = g_object_new(ZATHURA_TYPE_RENDER_REQUEST, NULL);
245   if (obj == NULL) {
246     return NULL;
247   }
248 
249   ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(obj);
250   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
251   /* we want to make sure that renderer lives long enough */
252   priv->renderer = g_object_ref(renderer);
253   priv->page = page;
254   priv->active_jobs = girara_list_new();
255   g_mutex_init(&priv->jobs_mutex);
256   priv->render_plain = false;
257 
258   /* register the request with the renderer */
259   renderer_register_request(renderer, request);
260 
261   return request;
262 }
263 
264 static void
render_request_dispose(GObject * object)265 render_request_dispose(GObject* object)
266 {
267   ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
268   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
269 
270   if (priv->renderer != NULL) {
271     /* unregister the request */
272     renderer_unregister_request(priv->renderer, request);
273     /* release our private reference to the renderer */
274     g_clear_object(&priv->renderer);
275   }
276 
277   G_OBJECT_CLASS(zathura_render_request_parent_class)->dispose(object);
278 }
279 
280 static void
render_request_finalize(GObject * object)281 render_request_finalize(GObject* object)
282 {
283   ZathuraRenderRequest* request = ZATHURA_RENDER_REQUEST(object);
284   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
285 
286   if (girara_list_size(priv->active_jobs) != 0) {
287     girara_error("This should not happen!");
288   }
289   girara_list_free(priv->active_jobs);
290   g_mutex_clear(&priv->jobs_mutex);
291 
292   G_OBJECT_CLASS(zathura_render_request_parent_class)->finalize(object);
293 }
294 
295 /* renderer methods */
296 
297 bool
zathura_renderer_recolor_enabled(ZathuraRenderer * renderer)298 zathura_renderer_recolor_enabled(ZathuraRenderer* renderer)
299 {
300   g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
301 
302   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
303   return priv->recolor.enabled;
304 }
305 
306 void
zathura_renderer_enable_recolor(ZathuraRenderer * renderer,bool enable)307 zathura_renderer_enable_recolor(ZathuraRenderer* renderer, bool enable)
308 {
309   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
310 
311   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
312   priv->recolor.enabled = enable;
313 }
314 
315 bool
zathura_renderer_recolor_hue_enabled(ZathuraRenderer * renderer)316 zathura_renderer_recolor_hue_enabled(ZathuraRenderer* renderer)
317 {
318   g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
319 
320   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
321   return priv->recolor.hue;
322 }
323 
324 void
zathura_renderer_enable_recolor_hue(ZathuraRenderer * renderer,bool enable)325 zathura_renderer_enable_recolor_hue(ZathuraRenderer* renderer, bool enable)
326 {
327   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
328 
329   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
330   priv->recolor.hue = enable;
331 }
332 
333 bool
zathura_renderer_recolor_reverse_video_enabled(ZathuraRenderer * renderer)334 zathura_renderer_recolor_reverse_video_enabled(ZathuraRenderer* renderer)
335 {
336   g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer), false);
337 
338   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
339   return priv->recolor.reverse_video;
340 }
341 
342 void
zathura_renderer_enable_recolor_reverse_video(ZathuraRenderer * renderer,bool enable)343 zathura_renderer_enable_recolor_reverse_video(ZathuraRenderer* renderer, bool enable)
344 {
345   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
346 
347   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
348   priv->recolor.reverse_video = enable;
349 }
350 void
zathura_renderer_set_recolor_colors(ZathuraRenderer * renderer,const GdkRGBA * light,const GdkRGBA * dark)351 zathura_renderer_set_recolor_colors(ZathuraRenderer* renderer,
352     const GdkRGBA* light, const GdkRGBA* dark)
353 {
354   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
355 
356   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
357   if (light != NULL) {
358     memcpy(&priv->recolor.light, light, sizeof(GdkRGBA));
359   }
360   if (dark != NULL) {
361     memcpy(&priv->recolor.dark, dark, sizeof(GdkRGBA));
362   }
363 }
364 
365 void
zathura_renderer_set_recolor_colors_str(ZathuraRenderer * renderer,const char * light,const char * dark)366 zathura_renderer_set_recolor_colors_str(ZathuraRenderer* renderer,
367     const char* light, const char* dark)
368 {
369   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
370 
371   if (dark != NULL) {
372     GdkRGBA color;
373     if (parse_color(&color, dark) == true) {
374       zathura_renderer_set_recolor_colors(renderer, NULL, &color);
375     }
376   }
377   if (light != NULL) {
378     GdkRGBA color;
379     if (parse_color(&color, light) == true) {
380       zathura_renderer_set_recolor_colors(renderer, &color, NULL);
381     }
382   }
383 }
384 
385 void
zathura_renderer_get_recolor_colors(ZathuraRenderer * renderer,GdkRGBA * light,GdkRGBA * dark)386 zathura_renderer_get_recolor_colors(ZathuraRenderer* renderer,
387     GdkRGBA* light, GdkRGBA* dark)
388 {
389   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
390 
391   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
392   if (light != NULL) {
393     memcpy(light, &priv->recolor.light, sizeof(GdkRGBA));
394   }
395   if (dark != NULL) {
396     memcpy(dark, &priv->recolor.dark, sizeof(GdkRGBA));
397   }
398 }
399 
400 void
zathura_renderer_lock(ZathuraRenderer * renderer)401 zathura_renderer_lock(ZathuraRenderer* renderer)
402 {
403   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
404 
405   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
406   g_mutex_lock(&priv->mutex);
407 }
408 
409 void
zathura_renderer_unlock(ZathuraRenderer * renderer)410 zathura_renderer_unlock(ZathuraRenderer* renderer)
411 {
412   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
413 
414   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
415   g_mutex_unlock(&priv->mutex);
416 }
417 
418 void
zathura_renderer_stop(ZathuraRenderer * renderer)419 zathura_renderer_stop(ZathuraRenderer* renderer)
420 {
421   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
422   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
423   priv->about_to_close = true;
424 }
425 
426 
427 /* ZathuraRenderRequest methods */
428 
429 void
zathura_render_request(ZathuraRenderRequest * request,gint64 last_view_time)430 zathura_render_request(ZathuraRenderRequest* request, gint64 last_view_time)
431 {
432   g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
433 
434   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
435   g_mutex_lock(&request_priv->jobs_mutex);
436 
437   bool unfinished_jobs = false;
438   /* check if there are any active jobs left */
439   GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
440     if (job->aborted == false) {
441       unfinished_jobs = true;
442       break;
443     }
444   );
445 
446   /* only add a new job if there are no active ones left */
447   if (unfinished_jobs == false) {
448     request_priv->last_view_time = last_view_time;
449 
450     render_job_t* job = g_try_malloc0(sizeof(render_job_t));
451     if (job == NULL) {
452       return;
453     }
454 
455     job->request = g_object_ref(request);
456     job->aborted = false;
457     girara_list_append(request_priv->active_jobs, job);
458 
459     ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
460     g_thread_pool_push(priv->pool, job, NULL);
461   }
462 
463   g_mutex_unlock(&request_priv->jobs_mutex);
464 }
465 
466 void
zathura_render_request_abort(ZathuraRenderRequest * request)467 zathura_render_request_abort(ZathuraRenderRequest* request)
468 {
469   g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
470 
471   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
472   g_mutex_lock(&request_priv->jobs_mutex);
473   GIRARA_LIST_FOREACH_BODY(request_priv->active_jobs, render_job_t*, job,
474     job->aborted = true;
475   );
476   g_mutex_unlock(&request_priv->jobs_mutex);
477 }
478 
479 void
zathura_render_request_update_view_time(ZathuraRenderRequest * request)480 zathura_render_request_update_view_time(ZathuraRenderRequest* request)
481 {
482   g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
483 
484   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
485   request_priv->last_view_time = g_get_real_time();
486 }
487 
488 /* render job */
489 
490 static void
remove_job_and_free(render_job_t * job)491 remove_job_and_free(render_job_t* job)
492 {
493   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(job->request);
494 
495   g_mutex_lock(&request_priv->jobs_mutex);
496   girara_list_remove(request_priv->active_jobs, job);
497   g_mutex_unlock(&request_priv->jobs_mutex);
498 
499   g_object_unref(job->request);
500   g_free(job);
501 }
502 
503 typedef struct emit_completed_signal_s
504 {
505   render_job_t* job;
506   cairo_surface_t* surface;
507 } emit_completed_signal_t;
508 
509 static gboolean
emit_completed_signal(void * data)510 emit_completed_signal(void* data)
511 {
512   emit_completed_signal_t* ecs = data;
513   render_job_t* job = ecs->job;
514   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(job->request);
515   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(request_priv->renderer);
516 
517   if (priv->about_to_close == false && job->aborted == false) {
518     /* emit the signal */
519     girara_debug("Emitting signal for page %d",
520         zathura_page_get_index(request_priv->page) + 1);
521     g_signal_emit(job->request, request_signals[REQUEST_COMPLETED], 0, ecs->surface);
522   } else {
523     girara_debug("Rendering of page %d aborted",
524         zathura_page_get_index(request_priv->page) + 1);
525   }
526   /* mark the request as done */
527   remove_job_and_free(job);
528 
529   /* clean up the data */
530   cairo_surface_destroy(ecs->surface);
531   g_free(ecs);
532 
533   return FALSE;
534 }
535 
536 /* Returns the maximum possible saturation for given h and l.
537    Assumes that l is in the interval l1, l2 and corrects the value to
538    force u=0 on l1 and l2 */
539 static double
colorumax(const double h[3],double l,double l1,double l2)540 colorumax(const double h[3], double l, double l1, double l2)
541 {
542   if (fabs(h[0]) <= DBL_EPSILON && fabs(h[1]) <= DBL_EPSILON && fabs(h[2]) <= DBL_EPSILON) {
543     return 0;
544   }
545 
546   const double lv = (l - l1) / (l2 - l1);    /* Remap l to the whole interval [0,1] */
547   double u = DBL_MAX;
548   double v = DBL_MAX;
549   for (unsigned int k = 0; k < 3; ++k) {
550     if (h[k] > DBL_EPSILON) {
551       u = fmin(fabs((1-l)/h[k]), u);
552       v = fmin(fabs((1-lv)/h[k]), v);
553     } else if (h[k] < -DBL_EPSILON) {
554       u = fmin(fabs(l/h[k]), u);
555       v = fmin(fabs(lv/h[k]), v);
556     }
557   }
558 
559   /* rescale v according to the length of the interval [l1, l2] */
560   v = fabs(l2 - l1) * v;
561 
562   /* forces the returned value to be 0 on l1 and l2, trying not to distort colors too much */
563   return fmin(u, v);
564 }
565 
566 static void
recolor(ZathuraRendererPrivate * priv,zathura_page_t * page,unsigned int page_width,unsigned int page_height,cairo_surface_t * surface)567 recolor(ZathuraRendererPrivate* priv, zathura_page_t* page, unsigned int page_width,
568         unsigned int page_height, cairo_surface_t* surface)
569 {
570   /* uses a representation of a rgb color as follows:
571      - a lightness scalar (between 0,1), which is a weighted average of r, g, b,
572      - a hue vector, which indicates a radian direction from the grey axis,
573        inside the equal lightness plane.
574      - a saturation scalar between 0,1. It is 0 when grey, 1 when the color is
575        in the boundary of the rgb cube.
576   */
577 
578   /* TODO: split handling of image handling off
579    * Ideally we would create a mask surface for the location of the images and
580    * we would blit the recolored and unmodified surfaces together to get the
581    * same effect.
582    */
583 
584   cairo_surface_flush(surface);
585 
586   const int rowstride  = cairo_image_surface_get_stride(surface);
587   unsigned char* image = cairo_image_surface_get_data(surface);
588 
589   /* RGB weights for computing lightness. Must sum to one */
590   static const double a[] = {0.30, 0.59, 0.11};
591 
592   const GdkRGBA rgb1 = priv->recolor.dark;
593   const GdkRGBA rgb2 = priv->recolor.light;
594 
595   const double l1 = a[0]*rgb1.red + a[1]*rgb1.green + a[2]*rgb1.blue;
596   const double l2 = a[0]*rgb2.red + a[1]*rgb2.green + a[2]*rgb2.blue;
597 
598   const double rgb_diff[] = {
599     rgb2.red - rgb1.red,
600     rgb2.green - rgb1.green,
601     rgb2.blue - rgb1.blue
602   };
603 
604   girara_list_t* images     = NULL;
605   girara_list_t* rectangles = NULL;
606   bool found_images         = false;
607 
608   /* If in reverse video mode retrieve images */
609   if (priv->recolor.reverse_video == true) {
610     images = zathura_page_images_get(page, NULL);
611     found_images = (images != NULL);
612 
613     rectangles = girara_list_new();
614     if (rectangles == NULL) {
615       found_images = false;
616       girara_warning("Failed to retrieve images.");
617     }
618 
619     if (found_images == true) {
620       /* Get images bounding boxes */
621       GIRARA_LIST_FOREACH_BODY(images, zathura_image_t*, image_it,
622         zathura_rectangle_t* rect = g_try_malloc(sizeof(zathura_rectangle_t));
623         if (rect == NULL) {
624           break;
625         }
626         *rect = recalc_rectangle(page, image_it->position);
627         girara_list_append(rectangles, rect);
628       );
629     }
630   }
631 
632   for (unsigned int y = 0; y < page_height; y++) {
633     unsigned char* data = image + y * rowstride;
634 
635     for (unsigned int x = 0; x < page_width; x++, data += 4) {
636       /* Check if the pixel belongs to an image when in reverse video mode*/
637       if (priv->recolor.reverse_video == true && found_images == true){
638         bool inside_image = false;
639         GIRARA_LIST_FOREACH_BODY(rectangles, zathura_rectangle_t*, rect_it,
640           if (rect_it->x1 <= x && rect_it->x2 >= x &&
641               rect_it->y1 <= y && rect_it->y2 >= y) {
642             inside_image = true;
643             break;
644           }
645         );
646         /* If it's inside and image don't recolor */
647         if (inside_image == true) {
648           continue;
649         }
650       }
651 
652       /* Careful. data color components blue, green, red. */
653       const double rgb[3] = {
654         data[2] / 255.,
655         data[1] / 255.,
656         data[0] / 255.
657       };
658 
659       /* compute h, s, l data   */
660       double l = a[0]*rgb[0] + a[1]*rgb[1] + a[2]*rgb[2];
661 
662       if (priv->recolor.hue == true) {
663         /* adjusting lightness keeping hue of current color. white and black
664          * go to grays of same ligtness as light and dark colors. */
665         const double h[3] = {
666           rgb[0] - l,
667           rgb[1] - l,
668           rgb[2] - l
669         };
670 
671         /* u is the maximum possible saturation for given h and l. s is a
672          * rescaled saturation between 0 and 1 */
673         const double u = colorumax(h, l, 0, 1);
674         const double s = fabs(u) > DBL_EPSILON ? 1.0 / u : 0.0;
675 
676         /* Interpolates lightness between light and dark colors. white goes to
677          * light, and black goes to dark. */
678         l = l * (l2 - l1) + l1;
679 
680         const double su = s * colorumax(h, l, l1, l2);
681         data[2] = (unsigned char)round(255.*(l + su * h[0]));
682         data[1] = (unsigned char)round(255.*(l + su * h[1]));
683         data[0] = (unsigned char)round(255.*(l + su * h[2]));
684       } else {
685         /* linear interpolation between dark and light with color ligtness as
686          * a parameter */
687         data[2] = (unsigned char)round(255.*(l * rgb_diff[0] + rgb1.red));
688         data[1] = (unsigned char)round(255.*(l * rgb_diff[1] + rgb1.green));
689         data[0] = (unsigned char)round(255.*(l * rgb_diff[2] + rgb1.blue));
690       }
691     }
692   }
693 
694   if (images != NULL) {
695     girara_list_free(images);
696   }
697   if (rectangles != NULL) {
698     girara_list_free(rectangles);
699   }
700 
701   cairo_surface_mark_dirty(surface);
702 }
703 
704 static bool
invoke_completed_signal(render_job_t * job,cairo_surface_t * surface)705 invoke_completed_signal(render_job_t* job, cairo_surface_t* surface)
706 {
707   emit_completed_signal_t* ecs = g_try_malloc0(sizeof(emit_completed_signal_t));
708   if (ecs == NULL) {
709     return false;
710   }
711 
712   ecs->job     = job;
713   ecs->surface = cairo_surface_reference(surface);
714 
715   /* emit signal from the main context, i.e. the main thread */
716   g_main_context_invoke(NULL, emit_completed_signal, ecs);
717   return true;
718 }
719 
720 static bool
render_to_cairo_surface(cairo_surface_t * surface,zathura_page_t * page,ZathuraRenderer * renderer,double real_scale)721 render_to_cairo_surface(cairo_surface_t* surface, zathura_page_t* page, ZathuraRenderer* renderer, double real_scale)
722 {
723   cairo_t* cairo = cairo_create(surface);
724   if (cairo == NULL) {
725     return false;
726   }
727 
728   cairo_save(cairo);
729   cairo_set_source_rgb(cairo, 1, 1, 1);
730   cairo_paint(cairo);
731   cairo_restore(cairo);
732   cairo_save(cairo);
733 
734   /* apply scale (used by e.g. Poppler as pixels per point) */
735   if (fabs(real_scale - 1.0f) > FLT_EPSILON) {
736     cairo_scale(cairo, real_scale, real_scale);
737   }
738 
739   zathura_renderer_lock(renderer);
740   const int err = zathura_page_render(page, cairo, false);
741   zathura_renderer_unlock(renderer);
742   cairo_restore(cairo);
743   cairo_destroy(cairo);
744 
745   return err == ZATHURA_ERROR_OK;
746 }
747 
748 static bool
render(render_job_t * job,ZathuraRenderRequest * request,ZathuraRenderer * renderer)749 render(render_job_t* job, ZathuraRenderRequest* request, ZathuraRenderer* renderer)
750 {
751   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
752   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
753   zathura_page_t* page = request_priv->page;
754 
755   /* create cairo surface */
756   unsigned int page_width  = 0;
757   unsigned int page_height = 0;
758 
759   /* page size in points */
760   zathura_document_t* document = zathura_page_get_document(page);
761   const double height = zathura_page_get_height(page);
762   const double width = zathura_page_get_width(page);
763 
764   zathura_device_factors_t device_factors = { 0 };
765   double real_scale = 1;
766   if (request_priv->render_plain == false) {
767     /* page size in user pixels based on document zoom: if PPI information is
768      * correct, 100% zoom will result in 72 documents points per inch of screen
769      * (i.e. document size on screen matching the physical paper size). */
770     real_scale = page_calc_height_width(document, height, width,
771                                         &page_height, &page_width, false);
772 
773     device_factors = zathura_document_get_device_factors(document);
774     page_width *= device_factors.x;
775     page_height *= device_factors.y;
776   } else {
777     page_width = width;
778     page_height = height;
779   }
780 
781   cairo_surface_t* surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24,
782       page_width, page_height);
783   if (surface == NULL) {
784     return false;
785   }
786 
787   if (request_priv->render_plain == false) {
788     cairo_surface_set_device_scale(surface, device_factors.x, device_factors.y);
789   }
790 
791   if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
792     cairo_surface_destroy(surface);
793     return false;
794   }
795 
796   /* actually render to the surface */
797   if (!render_to_cairo_surface(surface, page, renderer, real_scale)) {
798     cairo_surface_destroy(surface);
799     return false;
800   }
801 
802   /* before recoloring, check if we've been aborted */
803   if (priv->about_to_close == true || job->aborted == true) {
804     girara_debug("Rendering of page %d aborted",
805                  zathura_page_get_index(request_priv->page) + 1);
806     remove_job_and_free(job);
807     cairo_surface_destroy(surface);
808     return true;
809   }
810 
811   /* recolor */
812   if (request_priv->render_plain == false && priv->recolor.enabled == true) {
813     recolor(priv, page, page_width, page_height, surface);
814   }
815 
816   if (!invoke_completed_signal(job, surface)) {
817     cairo_surface_destroy(surface);
818     return false;
819   }
820 
821   cairo_surface_destroy(surface);
822 
823   return true;
824 }
825 
826 static void
render_job(void * data,void * user_data)827 render_job(void* data, void* user_data)
828 {
829   render_job_t* job = data;
830   ZathuraRenderRequest* request = job->request;
831   ZathuraRenderer* renderer = user_data;
832   g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
833   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
834 
835   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
836   if (priv->about_to_close == true || job->aborted == true) {
837     /* back out early */
838     remove_job_and_free(job);
839     return;
840   }
841 
842   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
843   girara_debug("Rendering page %d ...",
844       zathura_page_get_index(request_priv->page) + 1);
845   if (render(job, request, renderer) != true) {
846     girara_error("Rendering failed (page %d)\n",
847         zathura_page_get_index(request_priv->page) + 1);
848     remove_job_and_free(job);
849   }
850 }
851 
852 
853 void
render_all(zathura_t * zathura)854 render_all(zathura_t* zathura)
855 {
856   if (zathura == NULL || zathura->document == NULL) {
857     return;
858   }
859 
860   /* unmark all pages */
861   const unsigned int number_of_pages = zathura_document_get_number_of_pages(zathura->document);
862   for (unsigned int page_id = 0; page_id < number_of_pages; ++page_id) {
863     zathura_page_t* page = zathura_document_get_page(zathura->document,
864         page_id);
865     unsigned int page_height = 0, page_width = 0;
866     const double height = zathura_page_get_height(page);
867     const double width = zathura_page_get_width(page);
868     page_calc_height_width(zathura->document, height, width, &page_height, &page_width, true);
869 
870     girara_debug("Queuing resize for page %u to %u x %u (%0.2f x %0.2f).", page_id, page_width, page_height, width, height);
871     GtkWidget* widget = zathura_page_get_widget(zathura, page);
872     if (widget != NULL) {
873       gtk_widget_set_size_request(widget, page_width, page_height);
874       gtk_widget_queue_resize(widget);
875     }
876   }
877 }
878 
879 static gint
render_thread_sort(gconstpointer a,gconstpointer b,gpointer UNUSED (data))880 render_thread_sort(gconstpointer a, gconstpointer b, gpointer UNUSED(data))
881 {
882   if (a == NULL || b == NULL) {
883     return 0;
884   }
885 
886   const render_job_t* job_a = a;
887   const render_job_t* job_b = b;
888   if (job_a->aborted == job_b->aborted) {
889     ZathuraRenderRequestPrivate* priv_a = zathura_render_request_get_instance_private(job_a->request);
890     ZathuraRenderRequestPrivate* priv_b = zathura_render_request_get_instance_private(job_b->request);
891 
892     return priv_a->last_view_time < priv_b->last_view_time ? -1 :
893         (priv_a->last_view_time > priv_b->last_view_time ? 1 : 0);
894   }
895 
896   /* sort aborted entries earlier so that they are thrown out of the queue */
897   return job_a->aborted ? 1 : -1;
898 }
899 
900 /* cache functions */
901 
902 static bool
page_cache_is_cached(ZathuraRenderer * renderer,unsigned int page_index)903 page_cache_is_cached(ZathuraRenderer* renderer, unsigned int page_index)
904 {
905   g_return_val_if_fail(renderer != NULL, false);
906   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
907 
908   if (priv->page_cache.num_cached_pages != 0) {
909     for (size_t i = 0; i < priv->page_cache.size; ++i) {
910       if (priv->page_cache.cache[i] >= 0 &&
911           page_index == (unsigned int)priv->page_cache.cache[i]) {
912         girara_debug("Page %d is a cache hit", page_index + 1);
913         return true;
914       }
915     }
916   }
917 
918   girara_debug("Page %d is a cache miss", page_index + 1);
919   return false;
920 }
921 
922 static int
find_request_by_page_index(const void * req,const void * data)923 find_request_by_page_index(const void* req, const void* data)
924 {
925   ZathuraRenderRequest* request = (void*) req;
926   const unsigned int page_index = *((const int*)data);
927 
928   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
929   if (zathura_page_get_index(priv->page) == page_index) {
930     return 0;
931   }
932   return 1;
933 }
934 
935 static ssize_t
page_cache_lru_invalidate(ZathuraRenderer * renderer)936 page_cache_lru_invalidate(ZathuraRenderer* renderer)
937 {
938   g_return_val_if_fail(renderer != NULL, -1);
939   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
940   g_return_val_if_fail(priv->page_cache.size != 0, -1);
941 
942   ssize_t lru_index = 0;
943   gint64 lru_view_time = G_MAXINT64;
944   ZathuraRenderRequest* request = NULL;
945   for (size_t i = 0; i < priv->page_cache.size; ++i) {
946     ZathuraRenderRequest* tmp_request = girara_list_find(priv->requests,
947         find_request_by_page_index, &priv->page_cache.cache[i]);
948     g_return_val_if_fail(tmp_request != NULL, -1);
949     ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(tmp_request);
950 
951     if (request_priv->last_view_time < lru_view_time) {
952       lru_view_time = request_priv->last_view_time;
953       lru_index = i;
954       request = tmp_request;
955     }
956   }
957 
958   ZathuraRenderRequestPrivate* request_priv = zathura_render_request_get_instance_private(request);
959 
960   /* emit the signal */
961   g_signal_emit(request, request_signals[REQUEST_CACHE_INVALIDATED], 0);
962   girara_debug("Invalidated page %d at cache index %zd",
963       zathura_page_get_index(request_priv->page) + 1, lru_index);
964   priv->page_cache.cache[lru_index] = -1;
965   --priv->page_cache.num_cached_pages;
966 
967   return lru_index;
968 }
969 
970 static bool
page_cache_is_full(ZathuraRenderer * renderer,bool * result)971 page_cache_is_full(ZathuraRenderer* renderer, bool* result)
972 {
973   g_return_val_if_fail(ZATHURA_IS_RENDERER(renderer) && result != NULL, false);
974 
975   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
976   *result = priv->page_cache.num_cached_pages == priv->page_cache.size;
977 
978   return true;
979 }
980 
981 static void
page_cache_invalidate_all(ZathuraRenderer * renderer)982 page_cache_invalidate_all(ZathuraRenderer* renderer)
983 {
984   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
985 
986   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
987   for (size_t i = 0; i < priv->page_cache.size; ++i) {
988     priv->page_cache.cache[i] = -1;
989   }
990   priv->page_cache.num_cached_pages = 0;
991 }
992 
993 void
zathura_renderer_page_cache_add(ZathuraRenderer * renderer,unsigned int page_index)994 zathura_renderer_page_cache_add(ZathuraRenderer* renderer,
995     unsigned int page_index)
996 {
997   g_return_if_fail(ZATHURA_IS_RENDERER(renderer));
998   if (page_cache_is_cached(renderer, page_index) == true) {
999     return;
1000   }
1001 
1002   ZathuraRendererPrivate* priv = zathura_renderer_get_instance_private(renderer);
1003   bool full = false;
1004   if (page_cache_is_full(renderer, &full) == false) {
1005     return;
1006   } else if (full == true) {
1007     const ssize_t idx = page_cache_lru_invalidate(renderer);
1008     if (idx == -1) {
1009       return;
1010     }
1011 
1012     priv->page_cache.cache[idx] = page_index;
1013     ++priv->page_cache.num_cached_pages;
1014     girara_debug("Page %d is cached at cache index %zd", page_index + 1, idx);
1015   } else {
1016     priv->page_cache.cache[priv->page_cache.num_cached_pages++] = page_index;
1017     girara_debug("Page %d is cached at cache index %zu", page_index + 1,
1018         priv->page_cache.num_cached_pages - 1);
1019   }
1020 
1021   ZathuraRenderRequest* request = girara_list_find(priv->requests,
1022         find_request_by_page_index, &page_index);
1023   g_return_if_fail(request != NULL);
1024   g_signal_emit(request, request_signals[REQUEST_CACHE_ADDED], 0);
1025 }
1026 
zathura_render_request_set_render_plain(ZathuraRenderRequest * request,bool render_plain)1027 void zathura_render_request_set_render_plain(ZathuraRenderRequest* request,
1028     bool render_plain)
1029 {
1030   g_return_if_fail(ZATHURA_IS_RENDER_REQUEST(request));
1031 
1032   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
1033   priv->render_plain =render_plain;
1034 }
1035 
1036 bool
zathura_render_request_get_render_plain(ZathuraRenderRequest * request)1037 zathura_render_request_get_render_plain(ZathuraRenderRequest* request)
1038 {
1039   g_return_val_if_fail(ZATHURA_IS_RENDER_REQUEST(request), false);
1040 
1041   ZathuraRenderRequestPrivate* priv = zathura_render_request_get_instance_private(request);
1042   return priv->render_plain;
1043 }
1044 
1045