1/* poppler-action.cc: glib wrapper for poppler -*- c-basic-offset: 8 -*-
2 * Copyright (C) 2005, Red Hat, Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#include "poppler.h"
20#include "poppler-private.h"
21
22/**
23 * SECTION:poppler-action
24 * @short_description: Action links
25 * @title: PopplerAction
26 */
27
28G_DEFINE_BOXED_TYPE(PopplerDest, poppler_dest, poppler_dest_copy, poppler_dest_free)
29
30/**
31 * poppler_dest_copy:
32 * @dest: a #PopplerDest
33 *
34 * Copies @dest, creating an identical #PopplerDest.
35 *
36 * Return value: a new destination identical to @dest
37 **/
38PopplerDest *poppler_dest_copy(PopplerDest *dest)
39{
40 PopplerDest *new_dest;
41
42 new_dest = g_slice_dup(PopplerDest, dest);
43
44 if (dest->named_dest) {
45 new_dest->named_dest = g_strdup(str: dest->named_dest);
46 }
47
48 return new_dest;
49}
50
51/**
52 * poppler_dest_free:
53 * @dest: a #PopplerDest
54 *
55 * Frees @dest
56 **/
57void poppler_dest_free(PopplerDest *dest)
58{
59 if (!dest) {
60 return;
61 }
62
63 if (dest->named_dest) {
64 g_free(mem: dest->named_dest);
65 }
66
67 g_slice_free(PopplerDest, dest);
68}
69
70static void poppler_action_layer_free(PopplerActionLayer *action_layer)
71{
72 if (!action_layer) {
73 return;
74 }
75
76 if (action_layer->layers) {
77 g_list_free_full(list: action_layer->layers, free_func: g_object_unref);
78 action_layer->layers = nullptr;
79 }
80
81 g_slice_free(PopplerActionLayer, action_layer);
82}
83
84static PopplerActionLayer *poppler_action_layer_copy(PopplerActionLayer *action_layer)
85{
86 PopplerActionLayer *retval = g_slice_dup(PopplerActionLayer, action_layer);
87
88 retval->layers = g_list_copy(list: action_layer->layers);
89 for (GList *l = retval->layers; l != nullptr; l = l->next) {
90 g_object_ref(l->data);
91 }
92
93 return retval;
94}
95
96G_DEFINE_BOXED_TYPE(PopplerAction, poppler_action, poppler_action_copy, poppler_action_free)
97
98/**
99 * poppler_action_free:
100 * @action: a #PopplerAction
101 *
102 * Frees @action
103 **/
104void poppler_action_free(PopplerAction *action)
105{
106 if (action == nullptr) {
107 return;
108 }
109
110 /* Action specific stuff */
111 switch (action->type) {
112 case POPPLER_ACTION_GOTO_DEST:
113 poppler_dest_free(dest: action->goto_dest.dest);
114 break;
115 case POPPLER_ACTION_GOTO_REMOTE:
116 poppler_dest_free(dest: action->goto_remote.dest);
117 g_free(mem: action->goto_remote.file_name);
118 break;
119 case POPPLER_ACTION_URI:
120 g_free(mem: action->uri.uri);
121 break;
122 case POPPLER_ACTION_LAUNCH:
123 g_free(mem: action->launch.file_name);
124 g_free(mem: action->launch.params);
125 break;
126 case POPPLER_ACTION_NAMED:
127 g_free(mem: action->named.named_dest);
128 break;
129 case POPPLER_ACTION_MOVIE:
130 if (action->movie.movie) {
131 g_object_unref(object: action->movie.movie);
132 }
133 break;
134 case POPPLER_ACTION_RENDITION:
135 if (action->rendition.media) {
136 g_object_unref(object: action->rendition.media);
137 }
138 break;
139 case POPPLER_ACTION_OCG_STATE:
140 if (action->ocg_state.state_list) {
141 g_list_free_full(list: action->ocg_state.state_list, free_func: (GDestroyNotify)poppler_action_layer_free);
142 }
143 break;
144 case POPPLER_ACTION_JAVASCRIPT:
145 if (action->javascript.script) {
146 g_free(mem: action->javascript.script);
147 }
148 break;
149 case POPPLER_ACTION_RESET_FORM:
150 if (action->reset_form.fields) {
151 g_list_free_full(list: action->reset_form.fields, free_func: g_free);
152 }
153 break;
154 default:
155 break;
156 }
157
158 g_free(mem: action->any.title);
159 g_slice_free(PopplerAction, action);
160}
161
162/**
163 * poppler_action_copy:
164 * @action: a #PopplerAction
165 *
166 * Copies @action, creating an identical #PopplerAction.
167 *
168 * Return value: a new action identical to @action
169 **/
170PopplerAction *poppler_action_copy(PopplerAction *action)
171{
172 PopplerAction *new_action;
173
174 g_return_val_if_fail(action != nullptr, NULL);
175
176 /* Do a straight copy of the memory */
177 new_action = g_slice_dup(PopplerAction, action);
178
179 if (action->any.title != nullptr) {
180 new_action->any.title = g_strdup(str: action->any.title);
181 }
182
183 switch (action->type) {
184 case POPPLER_ACTION_GOTO_DEST:
185 new_action->goto_dest.dest = poppler_dest_copy(dest: action->goto_dest.dest);
186 break;
187 case POPPLER_ACTION_GOTO_REMOTE:
188 new_action->goto_remote.dest = poppler_dest_copy(dest: action->goto_remote.dest);
189 if (action->goto_remote.file_name) {
190 new_action->goto_remote.file_name = g_strdup(str: action->goto_remote.file_name);
191 }
192 break;
193 case POPPLER_ACTION_URI:
194 if (action->uri.uri) {
195 new_action->uri.uri = g_strdup(str: action->uri.uri);
196 }
197 break;
198 case POPPLER_ACTION_LAUNCH:
199 if (action->launch.file_name) {
200 new_action->launch.file_name = g_strdup(str: action->launch.file_name);
201 }
202 if (action->launch.params) {
203 new_action->launch.params = g_strdup(str: action->launch.params);
204 }
205 break;
206 case POPPLER_ACTION_NAMED:
207 if (action->named.named_dest) {
208 new_action->named.named_dest = g_strdup(str: action->named.named_dest);
209 }
210 break;
211 case POPPLER_ACTION_MOVIE:
212 if (action->movie.movie) {
213 new_action->movie.movie = (PopplerMovie *)g_object_ref(action->movie.movie);
214 }
215 break;
216 case POPPLER_ACTION_RENDITION:
217 if (action->rendition.media) {
218 new_action->rendition.media = (PopplerMedia *)g_object_ref(action->rendition.media);
219 }
220 break;
221 case POPPLER_ACTION_OCG_STATE:
222 if (action->ocg_state.state_list) {
223 GList *l;
224 GList *new_list = nullptr;
225
226 for (l = action->ocg_state.state_list; l; l = g_list_next(l)) {
227 PopplerActionLayer *alayer = (PopplerActionLayer *)l->data;
228 new_list = g_list_prepend(list: new_list, data: poppler_action_layer_copy(action_layer: alayer));
229 }
230
231 new_action->ocg_state.state_list = g_list_reverse(list: new_list);
232 }
233
234 break;
235 case POPPLER_ACTION_JAVASCRIPT:
236 if (action->javascript.script) {
237 new_action->javascript.script = g_strdup(str: action->javascript.script);
238 }
239 break;
240 case POPPLER_ACTION_RESET_FORM:
241 if (action->reset_form.fields) {
242 GList *iter;
243
244 new_action->reset_form.fields = nullptr;
245 for (iter = action->reset_form.fields; iter != nullptr; iter = iter->next) {
246 new_action->reset_form.fields = g_list_append(list: new_action->reset_form.fields, data: g_strdup(str: (char *)iter->data));
247 }
248 }
249 break;
250 default:
251 break;
252 }
253
254 return new_action;
255}
256
257static PopplerDest *dest_new_goto(PopplerDocument *document, const LinkDest *link_dest)
258{
259 PopplerDest *dest;
260
261 dest = g_slice_new0(PopplerDest);
262
263 if (link_dest == nullptr) {
264 dest->type = POPPLER_DEST_UNKNOWN;
265 return dest;
266 }
267
268 switch (link_dest->getKind()) {
269 case destXYZ:
270 dest->type = POPPLER_DEST_XYZ;
271 break;
272 case destFit:
273 dest->type = POPPLER_DEST_FIT;
274 break;
275 case destFitH:
276 dest->type = POPPLER_DEST_FITH;
277 break;
278 case destFitV:
279 dest->type = POPPLER_DEST_FITV;
280 break;
281 case destFitR:
282 dest->type = POPPLER_DEST_FITR;
283 break;
284 case destFitB:
285 dest->type = POPPLER_DEST_FITB;
286 break;
287 case destFitBH:
288 dest->type = POPPLER_DEST_FITBH;
289 break;
290 case destFitBV:
291 dest->type = POPPLER_DEST_FITBV;
292 break;
293 default:
294 dest->type = POPPLER_DEST_UNKNOWN;
295 }
296
297 if (link_dest->isPageRef()) {
298 if (document) {
299 const Ref page_ref = link_dest->getPageRef();
300 dest->page_num = document->doc->findPage(ref: page_ref);
301 } else {
302 /* FIXME: We don't keep areound the page_ref for the
303 * remote doc, so we can't look this up. Guess that
304 * it's 0*/
305 dest->page_num = 0;
306 }
307 } else {
308 dest->page_num = link_dest->getPageNum();
309 }
310
311 dest->left = link_dest->getLeft();
312 dest->bottom = link_dest->getBottom();
313 dest->right = link_dest->getRight();
314 dest->top = link_dest->getTop();
315 dest->zoom = link_dest->getZoom();
316 dest->change_left = link_dest->getChangeLeft();
317 dest->change_top = link_dest->getChangeTop();
318 dest->change_zoom = link_dest->getChangeZoom();
319
320 if (document && dest->page_num > 0) {
321 PopplerPage *page;
322
323 page = poppler_document_get_page(document, index: dest->page_num - 1);
324
325 if (page) {
326 dest->left -= page->page->getCropBox()->x1;
327 dest->bottom -= page->page->getCropBox()->x1;
328 dest->right -= page->page->getCropBox()->y1;
329 dest->top -= page->page->getCropBox()->y1;
330
331 g_object_unref(object: page);
332 } else {
333 g_warning("Invalid page %d in Link Destination\n", dest->page_num);
334 dest->page_num = 0;
335 }
336 }
337
338 return dest;
339}
340
341static PopplerDest *dest_new_named(const GooString *named_dest)
342{
343 PopplerDest *dest;
344
345 dest = g_slice_new0(PopplerDest);
346
347 if (named_dest == nullptr) {
348 dest->type = POPPLER_DEST_UNKNOWN;
349 return dest;
350 }
351
352 const std::string &str = named_dest->toStr();
353
354 dest->type = POPPLER_DEST_NAMED;
355 dest->named_dest = poppler_named_dest_from_bytestring(data: (const guint8 *)str.data(), length: str.size());
356
357 return dest;
358}
359
360static void build_goto_dest(PopplerDocument *document, PopplerAction *action, const LinkGoTo *link)
361{
362 const LinkDest *link_dest;
363 const GooString *named_dest;
364
365 /* Return if it isn't OK */
366 if (!link->isOk()) {
367 action->goto_dest.dest = dest_new_goto(document: nullptr, link_dest: nullptr);
368 return;
369 }
370
371 link_dest = link->getDest();
372 named_dest = link->getNamedDest();
373
374 if (link_dest != nullptr) {
375 action->goto_dest.dest = dest_new_goto(document, link_dest);
376 } else if (named_dest != nullptr) {
377 action->goto_dest.dest = dest_new_named(named_dest);
378 } else {
379 action->goto_dest.dest = dest_new_goto(document, link_dest: nullptr);
380 }
381}
382
383static void build_goto_remote(PopplerAction *action, const LinkGoToR *link)
384{
385 const LinkDest *link_dest;
386 const GooString *named_dest;
387
388 /* Return if it isn't OK */
389 if (!link->isOk()) {
390 action->goto_remote.dest = dest_new_goto(document: nullptr, link_dest: nullptr);
391 return;
392 }
393
394 action->goto_remote.file_name = _poppler_goo_string_to_utf8(s: link->getFileName());
395
396 link_dest = link->getDest();
397 named_dest = link->getNamedDest();
398
399 if (link_dest != nullptr) {
400 action->goto_remote.dest = dest_new_goto(document: nullptr, link_dest);
401 } else if (named_dest != nullptr) {
402 action->goto_remote.dest = dest_new_named(named_dest);
403 } else {
404 action->goto_remote.dest = dest_new_goto(document: nullptr, link_dest: nullptr);
405 }
406}
407
408static void build_launch(PopplerAction *action, const LinkLaunch *link)
409{
410 if (link->getFileName()) {
411 action->launch.file_name = g_strdup(str: link->getFileName()->c_str());
412 }
413 if (link->getParams()) {
414 action->launch.params = g_strdup(str: link->getParams()->c_str());
415 }
416}
417
418static void build_uri(PopplerAction *action, const LinkURI *link)
419{
420 const gchar *uri = link->getURI().c_str();
421 if (uri != nullptr) {
422 action->uri.uri = g_strdup(str: uri);
423 }
424}
425
426static void build_named(PopplerAction *action, const LinkNamed *link)
427{
428 const gchar *name = link->getName().c_str();
429 if (name != nullptr) {
430 action->named.named_dest = g_strdup(str: name);
431 }
432}
433
434static AnnotMovie *find_annot_movie_for_action(PopplerDocument *document, const LinkMovie *link)
435{
436 AnnotMovie *annot = nullptr;
437 XRef *xref = document->doc->getXRef();
438 Object annotObj;
439
440 if (link->hasAnnotRef()) {
441 const Ref *ref = link->getAnnotRef();
442
443 annotObj = xref->fetch(ref: *ref);
444 } else if (link->hasAnnotTitle()) {
445 const std::string &title = link->getAnnotTitle();
446 int i;
447
448 for (i = 1; i <= document->doc->getNumPages(); ++i) {
449 Page *p = document->doc->getPage(page: i);
450 if (!p) {
451 continue;
452 }
453
454 Object annots = p->getAnnotsObject();
455 if (annots.isArray()) {
456 int j;
457 bool found = false;
458
459 for (j = 0; j < annots.arrayGetLength() && !found; ++j) {
460 annotObj = annots.arrayGet(i: j);
461 if (annotObj.isDict()) {
462 Object obj1 = annotObj.dictLookup(key: "Subtype");
463 if (!obj1.isName(nameA: "Movie")) {
464 continue;
465 }
466
467 obj1 = annotObj.dictLookup(key: "T");
468 if (obj1.isString() && obj1.getString()->toStr() == title) {
469 found = true;
470 }
471 }
472 if (!found) {
473 annotObj.setToNull();
474 }
475 }
476 if (found) {
477 break;
478 } else {
479 annotObj.setToNull();
480 }
481 }
482 }
483 }
484
485 if (annotObj.isDict()) {
486 Object tmp;
487
488 annot = new AnnotMovie(document->doc, std::move(annotObj), &tmp);
489 if (!annot->isOk()) {
490 delete annot;
491 annot = nullptr;
492 }
493 }
494
495 return annot;
496}
497
498static void build_movie(PopplerDocument *document, PopplerAction *action, const LinkMovie *link)
499{
500 AnnotMovie *annot;
501
502 switch (link->getOperation()) {
503 case LinkMovie::operationTypePause:
504 action->movie.operation = POPPLER_ACTION_MOVIE_PAUSE;
505 break;
506 case LinkMovie::operationTypeResume:
507 action->movie.operation = POPPLER_ACTION_MOVIE_RESUME;
508 break;
509 case LinkMovie::operationTypeStop:
510 action->movie.operation = POPPLER_ACTION_MOVIE_STOP;
511 break;
512 default:
513 case LinkMovie::operationTypePlay:
514 action->movie.operation = POPPLER_ACTION_MOVIE_PLAY;
515 break;
516 }
517
518 annot = find_annot_movie_for_action(document, link);
519 if (annot) {
520 action->movie.movie = _poppler_movie_new(movie: annot->getMovie());
521 delete annot;
522 }
523}
524
525static void build_javascript(PopplerAction *action, const LinkJavaScript *link)
526{
527 if (link->isOk()) {
528 const GooString script(link->getScript());
529 action->javascript.script = _poppler_goo_string_to_utf8(s: &script);
530 }
531}
532
533static void build_reset_form(PopplerAction *action, const LinkResetForm *link)
534{
535 const std::vector<std::string> &fields = link->getFields();
536
537 if (action->reset_form.fields != nullptr) {
538 g_list_free_full(list: action->reset_form.fields, free_func: g_free);
539 }
540
541 action->reset_form.fields = nullptr;
542 for (const auto &field : fields) {
543 action->reset_form.fields = g_list_append(list: action->reset_form.fields, data: g_strdup(str: field.c_str()));
544 }
545
546 action->reset_form.exclude = link->getExclude();
547}
548
549static void build_rendition(PopplerAction *action, const LinkRendition *link)
550{
551 action->rendition.op = link->getOperation();
552 if (link->getMedia()) {
553 action->rendition.media = _poppler_media_new(media: link->getMedia());
554 }
555 // TODO: annotation reference
556}
557
558static PopplerLayer *get_layer_for_ref(PopplerDocument *document, GList *layers, const Ref ref, gboolean preserve_rb)
559{
560 GList *l;
561
562 for (l = layers; l; l = g_list_next(l)) {
563 Layer *layer = (Layer *)l->data;
564
565 if (layer->oc) {
566 const Ref ocgRef = layer->oc->getRef();
567
568 if (ref == ocgRef) {
569 GList *rb_group = nullptr;
570
571 if (preserve_rb) {
572 rb_group = _poppler_document_get_layer_rbgroup(document, layer);
573 }
574 return _poppler_layer_new(document, layer, rbgroup: rb_group);
575 }
576 }
577
578 if (layer->kids) {
579 PopplerLayer *retval = get_layer_for_ref(document, layers: layer->kids, ref, preserve_rb);
580 if (retval) {
581 return retval;
582 }
583 }
584 }
585
586 return nullptr;
587}
588
589static void build_ocg_state(PopplerDocument *document, PopplerAction *action, const LinkOCGState *ocg_state)
590{
591 const std::vector<LinkOCGState::StateList> &st_list = ocg_state->getStateList();
592 bool preserve_rb = ocg_state->getPreserveRB();
593 GList *layer_state = nullptr;
594
595 if (!document->layers) {
596 if (!_poppler_document_get_layers(document)) {
597 return;
598 }
599 }
600
601 for (const LinkOCGState::StateList &list : st_list) {
602 PopplerActionLayer *action_layer = g_slice_new0(PopplerActionLayer);
603
604 switch (list.st) {
605 case LinkOCGState::On:
606 action_layer->action = POPPLER_ACTION_LAYER_ON;
607 break;
608 case LinkOCGState::Off:
609 action_layer->action = POPPLER_ACTION_LAYER_OFF;
610 break;
611 case LinkOCGState::Toggle:
612 action_layer->action = POPPLER_ACTION_LAYER_TOGGLE;
613 break;
614 }
615
616 for (const Ref &ref : list.list) {
617 PopplerLayer *layer = get_layer_for_ref(document, layers: document->layers, ref, preserve_rb);
618
619 action_layer->layers = g_list_prepend(list: action_layer->layers, data: layer);
620 }
621
622 layer_state = g_list_prepend(list: layer_state, data: action_layer);
623 }
624
625 action->ocg_state.state_list = g_list_reverse(list: layer_state);
626}
627
628PopplerAction *_poppler_action_new(PopplerDocument *document, const LinkAction *link, const gchar *title)
629{
630 PopplerAction *action;
631
632 action = g_slice_new0(PopplerAction);
633
634 if (title) {
635 action->any.title = g_strdup(str: title);
636 }
637
638 if (link == nullptr) {
639 action->type = POPPLER_ACTION_NONE;
640 return action;
641 }
642
643 switch (link->getKind()) {
644 case actionGoTo:
645 action->type = POPPLER_ACTION_GOTO_DEST;
646 build_goto_dest(document, action, link: static_cast<const LinkGoTo *>(link));
647 break;
648 case actionGoToR:
649 action->type = POPPLER_ACTION_GOTO_REMOTE;
650 build_goto_remote(action, link: static_cast<const LinkGoToR *>(link));
651 break;
652 case actionLaunch:
653 action->type = POPPLER_ACTION_LAUNCH;
654 build_launch(action, link: static_cast<const LinkLaunch *>(link));
655 break;
656 case actionURI:
657 action->type = POPPLER_ACTION_URI;
658 build_uri(action, link: static_cast<const LinkURI *>(link));
659 break;
660 case actionNamed:
661 action->type = POPPLER_ACTION_NAMED;
662 build_named(action, link: static_cast<const LinkNamed *>(link));
663 break;
664 case actionMovie:
665 action->type = POPPLER_ACTION_MOVIE;
666 build_movie(document, action, link: static_cast<const LinkMovie *>(link));
667 break;
668 case actionRendition:
669 action->type = POPPLER_ACTION_RENDITION;
670 build_rendition(action, link: static_cast<const LinkRendition *>(link));
671 break;
672 case actionOCGState:
673 action->type = POPPLER_ACTION_OCG_STATE;
674 build_ocg_state(document, action, ocg_state: static_cast<const LinkOCGState *>(link));
675 break;
676 case actionJavaScript:
677 action->type = POPPLER_ACTION_JAVASCRIPT;
678 build_javascript(action, link: static_cast<const LinkJavaScript *>(link));
679 break;
680 case actionResetForm:
681 action->type = POPPLER_ACTION_RESET_FORM;
682 build_reset_form(action, link: dynamic_cast<const LinkResetForm *>(link));
683 break;
684 case actionUnknown:
685 default:
686 action->type = POPPLER_ACTION_UNKNOWN;
687 break;
688 }
689
690 return action;
691}
692
693PopplerDest *_poppler_dest_new_goto(PopplerDocument *document, LinkDest *link_dest)
694{
695 return dest_new_goto(document, link_dest);
696}
697

source code of poppler/glib/poppler-action.cc