blob: 3efa899400c3ce64053224a8d33bf3b1b706862d [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
*/
POPPLER_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_foreach (action_layer->layers, (GFunc)g_object_unref, nullptr);
g_list_free (action_layer->layers);
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);
g_list_foreach (action_layer->layers, (GFunc)g_object_ref, nullptr);
return retval;
}
POPPLER_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_foreach (action->ocg_state.state_list, (GFunc)poppler_action_layer_free, nullptr);
g_list_free (action->ocg_state.state_list);
}
break;
case POPPLER_ACTION_JAVASCRIPT:
if (action->javascript.script)
g_free (action->javascript.script);
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;
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;
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;
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 GooString *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()) {
const GooString *t = obj1.getString ();
if (title->cmp(t) == 0)
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)
{
const GooString *script;
script = link->getScript();
if (script)
action->javascript.script = _poppler_goo_string_to_utf8 (script);
}
static void
build_rendition (PopplerAction *action,
const LinkRendition *link)
{
action->rendition.op = link->getOperation();
if (link->hasRenditionObject())
action->rendition.media = _poppler_media_new (link->getMedia());
// TODO: annotation reference
}
static PopplerLayer *
get_layer_for_ref (PopplerDocument *document,
GList *layers,
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 (std::size_t i = 0; i < st_list->size(); ++i) {
LinkOCGState::StateList *list = (*st_list)[i];
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 (std::size_t j = 0; j < list->list->size(); ++j) {
Ref *ref = (*list->list)[j];
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, dynamic_cast <const LinkGoTo *> (link));
break;
case actionGoToR:
action->type = POPPLER_ACTION_GOTO_REMOTE;
build_goto_remote (action, dynamic_cast <const LinkGoToR *> (link));
break;
case actionLaunch:
action->type = POPPLER_ACTION_LAUNCH;
build_launch (action, dynamic_cast <const LinkLaunch *> (link));
break;
case actionURI:
action->type = POPPLER_ACTION_URI;
build_uri (action, dynamic_cast <const LinkURI *> (link));
break;
case actionNamed:
action->type = POPPLER_ACTION_NAMED;
build_named (action, dynamic_cast <const LinkNamed *> (link));
break;
case actionMovie:
action->type = POPPLER_ACTION_MOVIE;
build_movie (document, action, dynamic_cast<const LinkMovie*> (link));
break;
case actionRendition:
action->type = POPPLER_ACTION_RENDITION;
build_rendition (action, dynamic_cast<const LinkRendition*> (link));
break;
case actionOCGState:
action->type = POPPLER_ACTION_OCG_STATE;
build_ocg_state (document, action, dynamic_cast<const LinkOCGState*> (link));
break;
case actionJavaScript:
action->type = POPPLER_ACTION_JAVASCRIPT;
build_javascript (action, dynamic_cast<const LinkJavaScript*> (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);
}