/* poppler-action.cc: glib wrapper for poppler -*- c-basic-offset: 8 -*- * Copyright (C) 2005, Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "poppler.h" #include "poppler-private.h" /** * SECTION:poppler-action * @short_description: Action links * @title: PopplerAction */ G_DEFINE_BOXED_TYPE(PopplerDest, poppler_dest, poppler_dest_copy, poppler_dest_free) /** * poppler_dest_copy: * @dest: a #PopplerDest * * Copies @dest, creating an identical #PopplerDest. * * Return value: a new destination identical to @dest **/ PopplerDest *poppler_dest_copy(PopplerDest *dest) { PopplerDest *new_dest; new_dest = g_slice_dup(PopplerDest, dest); if (dest->named_dest) { new_dest->named_dest = g_strdup(dest->named_dest); } return new_dest; } /** * poppler_dest_free: * @dest: a #PopplerDest * * Frees @dest **/ void poppler_dest_free(PopplerDest *dest) { if (!dest) { return; } if (dest->named_dest) { g_free(dest->named_dest); } g_slice_free(PopplerDest, dest); } static void poppler_action_layer_free(PopplerActionLayer *action_layer) { if (!action_layer) { return; } if (action_layer->layers) { g_list_free_full(action_layer->layers, g_object_unref); action_layer->layers = nullptr; } g_slice_free(PopplerActionLayer, action_layer); } static PopplerActionLayer *poppler_action_layer_copy(PopplerActionLayer *action_layer) { PopplerActionLayer *retval = g_slice_dup(PopplerActionLayer, action_layer); retval->layers = g_list_copy(action_layer->layers); for (GList *l = retval->layers; l != nullptr; l = l->next) { g_object_ref(l->data); } return retval; } G_DEFINE_BOXED_TYPE(PopplerAction, poppler_action, poppler_action_copy, poppler_action_free) /** * poppler_action_free: * @action: a #PopplerAction * * Frees @action **/ void poppler_action_free(PopplerAction *action) { if (action == nullptr) { return; } /* Action specific stuff */ switch (action->type) { case POPPLER_ACTION_GOTO_DEST: poppler_dest_free(action->goto_dest.dest); break; case POPPLER_ACTION_GOTO_REMOTE: poppler_dest_free(action->goto_remote.dest); g_free(action->goto_remote.file_name); break; case POPPLER_ACTION_URI: g_free(action->uri.uri); break; case POPPLER_ACTION_LAUNCH: g_free(action->launch.file_name); g_free(action->launch.params); break; case POPPLER_ACTION_NAMED: g_free(action->named.named_dest); break; case POPPLER_ACTION_MOVIE: if (action->movie.movie) { g_object_unref(action->movie.movie); } break; case POPPLER_ACTION_RENDITION: if (action->rendition.media) { g_object_unref(action->rendition.media); } break; case POPPLER_ACTION_OCG_STATE: if (action->ocg_state.state_list) { g_list_free_full(action->ocg_state.state_list, (GDestroyNotify)poppler_action_layer_free); } break; case POPPLER_ACTION_JAVASCRIPT: if (action->javascript.script) { g_free(action->javascript.script); } break; case POPPLER_ACTION_RESET_FORM: if (action->reset_form.fields) { g_list_free_full(action->reset_form.fields, g_free); } break; default: break; } g_free(action->any.title); g_slice_free(PopplerAction, action); } /** * poppler_action_copy: * @action: a #PopplerAction * * Copies @action, creating an identical #PopplerAction. * * Return value: a new action identical to @action **/ PopplerAction *poppler_action_copy(PopplerAction *action) { PopplerAction *new_action; g_return_val_if_fail(action != nullptr, NULL); /* Do a straight copy of the memory */ new_action = g_slice_dup(PopplerAction, action); if (action->any.title != nullptr) { new_action->any.title = g_strdup(action->any.title); } switch (action->type) { case POPPLER_ACTION_GOTO_DEST: new_action->goto_dest.dest = poppler_dest_copy(action->goto_dest.dest); break; case POPPLER_ACTION_GOTO_REMOTE: new_action->goto_remote.dest = poppler_dest_copy(action->goto_remote.dest); if (action->goto_remote.file_name) { new_action->goto_remote.file_name = g_strdup(action->goto_remote.file_name); } break; case POPPLER_ACTION_URI: if (action->uri.uri) { new_action->uri.uri = g_strdup(action->uri.uri); } break; case POPPLER_ACTION_LAUNCH: if (action->launch.file_name) { new_action->launch.file_name = g_strdup(action->launch.file_name); } if (action->launch.params) { new_action->launch.params = g_strdup(action->launch.params); } break; case POPPLER_ACTION_NAMED: if (action->named.named_dest) { new_action->named.named_dest = g_strdup(action->named.named_dest); } break; case POPPLER_ACTION_MOVIE: if (action->movie.movie) { new_action->movie.movie = (PopplerMovie *)g_object_ref(action->movie.movie); } break; case POPPLER_ACTION_RENDITION: if (action->rendition.media) { new_action->rendition.media = (PopplerMedia *)g_object_ref(action->rendition.media); } break; case POPPLER_ACTION_OCG_STATE: if (action->ocg_state.state_list) { GList *l; GList *new_list = nullptr; for (l = action->ocg_state.state_list; l; l = g_list_next(l)) { PopplerActionLayer *alayer = (PopplerActionLayer *)l->data; new_list = g_list_prepend(new_list, poppler_action_layer_copy(alayer)); } new_action->ocg_state.state_list = g_list_reverse(new_list); } break; case POPPLER_ACTION_JAVASCRIPT: if (action->javascript.script) { new_action->javascript.script = g_strdup(action->javascript.script); } break; case POPPLER_ACTION_RESET_FORM: if (action->reset_form.fields) { GList *iter; new_action->reset_form.fields = nullptr; for (iter = action->reset_form.fields; iter != nullptr; iter = iter->next) { new_action->reset_form.fields = g_list_append(new_action->reset_form.fields, g_strdup((char *)iter->data)); } } break; default: break; } return new_action; } static PopplerDest *dest_new_goto(PopplerDocument *document, const LinkDest *link_dest) { PopplerDest *dest; dest = g_slice_new0(PopplerDest); if (link_dest == nullptr) { dest->type = POPPLER_DEST_UNKNOWN; return dest; } switch (link_dest->getKind()) { case destXYZ: dest->type = POPPLER_DEST_XYZ; break; case destFit: dest->type = POPPLER_DEST_FIT; break; case destFitH: dest->type = POPPLER_DEST_FITH; break; case destFitV: dest->type = POPPLER_DEST_FITV; break; case destFitR: dest->type = POPPLER_DEST_FITR; break; case destFitB: dest->type = POPPLER_DEST_FITB; break; case destFitBH: dest->type = POPPLER_DEST_FITBH; break; case destFitBV: dest->type = POPPLER_DEST_FITBV; break; default: dest->type = POPPLER_DEST_UNKNOWN; } if (link_dest->isPageRef()) { if (document) { const Ref page_ref = link_dest->getPageRef(); dest->page_num = document->doc->findPage(page_ref); } else { /* FIXME: We don't keep areound the page_ref for the * remote doc, so we can't look this up. Guess that * it's 0*/ dest->page_num = 0; } } else { dest->page_num = link_dest->getPageNum(); } dest->left = link_dest->getLeft(); dest->bottom = link_dest->getBottom(); dest->right = link_dest->getRight(); dest->top = link_dest->getTop(); dest->zoom = link_dest->getZoom(); dest->change_left = link_dest->getChangeLeft(); dest->change_top = link_dest->getChangeTop(); dest->change_zoom = link_dest->getChangeZoom(); if (document && dest->page_num > 0) { PopplerPage *page; page = poppler_document_get_page(document, dest->page_num - 1); if (page) { dest->left -= page->page->getCropBox()->x1; dest->bottom -= page->page->getCropBox()->x1; dest->right -= page->page->getCropBox()->y1; dest->top -= page->page->getCropBox()->y1; g_object_unref(page); } else { g_warning("Invalid page %d in Link Destination\n", dest->page_num); dest->page_num = 0; } } return dest; } static PopplerDest *dest_new_named(const GooString *named_dest) { PopplerDest *dest; dest = g_slice_new0(PopplerDest); if (named_dest == nullptr) { dest->type = POPPLER_DEST_UNKNOWN; return dest; } const std::string &str = named_dest->toStr(); dest->type = POPPLER_DEST_NAMED; dest->named_dest = poppler_named_dest_from_bytestring((const guint8 *)str.data(), str.size()); return dest; } static void build_goto_dest(PopplerDocument *document, PopplerAction *action, const LinkGoTo *link) { const LinkDest *link_dest; const GooString *named_dest; /* Return if it isn't OK */ if (!link->isOk()) { action->goto_dest.dest = dest_new_goto(nullptr, nullptr); return; } link_dest = link->getDest(); named_dest = link->getNamedDest(); if (link_dest != nullptr) { action->goto_dest.dest = dest_new_goto(document, link_dest); } else if (named_dest != nullptr) { action->goto_dest.dest = dest_new_named(named_dest); } else { action->goto_dest.dest = dest_new_goto(document, nullptr); } } static void build_goto_remote(PopplerAction *action, const LinkGoToR *link) { const LinkDest *link_dest; const GooString *named_dest; /* Return if it isn't OK */ if (!link->isOk()) { action->goto_remote.dest = dest_new_goto(nullptr, nullptr); return; } action->goto_remote.file_name = _poppler_goo_string_to_utf8(link->getFileName()); link_dest = link->getDest(); named_dest = link->getNamedDest(); if (link_dest != nullptr) { action->goto_remote.dest = dest_new_goto(nullptr, link_dest); } else if (named_dest != nullptr) { action->goto_remote.dest = dest_new_named(named_dest); } else { action->goto_remote.dest = dest_new_goto(nullptr, nullptr); } } static void build_launch(PopplerAction *action, const LinkLaunch *link) { if (link->getFileName()) { action->launch.file_name = _poppler_goo_string_to_utf8(link->getFileName()); } if (link->getParams()) { action->launch.params = g_strdup(link->getParams()->c_str()); } } static void build_uri(PopplerAction *action, const LinkURI *link) { const gchar *uri = link->getURI().c_str(); if (uri != nullptr) { action->uri.uri = g_strdup(uri); } } static void build_named(PopplerAction *action, const LinkNamed *link) { const gchar *name = link->getName().c_str(); if (name != nullptr) { action->named.named_dest = g_strdup(name); } } static AnnotMovie *find_annot_movie_for_action(PopplerDocument *document, const LinkMovie *link) { AnnotMovie *annot = nullptr; XRef *xref = document->doc->getXRef(); Object annotObj; if (link->hasAnnotRef()) { const Ref *ref = link->getAnnotRef(); annotObj = xref->fetch(*ref); } else if (link->hasAnnotTitle()) { const std::string &title = link->getAnnotTitle(); int i; for (i = 1; i <= document->doc->getNumPages(); ++i) { Page *p = document->doc->getPage(i); if (!p) { continue; } Object annots = p->getAnnotsObject(); if (annots.isArray()) { int j; bool found = false; for (j = 0; j < annots.arrayGetLength() && !found; ++j) { annotObj = annots.arrayGet(j); if (annotObj.isDict()) { Object obj1 = annotObj.dictLookup("Subtype"); if (!obj1.isName("Movie")) { continue; } obj1 = annotObj.dictLookup("T"); if (obj1.isString() && obj1.getString()->toStr() == title) { found = true; } } if (!found) { annotObj.setToNull(); } } if (found) { break; } else { annotObj.setToNull(); } } } } if (annotObj.isDict()) { Object tmp; annot = new AnnotMovie(document->doc, std::move(annotObj), &tmp); if (!annot->isOk()) { delete annot; annot = nullptr; } } return annot; } static void build_movie(PopplerDocument *document, PopplerAction *action, const LinkMovie *link) { AnnotMovie *annot; switch (link->getOperation()) { case LinkMovie::operationTypePause: action->movie.operation = POPPLER_ACTION_MOVIE_PAUSE; break; case LinkMovie::operationTypeResume: action->movie.operation = POPPLER_ACTION_MOVIE_RESUME; break; case LinkMovie::operationTypeStop: action->movie.operation = POPPLER_ACTION_MOVIE_STOP; break; default: case LinkMovie::operationTypePlay: action->movie.operation = POPPLER_ACTION_MOVIE_PLAY; break; } annot = find_annot_movie_for_action(document, link); if (annot) { action->movie.movie = _poppler_movie_new(annot->getMovie()); delete annot; } } static void build_javascript(PopplerAction *action, const LinkJavaScript *link) { if (link->isOk()) { const GooString script(link->getScript()); action->javascript.script = _poppler_goo_string_to_utf8(&script); } } static void build_reset_form(PopplerAction *action, const LinkResetForm *link) { const std::vector &fields = link->getFields(); if (action->reset_form.fields != nullptr) { g_list_free_full(action->reset_form.fields, g_free); } action->reset_form.fields = nullptr; for (const auto &field : fields) { action->reset_form.fields = g_list_append(action->reset_form.fields, g_strdup(field.c_str())); } action->reset_form.exclude = link->getExclude(); } static void build_rendition(PopplerAction *action, const LinkRendition *link) { action->rendition.op = link->getOperation(); if (link->getMedia()) { action->rendition.media = _poppler_media_new(link->getMedia()); } // TODO: annotation reference } static PopplerLayer *get_layer_for_ref(PopplerDocument *document, GList *layers, const Ref ref, gboolean preserve_rb) { GList *l; for (l = layers; l; l = g_list_next(l)) { Layer *layer = (Layer *)l->data; if (layer->oc) { const Ref ocgRef = layer->oc->getRef(); if (ref == ocgRef) { GList *rb_group = nullptr; if (preserve_rb) { rb_group = _poppler_document_get_layer_rbgroup(document, layer); } return _poppler_layer_new(document, layer, rb_group); } } if (layer->kids) { PopplerLayer *retval = get_layer_for_ref(document, layer->kids, ref, preserve_rb); if (retval) { return retval; } } } return nullptr; } static void build_ocg_state(PopplerDocument *document, PopplerAction *action, const LinkOCGState *ocg_state) { const std::vector &st_list = ocg_state->getStateList(); bool preserve_rb = ocg_state->getPreserveRB(); GList *layer_state = nullptr; if (!document->layers) { if (!_poppler_document_get_layers(document)) { return; } } for (const LinkOCGState::StateList &list : st_list) { PopplerActionLayer *action_layer = g_slice_new0(PopplerActionLayer); switch (list.st) { case LinkOCGState::On: action_layer->action = POPPLER_ACTION_LAYER_ON; break; case LinkOCGState::Off: action_layer->action = POPPLER_ACTION_LAYER_OFF; break; case LinkOCGState::Toggle: action_layer->action = POPPLER_ACTION_LAYER_TOGGLE; break; } for (const Ref &ref : list.list) { PopplerLayer *layer = get_layer_for_ref(document, document->layers, ref, preserve_rb); action_layer->layers = g_list_prepend(action_layer->layers, layer); } layer_state = g_list_prepend(layer_state, action_layer); } action->ocg_state.state_list = g_list_reverse(layer_state); } PopplerAction *_poppler_action_new(PopplerDocument *document, const LinkAction *link, const gchar *title) { PopplerAction *action; action = g_slice_new0(PopplerAction); if (title) { action->any.title = g_strdup(title); } if (link == nullptr) { action->type = POPPLER_ACTION_NONE; return action; } switch (link->getKind()) { case actionGoTo: action->type = POPPLER_ACTION_GOTO_DEST; build_goto_dest(document, action, static_cast(link)); break; case actionGoToR: action->type = POPPLER_ACTION_GOTO_REMOTE; build_goto_remote(action, static_cast(link)); break; case actionLaunch: action->type = POPPLER_ACTION_LAUNCH; build_launch(action, static_cast(link)); break; case actionURI: action->type = POPPLER_ACTION_URI; build_uri(action, static_cast(link)); break; case actionNamed: action->type = POPPLER_ACTION_NAMED; build_named(action, static_cast(link)); break; case actionMovie: action->type = POPPLER_ACTION_MOVIE; build_movie(document, action, static_cast(link)); break; case actionRendition: action->type = POPPLER_ACTION_RENDITION; build_rendition(action, static_cast(link)); break; case actionOCGState: action->type = POPPLER_ACTION_OCG_STATE; build_ocg_state(document, action, static_cast(link)); break; case actionJavaScript: action->type = POPPLER_ACTION_JAVASCRIPT; build_javascript(action, static_cast(link)); break; case actionResetForm: action->type = POPPLER_ACTION_RESET_FORM; build_reset_form(action, dynamic_cast(link)); break; case actionUnknown: default: action->type = POPPLER_ACTION_UNKNOWN; break; } return action; } PopplerDest *_poppler_dest_new_goto(PopplerDocument *document, LinkDest *link_dest) { return dest_new_goto(document, link_dest); }