blob: 726daedf236ea62f1a48f820cf738fad3a0c6d45 [file] [log] [blame]
/* 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 = g_strdup(link->getFileName()->c_str());
}
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<std::string> &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<LinkOCGState::StateList> &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<const LinkGoTo *>(link));
break;
case actionGoToR:
action->type = POPPLER_ACTION_GOTO_REMOTE;
build_goto_remote(action, static_cast<const LinkGoToR *>(link));
break;
case actionLaunch:
action->type = POPPLER_ACTION_LAUNCH;
build_launch(action, static_cast<const LinkLaunch *>(link));
break;
case actionURI:
action->type = POPPLER_ACTION_URI;
build_uri(action, static_cast<const LinkURI *>(link));
break;
case actionNamed:
action->type = POPPLER_ACTION_NAMED;
build_named(action, static_cast<const LinkNamed *>(link));
break;
case actionMovie:
action->type = POPPLER_ACTION_MOVIE;
build_movie(document, action, static_cast<const LinkMovie *>(link));
break;
case actionRendition:
action->type = POPPLER_ACTION_RENDITION;
build_rendition(action, static_cast<const LinkRendition *>(link));
break;
case actionOCGState:
action->type = POPPLER_ACTION_OCG_STATE;
build_ocg_state(document, action, static_cast<const LinkOCGState *>(link));
break;
case actionJavaScript:
action->type = POPPLER_ACTION_JAVASCRIPT;
build_javascript(action, static_cast<const LinkJavaScript *>(link));
break;
case actionResetForm:
action->type = POPPLER_ACTION_RESET_FORM;
build_reset_form(action, dynamic_cast<const LinkResetForm *>(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);
}