Spaces:
Sleeping
Sleeping
/* 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. | |
*/ | |
/** | |
* 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); | |
} | |