document.c 14.7 KB
Newer Older
Moritz Lipp's avatar
Moritz Lipp committed
1
2
/* See LICENSE file for license and copyright information */

Moritz Lipp's avatar
Moritz Lipp committed
3
#define _BSD_SOURCE
4
#define _XOPEN_SOURCE 700
Moritz Lipp's avatar
Moritz Lipp committed
5

Moritz Lipp's avatar
Moritz Lipp committed
6
#include <sys/wait.h>
Moritz Lipp's avatar
Moritz Lipp committed
7
#include <stdlib.h>
Moritz Lipp's avatar
Moritz Lipp committed
8
#include <stdio.h>
Moritz Lipp's avatar
Moritz Lipp committed
9
#include <string.h>
Moritz Lipp's avatar
Moritz Lipp committed
10
#include <limits.h>
11
#include <errno.h>
12
#include <glib.h>
13
#include <glib/gi18n.h>
14
15
16
#ifdef WITH_MAGIC
#include <magic.h>
#endif
Sebastian Ramacher's avatar
Sebastian Ramacher committed
17
#include <unistd.h>
Moritz Lipp's avatar
Moritz Lipp committed
18

19
20
21
22
23
24
#include <girara/datastructures.h>
#include <girara/utils.h>
#include <girara/statusbar.h>
#include <girara/session.h>
#include <girara/settings.h>

Moritz Lipp's avatar
Moritz Lipp committed
25
#include "document.h"
26
27
#include "utils.h"
#include "zathura.h"
Moritz Lipp's avatar
Update    
Moritz Lipp committed
28
#include "render.h"
Pavel Borzenkov's avatar
Pavel Borzenkov committed
29
#include "database.h"
Moritz Lipp's avatar
Moritz Lipp committed
30
#include "page.h"
31
#include "page-widget.h"
32
#include "plugin.h"
33

34
35
36
/** Read a most GT_MAX_READ bytes before falling back to file. */
static const size_t GT_MAX_READ = 1 << 16;

37
static const gchar* guess_type(const char* path);
38

39
40
41
/**
 * Document
 */
Moritz Lipp's avatar
Moritz Lipp committed
42
struct zathura_document_s {
43
44
45
46
47
48
49
50
  char* file_path; /**< File path of the document */
  const char* password; /**< Password of the document */
  unsigned int current_page_number; /**< Current page number */
  unsigned int number_of_pages; /**< Number of pages */
  double scale; /**< Scale value */
  unsigned int rotate; /**< Rotation */
  void* data; /**< Custom data */
  zathura_adjust_mode_t adjust_mode; /**< Adjust mode (best-fit, width) */
Moritz Lipp's avatar
Moritz Lipp committed
51
  int page_offset; /**< Page offset */
52
53
54
55
56
57

  /**
   * Document pages
   */
  zathura_page_t** pages;

58
59
60
61
  /**
   * Used plugin
   */
  zathura_plugin_t* plugin;
62
63
64
};


65
zathura_document_t*
66
zathura_document_open(zathura_plugin_manager_t* plugin_manager, const char*
Moritz Lipp's avatar
Moritz Lipp committed
67
                      path, const char* password, zathura_error_t* error)
68
69
70
71
72
{
  if (path == NULL) {
    return NULL;
  }

73
  if (g_file_test(path, G_FILE_TEST_EXISTS) == FALSE) {
74
75
76
77
78
79
80
81
    girara_error("File '%s' does not exist", path);
    return NULL;
  }

  const gchar* content_type = guess_type(path);
  if (content_type == NULL) {
    girara_error("Could not determine file type.");
    return NULL;
82
83
  }

Moritz Lipp's avatar
Moritz Lipp committed
84
  /* determine real path */
85
  long path_max;
Moritz Lipp's avatar
Moritz Lipp committed
86
87
88
89
#ifdef PATH_MAX
  path_max = PATH_MAX;
#else
  path_max = pathconf(path,_PC_PATH_MAX);
90
  if (path_max <= 0)
Moritz Lipp's avatar
Moritz Lipp committed
91
92
93
    path_max = 4096;
#endif

Moritz Lipp's avatar
Moritz Lipp committed
94
95
96
97
  char* real_path              = NULL;
  zathura_document_t* document = NULL;

  real_path = malloc(sizeof(char) * path_max);
Moritz Lipp's avatar
Moritz Lipp committed
98
  if (real_path == NULL) {
99
100
    g_free((void*)content_type);
    return NULL;
Moritz Lipp's avatar
Moritz Lipp committed
101
102
  }

Moritz Lipp's avatar
Moritz Lipp committed
103
  if (realpath(path, real_path) == NULL) {
104
105
106
107
108
    g_free((void*)content_type);
    free(real_path);
    return NULL;
  }

109
  zathura_plugin_t* plugin = zathura_plugin_manager_get_plugin(plugin_manager, content_type);
110
111
112
113
  g_free((void*)content_type);

  if (plugin == NULL) {
    girara_error("unknown file type\n");
Moritz Lipp's avatar
Fix    
Moritz Lipp committed
114
    *error = ZATHURA_ERROR_UNKNOWN;
115
    goto error_free;
Moritz Lipp's avatar
Moritz Lipp committed
116
117
  }

Moritz Lipp's avatar
Moritz Lipp committed
118
  document = g_malloc0(sizeof(zathura_document_t));
Moritz Lipp's avatar
Moritz Lipp committed
119

Moritz Lipp's avatar
Moritz Lipp committed
120
121
122
123
124
  document->file_path   = real_path;
  document->password    = password;
  document->scale       = 1.0;
  document->plugin      = plugin;
  document->adjust_mode = ZATHURA_ADJUST_NONE;
Moritz Lipp's avatar
Moritz Lipp committed
125

126
  /* open document */
127
128
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(plugin);
  if (functions->document_open == NULL) {
129
130
131
132
    girara_error("plugin has no open function\n");
    goto error_free;
  }

133
  zathura_error_t int_error = functions->document_open(document);
134
135
136
  if (int_error != ZATHURA_ERROR_OK) {
    if (error != NULL) {
      *error = int_error;
137
138
139
140
141
142
    }

    girara_error("could not open document\n");
    goto error_free;
  }

Moritz Lipp's avatar
Moritz Lipp committed
143
144
145
146
147
  /* read all pages */
  document->pages = calloc(document->number_of_pages, sizeof(zathura_page_t*));
  if (document->pages == NULL) {
    goto error_free;
  }
148

Moritz Lipp's avatar
Moritz Lipp committed
149
  for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) {
150
    zathura_page_t* page = zathura_page_new(document, page_id, NULL);
Moritz Lipp's avatar
Moritz Lipp committed
151
152
    if (page == NULL) {
      goto error_free;
Moritz Lipp's avatar
Moritz Lipp committed
153
    }
Moritz Lipp's avatar
Moritz Lipp committed
154
155
156
157
158

    document->pages[page_id] = page;
  }

  return document;
Moritz Lipp's avatar
Moritz Lipp committed
159

Moritz Lipp's avatar
Moritz Lipp committed
160
161
error_free:

Moritz Lipp's avatar
Moritz Lipp committed
162
  free(real_path);
Moritz Lipp's avatar
Moritz Lipp committed
163

Moritz Lipp's avatar
Moritz Lipp committed
164
165
  if (document != NULL && document->pages != NULL) {
    for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) {
Moritz Lipp's avatar
Moritz Lipp committed
166
167
168
169
170
171
      zathura_page_free(document->pages[page_id]);
    }

    free(document->pages);
  }

Moritz Lipp's avatar
Moritz Lipp committed
172
  g_free(document);
Moritz Lipp's avatar
Moritz Lipp committed
173
174
175
  return NULL;
}

176
zathura_error_t
Moritz Lipp's avatar
Moritz Lipp committed
177
zathura_document_free(zathura_document_t* document)
Moritz Lipp's avatar
Moritz Lipp committed
178
{
179
  if (document == NULL || document->plugin == NULL) {
180
    return ZATHURA_ERROR_INVALID_ARGUMENTS;
Moritz Lipp's avatar
Moritz Lipp committed
181
182
  }

Moritz Lipp's avatar
Moritz Lipp committed
183
  /* free pages */
Moritz Lipp's avatar
Moritz Lipp committed
184
  for (unsigned int page_id = 0; page_id < document->number_of_pages; page_id++) {
Moritz Lipp's avatar
Moritz Lipp committed
185
    zathura_page_free(document->pages[page_id]);
Pavel Borzenkov's avatar
Pavel Borzenkov committed
186
    document->pages[page_id] = NULL;
Moritz Lipp's avatar
Moritz Lipp committed
187
188
189
190
191
  }

  free(document->pages);

  /* free document */
192
  zathura_error_t error = ZATHURA_ERROR_OK;
193
194
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_free == NULL) {
195
    error = ZATHURA_ERROR_NOT_IMPLEMENTED;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
196
  } else {
197
    error = functions->document_free(document, document->data);
Moritz Lipp's avatar
Moritz Lipp committed
198
199
  }

Moritz Lipp's avatar
Moritz Lipp committed
200
  if (document->file_path != NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
201
202
203
    free(document->file_path);
  }

Moritz Lipp's avatar
Moritz Lipp committed
204
  g_free(document);
Moritz Lipp's avatar
Moritz Lipp committed
205

Moritz Lipp's avatar
Moritz Lipp committed
206
  return error;
Moritz Lipp's avatar
Moritz Lipp committed
207
208
}

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
const char*
zathura_document_get_path(zathura_document_t* document)
{
  if (document == NULL) {
    return NULL;
  }

  return document->file_path;
}

const char*
zathura_document_get_password(zathura_document_t* document)
{
  if (document == NULL) {
    return NULL;
  }

  return document->password;
}

229
230
231
232
233
234
235
236
237
238
zathura_page_t*
zathura_document_get_page(zathura_document_t* document, unsigned int index)
{
  if (document == NULL || document->pages == NULL || (document->number_of_pages <= index)) {
    return NULL;
  }

  return document->pages[index];
}

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
void*
zathura_document_get_data(zathura_document_t* document)
{
  if (document == NULL) {
    return NULL;
  }

  return document->data;
}

void
zathura_document_set_data(zathura_document_t* document, void* data)
{
  if (document == NULL) {
    return;
  }

  document->data = data;
}

unsigned int
zathura_document_get_number_of_pages(zathura_document_t* document)
{
  if (document == NULL) {
    return 0;
  }

  return document->number_of_pages;
}

void
zathura_document_set_number_of_pages(zathura_document_t* document, unsigned int number_of_pages)
{
  if (document == NULL) {
    return;
  }

  document->number_of_pages = number_of_pages;
}

unsigned int
280
zathura_document_get_current_page_number(zathura_document_t* document)
281
282
283
284
285
286
287
288
289
{
  if (document == NULL) {
    return 0;
  }

  return document->current_page_number;
}

void
290
zathura_document_set_current_page_number(zathura_document_t* document, unsigned int
291
292
293
294
295
296
297
298
299
    current_page)
{
  if (document == NULL) {
    return;
  }

  document->current_page_number = current_page;
}

300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
double
zathura_document_get_scale(zathura_document_t* document)
{
  if (document == NULL) {
    return 0;
  }

  return document->scale;
}

void
zathura_document_set_scale(zathura_document_t* document, double scale)
{
  if (document == NULL) {
    return;
  }

  document->scale = scale;
}

unsigned int
zathura_document_get_rotation(zathura_document_t* document)
{
  if (document == NULL) {
    return 0;
  }

  return document->rotate;
}

void
zathura_document_set_rotation(zathura_document_t* document, unsigned int rotation)
{
  if (document == NULL) {
    return;
  }

  document->rotate = rotation % 360;

Moritz Lipp's avatar
Moritz Lipp committed
339
  if (document->rotate > 0 && document->rotate <= 90) {
340
    document->rotate = 90;
Moritz Lipp's avatar
Moritz Lipp committed
341
  } else if (document->rotate > 0 && document->rotate <= 180) {
342
    document->rotate = 180;
Moritz Lipp's avatar
Moritz Lipp committed
343
  } else if (document->rotate > 0 && document->rotate <= 270) {
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
    document->rotate = 270;
  } else {
    document->rotate = 0;
  }
}

zathura_adjust_mode_t
zathura_document_get_adjust_mode(zathura_document_t* document)
{
  if (document == NULL) {
    return ZATHURA_ADJUST_NONE;
  }

  return document->adjust_mode;
}

void
zathura_document_set_adjust_mode(zathura_document_t* document, zathura_adjust_mode_t mode)
{
  if (document == NULL) {
    return;
  }

367
  document->adjust_mode = mode;
368
369
}

Moritz Lipp's avatar
Moritz Lipp committed
370
int
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
zathura_document_get_page_offset(zathura_document_t* document)
{
  if (document == NULL) {
    return 0;
  }

  return document->page_offset;
}

void
zathura_document_set_page_offset(zathura_document_t* document, unsigned int page_offset)
{
  if (document == NULL) {
    return;
  }

Moritz Lipp's avatar
Moritz Lipp committed
387
  document->page_offset = page_offset;
388
389
}

390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
void
zathura_document_get_cell_size(zathura_document_t* document,
                               unsigned int* height, unsigned int* width)
{
  g_return_if_fail(document != NULL && height != NULL && width != NULL);

  unsigned int number_of_pages =
    zathura_document_get_number_of_pages(document);
  *width = 0;
  *height = 0;

  /* Get the size of each cell of the table/grid, assuming it is homogeneous
   * (i.e. each cell has the same dimensions. */
  for (unsigned int page_id = 0; page_id < number_of_pages; page_id++) {
    zathura_page_t* page = zathura_document_get_page(document, page_id);
    if (page == NULL)
      continue;

    unsigned int page_width = 0, page_height = 0;
    page_calc_height_width(page, &page_height, &page_width, true);

    if (*width < page_width)
      *width = page_width;
    if (*height < page_height)
      *height = page_height;
  }
}

418
zathura_error_t
Moritz Lipp's avatar
Moritz Lipp committed
419
420
zathura_document_save_as(zathura_document_t* document, const char* path)
{
421
  if (document == NULL || document->plugin == NULL || path == NULL) {
422
    return ZATHURA_ERROR_UNKNOWN;
Moritz Lipp's avatar
Moritz Lipp committed
423
424
  }

425
426
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_save_as == NULL) {
427
    return ZATHURA_ERROR_NOT_IMPLEMENTED;
Moritz Lipp's avatar
Moritz Lipp committed
428
429
  }

430
  return functions->document_save_as(document, document->data, path);
Moritz Lipp's avatar
Moritz Lipp committed
431
432
}

433
girara_tree_node_t*
434
zathura_document_index_generate(zathura_document_t* document, zathura_error_t* error)
Moritz Lipp's avatar
Moritz Lipp committed
435
{
436
  if (document == NULL || document->plugin == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
437
    if (error != NULL) {
438
      *error = ZATHURA_ERROR_INVALID_ARGUMENTS;
Moritz Lipp's avatar
Moritz Lipp committed
439
    }
Moritz Lipp's avatar
Moritz Lipp committed
440
441
442
    return NULL;
  }

443
444
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_index_generate == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
445
    if (error != NULL) {
446
      *error = ZATHURA_ERROR_NOT_IMPLEMENTED;
Moritz Lipp's avatar
Moritz Lipp committed
447
    }
Moritz Lipp's avatar
Moritz Lipp committed
448
449
450
    return NULL;
  }

451
  return functions->document_index_generate(document, document->data, error);
Moritz Lipp's avatar
Moritz Lipp committed
452
453
}

Sebastian Ramacher's avatar
Sebastian Ramacher committed
454
girara_list_t*
455
zathura_document_attachments_get(zathura_document_t* document, zathura_error_t* error)
Moritz Lipp's avatar
Moritz Lipp committed
456
{
457
  if (document == NULL || document->plugin == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
458
    if (error != NULL) {
459
      *error = ZATHURA_ERROR_INVALID_ARGUMENTS;
Moritz Lipp's avatar
Moritz Lipp committed
460
    }
Moritz Lipp's avatar
Moritz Lipp committed
461
462
463
    return NULL;
  }

464
465
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_attachments_get == NULL) {
Moritz Lipp's avatar
Moritz Lipp committed
466
    if (error != NULL) {
467
      *error = ZATHURA_ERROR_NOT_IMPLEMENTED;
Moritz Lipp's avatar
Moritz Lipp committed
468
    }
Moritz Lipp's avatar
Moritz Lipp committed
469
470
471
    return NULL;
  }

472
  return functions->document_attachments_get(document, document->data, error);
Moritz Lipp's avatar
Moritz Lipp committed
473
474
}

475
zathura_error_t
476
zathura_document_attachment_save(zathura_document_t* document, const char* attachment, const char* file)
Moritz Lipp's avatar
Moritz Lipp committed
477
{
478
  if (document == NULL || document->plugin == NULL) {
479
    return ZATHURA_ERROR_INVALID_ARGUMENTS;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
480
481
  }

482
483
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_attachment_save == NULL) {
484
    return ZATHURA_ERROR_NOT_IMPLEMENTED;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
485
486
  }

487
  return functions->document_attachment_save(document, document->data, attachment, file);
Moritz Lipp's avatar
Moritz Lipp committed
488
489
}

490
491
girara_list_t*
zathura_document_get_information(zathura_document_t* document, zathura_error_t* error)
Moritz Lipp's avatar
Moritz Lipp committed
492
{
493
  if (document == NULL || document->plugin == NULL) {
494
    if (error != NULL) {
495
      *error = ZATHURA_ERROR_INVALID_ARGUMENTS;
496
    }
Moritz Lipp's avatar
Moritz Lipp committed
497
498
499
    return NULL;
  }

500
501
  zathura_plugin_functions_t* functions = zathura_plugin_get_functions(document->plugin);
  if (functions->document_get_information == NULL) {
502
    if (error != NULL) {
503
      *error = ZATHURA_ERROR_NOT_IMPLEMENTED;
504
    }
Moritz Lipp's avatar
Moritz Lipp committed
505
506
507
    return NULL;
  }

508
  girara_list_t* result = functions->document_get_information(document, document->data, error);
509
510
511
512
513
  if (result != NULL) {
    girara_list_set_free_function(result, (girara_free_function_t) zathura_document_information_entry_free);
  }

  return result;
Moritz Lipp's avatar
Moritz Lipp committed
514
515
}

516
517
static const gchar*
guess_type(const char* path)
518
{
519
  const gchar* content_type = NULL;
520
#ifdef WITH_MAGIC
521
  const char* mime_type = NULL;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
522
523

  /* creat magic cookie */
524
525
526
527
528
529
530
531
532
  const int flags =
    MAGIC_MIME_TYPE |
    MAGIC_SYMLINK |
    MAGIC_NO_CHECK_APPTYPE |
    MAGIC_NO_CHECK_CDF |
    MAGIC_NO_CHECK_ELF |
    MAGIC_NO_CHECK_ENCODING;
  magic_t magic = magic_open(flags);
  if (magic == NULL) {
533
    girara_debug("failed creating the magic cookie");
534
    goto cleanup;
535
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
536
537

  /* ... and load mime database */
538
  if (magic_load(magic, NULL) < 0) {
539
    girara_debug("failed loading the magic database: %s", magic_error(magic));
540
541
    goto cleanup;
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
542
543

  /* get the mime type */
544
  mime_type = magic_file(magic, path);
545
  if (mime_type == NULL) {
546
    girara_debug("failed guessing filetype: %s", magic_error(magic));
547
    goto cleanup;
548
  }
Sebastian Ramacher's avatar
Sebastian Ramacher committed
549

550
  girara_debug("magic detected filetype: %s", mime_type);
551
552
  content_type = g_strdup(mime_type);

553
cleanup:
554
  if (magic != NULL) {
555
556
    magic_close(magic);
  }
557
558
559

  if (content_type != NULL) {
    return content_type;
560
561
  }
  /* else fallback to g_content_type_guess method */
562
#endif /*WITH_MAGIC*/
563
  gboolean uncertain = FALSE;
564
  content_type = g_content_type_guess(path, NULL, 0, &uncertain);
565
  if (content_type == NULL) {
Sebastian Ramacher's avatar
Sebastian Ramacher committed
566
567
568
    girara_debug("g_content_type failed\n");
  } else {
    if (uncertain == FALSE) {
569
      girara_debug("g_content_type detected filetype: %s", content_type);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
570
571
      return content_type;
    }
572
    girara_debug("g_content_type is uncertain, guess: %s", content_type);
Moritz Lipp's avatar
Moritz Lipp committed
573
574
  }

575
576
  FILE* f = fopen(path, "rb");
  if (f == NULL) {
577
578
579
    return NULL;
  }

580
581
582
  const int fd = fileno(f);
  guchar* content = NULL;
  size_t length = 0u;
Sebastian Ramacher's avatar
Sebastian Ramacher committed
583
584
  ssize_t bytes_read = -1;
  while (uncertain == TRUE && length < GT_MAX_READ && bytes_read != 0) {
585
586
    g_free((void*)content_type);
    content_type = NULL;
587

588
    content = g_realloc(content, length + BUFSIZ);
Sebastian Ramacher's avatar
Sebastian Ramacher committed
589
590
    bytes_read = read(fd, content + length, BUFSIZ);
    if (bytes_read == -1) {
591
592
      break;
    }
593

Sebastian Ramacher's avatar
Sebastian Ramacher committed
594
    length += bytes_read;
595
    content_type = g_content_type_guess(NULL, content, length, &uncertain);
596
    girara_debug("new guess: %s uncertain: %d, read: %zu", content_type, uncertain, length);
597
  }
598

599
600
601
602
  fclose(f);
  g_free(content);
  if (uncertain == FALSE) {
    return content_type;
603
604
  }

605
606
  g_free((void*)content_type);
  content_type = NULL;
607

608
  girara_debug("falling back to file");
609

610
611
  GString* command = g_string_new("file -b --mime-type ");
  char* tmp        = g_shell_quote(path);
612

613
614
  g_string_append(command, tmp);
  g_free(tmp);
615

616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
  GError* error = NULL;
  char* out = NULL;
  int ret = 0;
  g_spawn_command_line_sync(command->str, &out, NULL, &ret, &error);
  g_string_free(command, TRUE);
  if (error != NULL) {
    girara_warning("failed to execute command: %s", error->message);
    g_error_free(error);
    g_free(out);
    return NULL;
  }
  if (WEXITSTATUS(ret) != 0) {
    girara_warning("file failed with error code: %d", WEXITSTATUS(ret));
    g_free(out);
    return NULL;
631
632
  }

633
634
  g_strdelimit(out, "\n\r", '\0');
  return out;
635
}
636
637
638
639
640
641
642
643
644
645

zathura_plugin_t*
zathura_document_get_plugin(zathura_document_t* document)
{
  if (document == NULL) {
    return NULL;
  }

  return document->plugin;
}