358 lines
8.3 KiB
C
358 lines
8.3 KiB
C
|
#include "mupdf/fitz.h"
|
||
|
#include "mupdf/pdf.h"
|
||
|
|
||
|
#include <string.h>
|
||
|
|
||
|
static pdf_obj *
|
||
|
resolve_dest_rec(fz_context *ctx, pdf_document *doc, pdf_obj *dest, int depth)
|
||
|
{
|
||
|
if (depth > 10) /* Arbitrary to avoid infinite recursion */
|
||
|
return NULL;
|
||
|
|
||
|
if (pdf_is_name(ctx, dest) || pdf_is_string(ctx, dest))
|
||
|
{
|
||
|
dest = pdf_lookup_dest(ctx, doc, dest);
|
||
|
dest = resolve_dest_rec(ctx, doc, dest, depth+1);
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
else if (pdf_is_array(ctx, dest))
|
||
|
{
|
||
|
return dest;
|
||
|
}
|
||
|
|
||
|
else if (pdf_is_dict(ctx, dest))
|
||
|
{
|
||
|
dest = pdf_dict_get(ctx, dest, PDF_NAME(D));
|
||
|
return resolve_dest_rec(ctx, doc, dest, depth+1);
|
||
|
}
|
||
|
|
||
|
else if (pdf_is_indirect(ctx, dest))
|
||
|
return dest;
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static pdf_obj *
|
||
|
resolve_dest(fz_context *ctx, pdf_document *doc, pdf_obj *dest)
|
||
|
{
|
||
|
return resolve_dest_rec(ctx, doc, dest, 0);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
pdf_parse_link_dest(fz_context *ctx, pdf_document *doc, pdf_obj *dest)
|
||
|
{
|
||
|
pdf_obj *obj, *pageobj;
|
||
|
fz_rect mediabox;
|
||
|
fz_matrix pagectm;
|
||
|
const char *ld;
|
||
|
int page, x, y, h;
|
||
|
|
||
|
dest = resolve_dest(ctx, doc, dest);
|
||
|
if (dest == NULL)
|
||
|
{
|
||
|
fz_warn(ctx, "undefined link destination");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (pdf_is_name(ctx, dest))
|
||
|
{
|
||
|
ld = pdf_to_name(ctx, dest);
|
||
|
return fz_strdup(ctx, ld);
|
||
|
}
|
||
|
else if (pdf_is_string(ctx, dest))
|
||
|
{
|
||
|
ld = pdf_to_str_buf(ctx, dest);
|
||
|
return fz_strdup(ctx, ld);
|
||
|
}
|
||
|
|
||
|
pageobj = pdf_array_get(ctx, dest, 0);
|
||
|
if (pdf_is_int(ctx, pageobj))
|
||
|
{
|
||
|
page = pdf_to_int(ctx, pageobj);
|
||
|
pageobj = pdf_lookup_page_obj(ctx, doc, page);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fz_try(ctx)
|
||
|
page = pdf_lookup_page_number(ctx, doc, pageobj);
|
||
|
fz_catch(ctx)
|
||
|
page = -1;
|
||
|
}
|
||
|
|
||
|
if (page < 0)
|
||
|
return NULL;
|
||
|
|
||
|
obj = pdf_array_get(ctx, dest, 1);
|
||
|
if (obj)
|
||
|
{
|
||
|
/* Link coords use a coordinate space that does not seem to respect Rotate or UserUnit. */
|
||
|
/* All we need to do is figure out the page height to flip the coordinate space. */
|
||
|
pdf_page_obj_transform(ctx, pageobj, &mediabox, &pagectm);
|
||
|
mediabox = fz_transform_rect(mediabox, pagectm);
|
||
|
h = mediabox.y1 - mediabox.y0;
|
||
|
|
||
|
if (pdf_name_eq(ctx, obj, PDF_NAME(XYZ)))
|
||
|
{
|
||
|
x = pdf_array_get_int(ctx, dest, 2);
|
||
|
y = h - pdf_array_get_int(ctx, dest, 3);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, obj, PDF_NAME(FitR)))
|
||
|
{
|
||
|
x = pdf_array_get_int(ctx, dest, 2);
|
||
|
y = h - pdf_array_get_int(ctx, dest, 5);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, obj, PDF_NAME(FitH)) || pdf_name_eq(ctx, obj, PDF_NAME(FitBH)))
|
||
|
{
|
||
|
x = 0;
|
||
|
y = h - pdf_array_get_int(ctx, dest, 2);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, obj, PDF_NAME(FitV)) || pdf_name_eq(ctx, obj, PDF_NAME(FitBV)))
|
||
|
{
|
||
|
x = pdf_array_get_int(ctx, dest, 2);
|
||
|
y = 0;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
x = 0;
|
||
|
y = 0;
|
||
|
}
|
||
|
return fz_asprintf(ctx, "#%d,%d,%d", page + 1, x, y);
|
||
|
}
|
||
|
|
||
|
return fz_asprintf(ctx, "#%d", page + 1);
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
pdf_parse_file_spec(fz_context *ctx, pdf_document *doc, pdf_obj *file_spec, pdf_obj *dest)
|
||
|
{
|
||
|
pdf_obj *filename = NULL;
|
||
|
const char *path;
|
||
|
char *uri;
|
||
|
char frag[256];
|
||
|
|
||
|
if (pdf_is_string(ctx, file_spec))
|
||
|
filename = file_spec;
|
||
|
|
||
|
if (pdf_is_dict(ctx, file_spec)) {
|
||
|
#ifdef _WIN32
|
||
|
filename = pdf_dict_get(ctx, file_spec, PDF_NAME(DOS));
|
||
|
#else
|
||
|
filename = pdf_dict_get(ctx, file_spec, PDF_NAME(Unix));
|
||
|
#endif
|
||
|
if (!filename)
|
||
|
filename = pdf_dict_geta(ctx, file_spec, PDF_NAME(UF), PDF_NAME(F));
|
||
|
}
|
||
|
|
||
|
if (!pdf_is_string(ctx, filename))
|
||
|
{
|
||
|
fz_warn(ctx, "cannot parse file specification");
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
if (pdf_is_array(ctx, dest))
|
||
|
fz_snprintf(frag, sizeof frag, "#page=%d", pdf_array_get_int(ctx, dest, 0) + 1);
|
||
|
else if (pdf_is_name(ctx, dest))
|
||
|
fz_snprintf(frag, sizeof frag, "#%s", pdf_to_name(ctx, dest));
|
||
|
else if (pdf_is_string(ctx, dest))
|
||
|
fz_snprintf(frag, sizeof frag, "#%s", pdf_to_str_buf(ctx, dest));
|
||
|
else
|
||
|
frag[0] = 0;
|
||
|
|
||
|
path = pdf_to_text_string(ctx, filename);
|
||
|
uri = NULL;
|
||
|
#ifdef _WIN32
|
||
|
if (!pdf_name_eq(ctx, pdf_dict_get(ctx, file_spec, PDF_NAME(FS)), PDF_NAME(URL)))
|
||
|
{
|
||
|
/* Fix up the drive letter (change "/C/Documents/Foo" to "C:/Documents/Foo") */
|
||
|
if (path[0] == '/' && (('A' <= path[1] && path[1] <= 'Z') || ('a' <= path[1] && path[1] <= 'z')) && path[2] == '/')
|
||
|
uri = fz_asprintf(ctx, "file://%c:%s%s", path[1], path+2, frag);
|
||
|
}
|
||
|
#endif
|
||
|
if (!uri)
|
||
|
uri = fz_asprintf(ctx, "file://%s%s", path, frag);
|
||
|
|
||
|
return uri;
|
||
|
}
|
||
|
|
||
|
char *
|
||
|
pdf_parse_link_action(fz_context *ctx, pdf_document *doc, pdf_obj *action, int pagenum)
|
||
|
{
|
||
|
pdf_obj *obj, *dest, *file_spec;
|
||
|
|
||
|
if (!action)
|
||
|
return NULL;
|
||
|
|
||
|
obj = pdf_dict_get(ctx, action, PDF_NAME(S));
|
||
|
if (pdf_name_eq(ctx, PDF_NAME(GoTo), obj))
|
||
|
{
|
||
|
dest = pdf_dict_get(ctx, action, PDF_NAME(D));
|
||
|
return pdf_parse_link_dest(ctx, doc, dest);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(URI), obj))
|
||
|
{
|
||
|
/* URI entries are ASCII strings */
|
||
|
const char *uri = pdf_dict_get_text_string(ctx, action, PDF_NAME(URI));
|
||
|
if (!fz_is_external_link(ctx, uri))
|
||
|
{
|
||
|
pdf_obj *uri_base_obj = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/URI/Base");
|
||
|
const char *uri_base = uri_base_obj ? pdf_to_text_string(ctx, uri_base_obj) : "file://";
|
||
|
char *new_uri = fz_malloc(ctx, strlen(uri_base) + strlen(uri) + 1);
|
||
|
strcpy(new_uri, uri_base);
|
||
|
strcat(new_uri, uri);
|
||
|
return new_uri;
|
||
|
}
|
||
|
return fz_strdup(ctx, uri);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(Launch), obj))
|
||
|
{
|
||
|
file_spec = pdf_dict_get(ctx, action, PDF_NAME(F));
|
||
|
return pdf_parse_file_spec(ctx, doc, file_spec, NULL);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(GoToR), obj))
|
||
|
{
|
||
|
dest = pdf_dict_get(ctx, action, PDF_NAME(D));
|
||
|
file_spec = pdf_dict_get(ctx, action, PDF_NAME(F));
|
||
|
return pdf_parse_file_spec(ctx, doc, file_spec, dest);
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(Named), obj))
|
||
|
{
|
||
|
dest = pdf_dict_get(ctx, action, PDF_NAME(N));
|
||
|
|
||
|
if (pdf_name_eq(ctx, PDF_NAME(FirstPage), dest))
|
||
|
pagenum = 0;
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(LastPage), dest))
|
||
|
pagenum = pdf_count_pages(ctx, doc) - 1;
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(PrevPage), dest) && pagenum >= 0)
|
||
|
{
|
||
|
if (pagenum > 0)
|
||
|
pagenum--;
|
||
|
}
|
||
|
else if (pdf_name_eq(ctx, PDF_NAME(NextPage), dest) && pagenum >= 0)
|
||
|
{
|
||
|
if (pagenum < pdf_count_pages(ctx, doc) - 1)
|
||
|
pagenum++;
|
||
|
}
|
||
|
else
|
||
|
return NULL;
|
||
|
|
||
|
return fz_asprintf(ctx, "#%d", pagenum + 1);
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static fz_link *
|
||
|
pdf_load_link(fz_context *ctx, pdf_document *doc, pdf_obj *dict, int pagenum, fz_matrix page_ctm)
|
||
|
{
|
||
|
pdf_obj *action;
|
||
|
pdf_obj *obj;
|
||
|
fz_rect bbox;
|
||
|
char *uri;
|
||
|
fz_link *link = NULL;
|
||
|
|
||
|
obj = pdf_dict_get(ctx, dict, PDF_NAME(Subtype));
|
||
|
if (!pdf_name_eq(ctx, obj, PDF_NAME(Link)))
|
||
|
return NULL;
|
||
|
|
||
|
obj = pdf_dict_get(ctx, dict, PDF_NAME(Rect));
|
||
|
if (!obj)
|
||
|
return NULL;
|
||
|
|
||
|
bbox = pdf_to_rect(ctx, obj);
|
||
|
bbox = fz_transform_rect(bbox, page_ctm);
|
||
|
|
||
|
obj = pdf_dict_get(ctx, dict, PDF_NAME(Dest));
|
||
|
if (obj)
|
||
|
uri = pdf_parse_link_dest(ctx, doc, obj);
|
||
|
else
|
||
|
{
|
||
|
action = pdf_dict_get(ctx, dict, PDF_NAME(A));
|
||
|
/* fall back to additional action button's down/up action */
|
||
|
if (!action)
|
||
|
action = pdf_dict_geta(ctx, pdf_dict_get(ctx, dict, PDF_NAME(AA)), PDF_NAME(U), PDF_NAME(D));
|
||
|
uri = pdf_parse_link_action(ctx, doc, action, pagenum);
|
||
|
}
|
||
|
|
||
|
if (!uri)
|
||
|
return NULL;
|
||
|
|
||
|
fz_try(ctx)
|
||
|
link = fz_new_link(ctx, bbox, doc, uri);
|
||
|
fz_always(ctx)
|
||
|
fz_free(ctx, uri);
|
||
|
fz_catch(ctx)
|
||
|
fz_rethrow(ctx);
|
||
|
|
||
|
return link;
|
||
|
}
|
||
|
|
||
|
fz_link *
|
||
|
pdf_load_link_annots(fz_context *ctx, pdf_document *doc, pdf_obj *annots, int pagenum, fz_matrix page_ctm)
|
||
|
{
|
||
|
fz_link *link, *head, *tail;
|
||
|
pdf_obj *obj;
|
||
|
int i, n;
|
||
|
|
||
|
head = tail = NULL;
|
||
|
link = NULL;
|
||
|
|
||
|
n = pdf_array_len(ctx, annots);
|
||
|
for (i = 0; i < n; i++)
|
||
|
{
|
||
|
/* FIXME: Move the try/catch out of the loop for performance? */
|
||
|
fz_try(ctx)
|
||
|
{
|
||
|
obj = pdf_array_get(ctx, annots, i);
|
||
|
link = pdf_load_link(ctx, doc, obj, pagenum, page_ctm);
|
||
|
}
|
||
|
fz_catch(ctx)
|
||
|
{
|
||
|
fz_rethrow_if(ctx, FZ_ERROR_TRYLATER);
|
||
|
link = NULL;
|
||
|
}
|
||
|
|
||
|
if (link)
|
||
|
{
|
||
|
if (!head)
|
||
|
head = tail = link;
|
||
|
else
|
||
|
{
|
||
|
tail->next = link;
|
||
|
tail = link;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return head;
|
||
|
}
|
||
|
|
||
|
int
|
||
|
pdf_resolve_link(fz_context *ctx, pdf_document *doc, const char *uri, float *xp, float *yp)
|
||
|
{
|
||
|
if (uri && uri[0] == '#')
|
||
|
{
|
||
|
int page = fz_atoi(uri + 1) - 1;
|
||
|
if (xp || yp)
|
||
|
{
|
||
|
const char *x = strchr(uri, ',');
|
||
|
const char *y = strrchr(uri, ',');
|
||
|
if (x && y)
|
||
|
{
|
||
|
if (xp) *xp = fz_atoi(x + 1);
|
||
|
if (yp) *yp = fz_atoi(y + 1);
|
||
|
}
|
||
|
}
|
||
|
return page;
|
||
|
}
|
||
|
fz_warn(ctx, "unknown link uri '%s'", uri);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
fz_location
|
||
|
pdf_resolve_link_imp(fz_context *ctx, fz_document *doc_, const char *uri, float *xp, float *yp)
|
||
|
{
|
||
|
pdf_document *doc = (pdf_document*)doc_;
|
||
|
return fz_make_location(0, pdf_resolve_link(ctx, doc, uri, xp, yp));
|
||
|
}
|