diff options
author | Robin Watts <robin.watts@artifex.com> | 2016-11-23 16:28:25 +0000 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2016-12-12 13:40:40 +0100 |
commit | fd2cb625e77cf71eec9d37bdd034cee6ef184d62 (patch) | |
tree | 38809491d6b1c7f7623e0b41fc3477f41e728d28 /source | |
parent | e3180169b42481fbbc4886db741782ca1391c46e (diff) | |
download | mupdf-fd2cb625e77cf71eec9d37bdd034cee6ef184d62.tar.xz |
PDF Portfolio support.
New PDF Portfolio manipulation API.
Simple mutool 'portfolio' tool for listing/extracting/embedding
files.
Diffstat (limited to 'source')
-rw-r--r-- | source/pdf/pdf-imp.h | 2 | ||||
-rw-r--r-- | source/pdf/pdf-portfolio.c | 740 | ||||
-rw-r--r-- | source/pdf/pdf-xref.c | 1 | ||||
-rw-r--r-- | source/tools/mutool.c | 2 | ||||
-rw-r--r-- | source/tools/pdfportfolio.c | 245 |
5 files changed, 990 insertions, 0 deletions
diff --git a/source/pdf/pdf-imp.h b/source/pdf/pdf-imp.h index 5f2dcc2d..6dd3571d 100644 --- a/source/pdf/pdf-imp.h +++ b/source/pdf/pdf-imp.h @@ -8,4 +8,6 @@ void pdf_drop_ocg(fz_context *ctx, pdf_document *doc); int pdf_is_hidden_ocg(fz_context *ctx, pdf_ocg_descriptor *desc, pdf_obj *rdb, const char *usage, pdf_obj *ocg); +void pdf_drop_portfolio(fz_context *ctx, pdf_document *doc); + #endif diff --git a/source/pdf/pdf-portfolio.c b/source/pdf/pdf-portfolio.c new file mode 100644 index 00000000..c86c254b --- /dev/null +++ b/source/pdf/pdf-portfolio.c @@ -0,0 +1,740 @@ +#include "mupdf/fitz.h" +#include "pdf-imp.h" + +/* + PDF Portfolio is just a sorted list of schema entries. +*/ +struct pdf_portfolio_s +{ + pdf_obj *key; + pdf_obj *val; + int sort; + pdf_portfolio_schema entry; + pdf_portfolio *next; +}; + +static void +load_portfolio(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *obj; + int i, n; + pdf_portfolio **pp; + + obj = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Collection, PDF_NAME_Schema, NULL); + + n = pdf_dict_len(ctx, obj); + for (i = 0; i < n; i++) + { + pdf_obj *k = pdf_dict_get_key(ctx, obj, i); + pdf_obj *v = pdf_dict_get_val(ctx, obj, i); + int sort = pdf_to_int(ctx, pdf_dict_get(ctx, v, PDF_NAME_O)); + pdf_obj *eo = pdf_dict_get(ctx, v, PDF_NAME_E); + int editable = eo ? pdf_to_bool(ctx, eo) : 0; + pdf_obj *vo = pdf_dict_get(ctx, v, PDF_NAME_V); + int visible = vo ? pdf_to_bool(ctx, vo) : 1; + char *subtype = pdf_to_name(ctx, pdf_dict_get(ctx, v, PDF_NAME_Subtype)); + pdf_obj *name = pdf_dict_get(ctx, v, PDF_NAME_N); + pdf_portfolio *p = fz_malloc_struct(ctx, pdf_portfolio); + p->key = pdf_keep_obj(ctx, k); + p->val = pdf_keep_obj(ctx, v); + p->sort = sort; + p->entry.visible = visible; + p->entry.editable = editable; + p->entry.name = pdf_keep_obj(ctx, name); + if (!strcmp(subtype, "S")) + p->entry.type = PDF_SCHEMA_TEXT; + else if (!strcmp(subtype, "D")) + p->entry.type = PDF_SCHEMA_DATE; + else if (!strcmp(subtype, "N")) + p->entry.type = PDF_SCHEMA_NUMBER; + else if (!strcmp(subtype, "F")) + p->entry.type = PDF_SCHEMA_FILENAME; + else if (!strcmp(subtype, "Desc")) + p->entry.type = PDF_SCHEMA_DESC; + else if (!strcmp(subtype, "ModDate")) + p->entry.type = PDF_SCHEMA_MODDATE; + else if (!strcmp(subtype, "CreationDate")) + p->entry.type = PDF_SCHEMA_CREATIONDATE; + else if (!strcmp(subtype, "Size")) + p->entry.type = PDF_SCHEMA_SIZE; + else + p->entry.type = PDF_SCHEMA_UNKNOWN; + + /* Now insert p */ + pp = &doc->portfolio; + + while (*pp && (*pp)->sort <= p->sort) + pp = &(*pp)->next; + + p->next = *pp; + *pp = p; + } +} + +int pdf_count_portfolio_schema(fz_context *ctx, pdf_document *doc) +{ + pdf_portfolio *port; + int i; + + if (!doc) + return 0; + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + for (i = 0, port = doc->portfolio; port; port = port->next, i++); + + return i; +} + +/* + pdf_portfolio_schema_info: Fetch information about a given + portfolio schema entry. + + doc: The document in question. + + entry: A value in the 0..n-1 range, where n is the + value returned from pdf_count_portfolio_schema. + + info: Pointer to structure to fill in. Pointers within + this structure may be set to NULL if no information is + available. +*/ +void pdf_portfolio_schema_info(fz_context *ctx, pdf_document *doc, int entry, pdf_portfolio_schema *info) +{ + pdf_portfolio *p; + + if (!doc || !info) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_portfolio_schema_info call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + p = doc->portfolio; + while (p && entry > 0) + p = p->next, entry--; + + if (p == NULL || entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "entry out of range in pdf_portfolio_schema_info"); + + *info = p->entry; +} + +void pdf_reorder_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, int new_pos) +{ + pdf_portfolio **pp; + pdf_portfolio *p; + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_portfolio_schema_info call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + /* Take p out */ + pp = &doc->portfolio; + while (*pp && entry > 0) + pp = &(*pp)->next, entry--; + p = *pp; + if (p == NULL || entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "entry out of range in pdf_reorder_portfolio_schema"); + *pp = p->next; + + /* Put p back in */ + pp = &doc->portfolio; + while (*pp && new_pos > 0) + pp = &(*pp)->next, new_pos--; + p->next = *pp; + *pp = p; + + /* Rewrite the underlying orderings */ + for (p = doc->portfolio, entry = 0; p; p = p->next, entry++) + pdf_dict_put_drop(ctx, p->val, PDF_NAME_O, pdf_new_int(ctx, doc, entry)); +} + +void pdf_rename_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, const char *name, int name_len) +{ + pdf_portfolio *p; + pdf_obj *s; + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_rename_portfolio_schema call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + p = doc->portfolio; + while (p && entry > 0) + p = p->next, entry--; + + if (p == NULL || entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "entry out of range in pdf_rename_portfolio_schema"); + + s = pdf_new_string(ctx, doc, name, name_len); + pdf_drop_obj(ctx, p->entry.name); + p->entry.name = s; + pdf_dict_put(ctx, p->val, PDF_NAME_N, s); +} + +typedef int (pdf_name_tree_map_fn)(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg); + +static int +do_name_tree_map(fz_context *ctx, pdf_obj *tree, pdf_name_tree_map_fn *fn, void *arg) +{ + int i; + int n = 0; + int m = 0; + + fz_var(n); + fz_var(m); + + if (pdf_mark_obj(ctx, tree)) + fz_throw(ctx, FZ_ERROR_GENERIC, "Recursive name tree!"); + + fz_try(ctx) + { + pdf_obj *arr = pdf_dict_get(ctx, tree, PDF_NAME_Kids); + n = pdf_array_len(ctx, arr); + + for (i = n; i > 0;) + { + i--; + if (do_name_tree_map(ctx, pdf_array_get(ctx, arr, i), fn, arg)) + { + pdf_array_delete(ctx, arr, i); + n--; + } + } + + arr = pdf_dict_get(ctx, tree, PDF_NAME_Names); + m = pdf_array_len(ctx, arr); + + if (m & 1) + fz_throw(ctx, FZ_ERROR_GENERIC, "Malformed Names array"); + + for (i = m; i > 0;) + { + i -= 2; + if (fn(ctx, tree, pdf_array_get(ctx, arr, i), pdf_array_get(ctx, arr, i+1), arg)) + { + pdf_array_delete(ctx, arr, i+1); + pdf_array_delete(ctx, arr, i); + m -= 2; + } + } + } + fz_always(ctx) + pdf_unmark_obj(ctx, tree); + fz_catch(ctx) + fz_rethrow(ctx); + + return n == 0 && m == 0; +} + +void pdf_name_tree_map(fz_context *ctx, pdf_obj *tree, pdf_name_tree_map_fn *fn, void *arg) +{ + (void)do_name_tree_map(ctx, tree, fn, arg); +} + +static int delete_from_node(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg) +{ + pdf_obj *delete_key = (pdf_obj *)arg; + + pdf_dict_del(ctx, pdf_dict_get(ctx, val, PDF_NAME_CI), delete_key); + + return 0; +} + +void pdf_delete_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry) +{ + pdf_portfolio **pp; + pdf_portfolio *p; + pdf_obj *s; + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_delete_portfolio_schema call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + pp = &doc->portfolio; + while (*pp && entry > 0) + pp = &(*pp)->next, entry--; + + p = *pp; + if (p == NULL || entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "entry out of range in pdf_delete_portfolio_schema"); + *pp = p->next; + + /* Delete the key from the schema */ + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Collection, PDF_NAME_Schema, NULL); + pdf_dict_del(ctx, s, p->key); + + /* Delete this entry from all the collection entries */ + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Names, PDF_NAME_EmbeddedFiles, NULL); + pdf_name_tree_map(ctx, s, delete_from_node, p->key); + + pdf_drop_obj(ctx, p->entry.name); + pdf_drop_obj(ctx, p->key); + pdf_drop_obj(ctx, p->val); + fz_free(ctx, p); +} + +void pdf_add_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, const pdf_portfolio_schema *info) +{ + pdf_portfolio **pp; + pdf_portfolio *p; + pdf_obj *s; + pdf_obj *sc; + int num; + char str_name[32]; + pdf_obj *num_name = NULL; + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_add_portfolio_schema call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + fz_var(num_name); + + pp = &doc->portfolio; + while (*pp && entry > 0) + pp = &(*pp)->next, entry--; + + fz_try(ctx) + { + /* Find a name for the new schema entry */ + num = 0; + do + { + pdf_drop_obj(ctx, num_name); + num_name = NULL; + num++; + sprintf(str_name, "%d", num); + num_name = pdf_new_name(ctx, doc, str_name); + p = doc->portfolio; + for (p = doc->portfolio; p; p = p->next) + if (pdf_name_eq(ctx, num_name, p->key)) + break; + } + while (p); + + sc = pdf_new_dict(ctx, doc, 4); + pdf_dict_put_drop(ctx, sc, PDF_NAME_E, pdf_new_bool(ctx, doc, !!info->editable)); + pdf_dict_put_drop(ctx, sc, PDF_NAME_V, pdf_new_bool(ctx, doc, !!info->visible)); + pdf_dict_put_drop(ctx, sc, PDF_NAME_N, info->name); + pdf_dict_put(ctx, sc, PDF_NAME_Subtype, PDF_NAME_S); + + /* Add to our linked list (in the correct sorted place) */ + p = fz_malloc_struct(ctx, pdf_portfolio); + p->entry = *info; + p->sort = 0; /* Will be rewritten in a mo */ + p->key = pdf_keep_obj(ctx, num_name); + p->val = pdf_keep_obj(ctx, sc); + p->next = *pp; + *pp = p; + + /* Add the key to the schema */ + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Collection, PDF_NAME_Schema, NULL); + pdf_dict_put(ctx, s, num_name, sc); + + /* Renumber the schema entries */ + for (num = 0, p = doc->portfolio; p; num++, p = p->next) + { + pdf_dict_put_drop(ctx, p->val, PDF_NAME_O, pdf_new_int(ctx, doc, num)); + p->sort = num; + } + } + fz_always(ctx) + pdf_drop_obj(ctx, num_name); + fz_catch(ctx) + fz_rethrow(ctx); +} + +static int count_nodes(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg) +{ + int *count = (int *)arg; + + *count += 1; + + return 0; +} + +/* + pdf_count_portfolio_entries: Get the number of portfolio entries + in this document. + + doc: The document in question. +*/ +int pdf_count_portfolio_entries(fz_context *ctx, pdf_document *doc) +{ + pdf_obj *s; + int count; + + if (!doc) + return 0; + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Names, PDF_NAME_EmbeddedFiles, NULL); + count = 0; + pdf_name_tree_map(ctx, s, count_nodes, &count); + + return count; +} + +struct find_data { + pdf_obj *key; + pdf_obj *val; + int count; +}; + +static int find_entry(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg) +{ + struct find_data *data = (struct find_data *)arg; + + if (data->count == 0) + { + data->key = key; + data->val = val; + } + data->count--; + + return 0; +} + +/* + pdf_portfolio_entry_info: Fetch information about a given + portfolio entry. + + doc: The document in question. + + entry: A value in the 0..n-1 range, where n is the + value returned from pdf_count_portfolio. + + Returns pdf_object representing this entry. This reference + is borrowed, so call pdf_keep_obj on it if you wish to keep + it. +*/ +pdf_obj *pdf_portfolio_entry_obj_name(fz_context *ctx, pdf_document *doc, int entry, pdf_obj **name) +{ + struct find_data data; + pdf_obj *s; + + if (name) + *name = NULL; + + if (!doc) + return NULL; + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Names, PDF_NAME_EmbeddedFiles, NULL); + data.count = entry; + data.key = NULL; + data.val = NULL; + pdf_name_tree_map(ctx, s, find_entry, &data); + + if (name) + *name = data.key; + return data.val; +} + +pdf_obj *pdf_portfolio_entry_obj(fz_context *ctx, pdf_document *doc, int entry) +{ + pdf_obj *name; + + return pdf_portfolio_entry_obj_name(ctx, doc, entry, &name); +} + +pdf_obj *pdf_portfolio_entry_name(fz_context *ctx, pdf_document *doc, int entry) +{ + pdf_obj *name; + + (void)pdf_portfolio_entry_obj_name(ctx, doc, entry, &name); + return name; +} + +fz_buffer *pdf_portfolio_entry(fz_context *ctx, pdf_document *doc, int entry) +{ + pdf_obj *obj = pdf_portfolio_entry_obj(ctx, doc, entry); + + return pdf_load_stream(ctx, pdf_dict_getl(ctx, obj, PDF_NAME_EF, PDF_NAME_F, NULL)); +} + +pdf_obj *pdf_portfolio_entry_info(fz_context *ctx, pdf_document *doc, int entry, int schema_entry) +{ + pdf_obj *obj = pdf_portfolio_entry_obj_name(ctx, doc, entry, NULL); + pdf_portfolio *p; + pdf_obj *lookup; + int ef = 0; + + if (!obj) + return NULL; + + for (p = doc->portfolio; p != NULL && schema_entry > 0; p = p->next, schema_entry--); + + if (schema_entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "schema_entry out of range"); + + switch (p->entry.type) + { + default: + case PDF_SCHEMA_TEXT: + case PDF_SCHEMA_DATE: + case PDF_SCHEMA_NUMBER: + lookup = NULL; + break; + case PDF_SCHEMA_FILENAME: + lookup = PDF_NAME_UF; + break; + case PDF_SCHEMA_DESC: + lookup = PDF_NAME_Desc; + break; + case PDF_SCHEMA_MODDATE: + lookup = PDF_NAME_ModDate; + ef = 1; + break; + case PDF_SCHEMA_CREATIONDATE: + lookup = PDF_NAME_CreationDate; + ef = 1; + break; + case PDF_SCHEMA_SIZE: + lookup = PDF_NAME_Size; + ef = 1; + break; + } + if (lookup) + { + pdf_obj *res; + + if (ef) + obj = pdf_dict_getl(ctx, obj, PDF_NAME_EF, PDF_NAME_F, PDF_NAME_Params, NULL); + res = pdf_dict_get(ctx, obj, lookup); + if (res == NULL && lookup == PDF_NAME_UF) + res = pdf_dict_get(ctx, obj, PDF_NAME_F); + return res; + } + return pdf_dict_getl(ctx, obj, PDF_NAME_CI, p->key, NULL); +} + +typedef struct +{ + pdf_obj *key; + pdf_obj *found; + int found_index; + pdf_obj *last; + int last_index; + int entry; +} find_data; + +static int +find_position(fz_context *ctx, pdf_obj *container, pdf_obj *key, pdf_obj *val, void *arg) +{ + find_data *data = (find_data *)arg; + + if (data->found) + return 0; + data->entry++; + if (data->last != container) + { + data->last = container; + data->last_index = 0; + } + else + data->last_index++; + if (pdf_objcmp(ctx, key, data->key) > 0) + { + data->found = container; + data->found_index = data->last_index; + } + return 0; +} + +static int +pdf_name_tree_insert(fz_context *ctx, pdf_document *doc, pdf_obj *tree, pdf_obj *key, pdf_obj *val) +{ + find_data data; + pdf_obj *names, *limits, *limit0, *limit1; + + data.key = key; + data.found = NULL; + data.found_index = 0; + data.last = NULL; + data.last_index = 0; + data.entry = 0; + pdf_name_tree_map(ctx, tree, find_position, &data); + + if (!data.found) + { + data.found = data.last; + data.found_index = data.last_index; + } + if (!data.found) + { + /* Completely empty name tree! */ + pdf_dict_put_drop(ctx, tree, PDF_NAME_Names, pdf_new_array(ctx, doc, 2)); + pdf_dict_put_drop(ctx, tree, PDF_NAME_Limits, pdf_new_array(ctx, doc, 2)); + data.found = tree; + data.found_index = 0; + } + + names = pdf_dict_get(ctx, data.found, PDF_NAME_Names); + if (names == NULL) + pdf_dict_put_drop(ctx, data.found, PDF_NAME_Names, (names = pdf_new_array(ctx, doc, 2))); + pdf_array_insert(ctx, names, key, 2*data.found_index); + pdf_array_insert(ctx, names, val, 2*data.found_index+1); + + limits = pdf_dict_get(ctx, data.found, PDF_NAME_Limits); + if (limits == NULL) + pdf_dict_put_drop(ctx, data.found, PDF_NAME_Limits, (limits = pdf_new_array(ctx, doc, 2))); + limit0 = pdf_array_get(ctx, limits, 0); + limit1 = pdf_array_get(ctx, limits, 1); + if (!pdf_is_string(ctx, limit0) || data.found_index == 0) + pdf_array_put(ctx, limits, 0, key); + if (!pdf_is_string(ctx, limit1) || 2 * (data.found_index+1) == pdf_array_len(ctx, limits)) + pdf_array_put(ctx, limits, 1, key); + + return data.entry; +} + +int pdf_add_portfolio_entry(fz_context *ctx, pdf_document *doc, + const char *name, int name_len, + const char *desc, int desc_len, + const char *filename, int filename_len, + const char *unifile, int unifile_len, fz_buffer *buf) +{ + int entry, len; + pdf_obj *ef, *f, *params, *s; + pdf_obj *key; + pdf_obj *val = NULL; + + fz_var(val); + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_add_portfolio_entry call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + key = pdf_new_string(ctx, doc, name, name_len); + fz_try(ctx) + { + val = pdf_new_dict(ctx, doc, 6); + pdf_dict_put_drop(ctx, val, PDF_NAME_CI, pdf_new_dict(ctx, doc, 4)); + pdf_dict_put_drop(ctx, val, PDF_NAME_EF, (ef = pdf_new_dict(ctx, doc, 4))); + pdf_dict_put_drop(ctx, val, PDF_NAME_F, pdf_new_string(ctx, doc, filename, filename_len)); + pdf_dict_put_drop(ctx, val, PDF_NAME_UF, pdf_new_string(ctx, doc, unifile, unifile_len)); + pdf_dict_put_drop(ctx, val, PDF_NAME_Desc, pdf_new_string(ctx, doc, desc, desc_len)); + pdf_dict_put_drop(ctx, val, PDF_NAME_Type, PDF_NAME_Filespec); + pdf_dict_put_drop(ctx, ef, PDF_NAME_F, (f = pdf_add_stream(ctx, doc, buf, NULL, 0))); + len = fz_buffer_storage(ctx, buf, NULL); + pdf_dict_put_drop(ctx, f, PDF_NAME_DL, pdf_new_int(ctx, doc, len)); + pdf_dict_put_drop(ctx, f, PDF_NAME_Length, pdf_new_int(ctx, doc, len)); + pdf_dict_put_drop(ctx, f, PDF_NAME_Params, (params = pdf_new_dict(ctx, doc, 4))); + pdf_dict_put_drop(ctx, params, PDF_NAME_Size, pdf_new_int(ctx, doc, len)); + + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Collection, NULL); + if (s == NULL) + { + s = pdf_new_dict(ctx, doc, 4); + pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), s, PDF_NAME_Root, PDF_NAME_Collection, NULL); + } + + s = pdf_dict_getl(ctx, pdf_trailer(ctx, doc), PDF_NAME_Root, PDF_NAME_Names, PDF_NAME_EmbeddedFiles, NULL); + if (s == NULL) + { + s = pdf_new_dict(ctx, doc, 4); + pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), s, PDF_NAME_Root, PDF_NAME_Names, PDF_NAME_EmbeddedFiles, NULL); + } + entry = pdf_name_tree_insert(ctx, doc, s, key, val); + } + fz_always(ctx) + { + pdf_drop_obj(ctx, key); + pdf_drop_obj(ctx, val); + } + fz_catch(ctx) + fz_rethrow(ctx); + + return entry; +} + +void pdf_set_portfolio_entry_info(fz_context *ctx, pdf_document *doc, int entry, int schema_entry, pdf_obj *data) +{ + pdf_portfolio *p; + pdf_obj *obj, *lookup; + int ef = 0; + + if (!doc) + fz_throw(ctx, FZ_ERROR_GENERIC, "Bad pdf_add_portfolio_entry call"); + + if (doc->portfolio == NULL) + load_portfolio(ctx, doc); + + obj = pdf_portfolio_entry_obj_name(ctx, doc, entry, NULL); + if (!obj) + fz_throw(ctx, FZ_ERROR_GENERIC, "Can't set info on non existent portfolio entry"); + + for (p = doc->portfolio; p != NULL && schema_entry > 0; p = p->next, schema_entry--); + + if (schema_entry) + fz_throw(ctx, FZ_ERROR_GENERIC, "schema_entry out of range"); + + switch (p->entry.type) + { + default: + case PDF_SCHEMA_TEXT: + case PDF_SCHEMA_DATE: + case PDF_SCHEMA_NUMBER: + lookup = NULL; + break; + case PDF_SCHEMA_FILENAME: + lookup = PDF_NAME_UF; + break; + case PDF_SCHEMA_DESC: + lookup = PDF_NAME_Desc; + break; + case PDF_SCHEMA_MODDATE: + lookup = PDF_NAME_ModDate; + ef = 1; + break; + case PDF_SCHEMA_CREATIONDATE: + lookup = PDF_NAME_CreationDate; + ef = 1; + break; + case PDF_SCHEMA_SIZE: + fz_throw(ctx, FZ_ERROR_GENERIC, "Can't set size!"); + break; + } + if (lookup) + { + if (ef) + obj = pdf_dict_getl(ctx, obj, PDF_NAME_EF, PDF_NAME_F, PDF_NAME_Params, NULL); + pdf_dict_put(ctx, obj, lookup, data); + if (lookup == PDF_NAME_UF) + pdf_dict_put(ctx, obj, PDF_NAME_F, data); + return; + } + pdf_dict_putl(ctx, obj, data, PDF_NAME_CI, p->key, NULL); +} + +void pdf_drop_portfolio(fz_context *ctx, pdf_document *doc) +{ + if (!doc) + return; + + while (doc->portfolio) + { + pdf_portfolio *p = doc->portfolio; + doc->portfolio = p->next; + + pdf_drop_obj(ctx, p->entry.name); + pdf_drop_obj(ctx, p->key); + pdf_drop_obj(ctx, p->val); + fz_free(ctx, p); + } +} diff --git a/source/pdf/pdf-xref.c b/source/pdf/pdf-xref.c index ccbe4cf6..9c9d2fde 100644 --- a/source/pdf/pdf-xref.c +++ b/source/pdf/pdf-xref.c @@ -1477,6 +1477,7 @@ pdf_drop_document_imp(fz_context *ctx, pdf_document *doc) fz_free(ctx, doc->type3_fonts); pdf_drop_ocg(ctx, doc); + pdf_drop_portfolio(ctx, doc); pdf_empty_store(ctx, doc); diff --git a/source/tools/mutool.c b/source/tools/mutool.c index 97c74fae..8cba3cff 100644 --- a/source/tools/mutool.c +++ b/source/tools/mutool.c @@ -20,6 +20,7 @@ int pdfshow_main(int argc, char *argv[]); int pdfpages_main(int argc, char *argv[]); int pdfcreate_main(int argc, char *argv[]); int pdfmerge_main(int argc, char *argv[]); +int pdfportfolio_main(int argc, char *argv[]); static struct { int (*func)(int argc, char *argv[]); @@ -40,6 +41,7 @@ static struct { { pdfshow_main, "show", "show internal pdf objects" }, { pdfcreate_main, "create", "create pdf document" }, { pdfmerge_main, "merge", "merge pages from multiple pdf sources into a new pdf" }, + { pdfportfolio_main, "portfolio", "manipulate PDF portfolios" }, #endif }; diff --git a/source/tools/pdfportfolio.c b/source/tools/pdfportfolio.c new file mode 100644 index 00000000..51b43ae3 --- /dev/null +++ b/source/tools/pdfportfolio.c @@ -0,0 +1,245 @@ +/* + * pdfportfolio -- manipulate embedded files in a PDF + */ + +#include "mupdf/pdf.h" + +static pdf_document *doc = NULL; +static fz_context *ctx = NULL; + +static void usage(void) +{ + fprintf(stderr, "usage: mutool portfolio [options] infile.pdf [actions]\n"); + fprintf(stderr, "\tOptions are:\n"); + fprintf(stderr, "\t-p -\tpassword\n"); + fprintf(stderr, "Actions are:\n"); + fprintf(stderr, "\tl\tlist embedded files\n"); + fprintf(stderr, "\tx N <filename>\n\t\textract Nth embedded file as <filename>\n"); + fprintf(stderr, "\te outfile.pdf <filename> <embed>\n\t\tembed <filename> as <embed>, saving the result as outfile.pdf\n"); + fprintf(stderr, "\nFor safety, keep all filenames as 7 bit clean for now.\n"); + exit(1); +} + +static void +safe_print_pdf_string(fz_context *ctx, unsigned char *str, int len) +{ + int c; + + if (len > 1 && str[0] == 0xFE && str[1] == 0xFF) + { + str += 2; + len -= 2; + while (len) + { + c = (*str++<<8); + c += *str++; + if (c >= 32 && c != 127 && c < 256) + fprintf(stderr, "%c", c); + else + fprintf(stderr, "<%04x>", c); + len -= 2; + }; + } + else + { + while (len) + { + c = *str++; + if (c >= 32 && c != 127 && c < 256) + fprintf(stderr, "%c", c); + else + fprintf(stderr, "<%02x>", c); + len--; + }; + } +} + +static void +safe_print_pdf_obj(fz_context *ctx, pdf_obj *obj, const char *dflt) +{ + if (obj == NULL) + fprintf(stderr, "%s", dflt); + else if (pdf_is_string(ctx, obj)) + safe_print_pdf_string(ctx, (unsigned char *)pdf_to_str_buf(ctx, obj), pdf_to_str_len(ctx, obj)); + else + pdf_print_obj(ctx, fz_stderr(ctx), obj, 1); +} + +int pdfportfolio_main(int argc, char **argv) +{ + char *infile; + char *password = ""; + int c; + int exit_code = 0; + + while ((c = fz_getopt(argc, argv, "p:")) != -1) + { + switch (c) + { + case 'p': password = fz_optarg; break; + default: usage(); break; + } + } + + if (fz_optind == argc) + usage(); + + infile = argv[fz_optind++]; + + ctx = fz_new_context(NULL, NULL, FZ_STORE_UNLIMITED); + if (!ctx) + { + fprintf(stderr, "cannot initialise context\n"); + exit(1); + } + + doc = pdf_open_document(ctx, infile); + if (pdf_needs_password(ctx, doc)) + if (!pdf_authenticate_password(ctx, doc, password)) + fz_throw(ctx, FZ_ERROR_GENERIC, "cannot authenticate password: %s", infile); + + if (fz_optind == argc) + usage(); + fz_optarg = argv[fz_optind++]; + if (*fz_optarg == 0 || (*fz_optarg != 'l' && *fz_optarg != 'x' && *fz_optarg != 'e') || fz_optarg[1] != 0) + usage(); + + fz_try(ctx) + { + switch (*fz_optarg) + { + case 'l': + { + /* List files */ + int m = pdf_count_portfolio_schema(ctx, doc); + int n = pdf_count_portfolio_entries(ctx, doc); + int i, j; + + for (i = 0; i < n; i++) + { + pdf_obj *name = pdf_portfolio_entry_name(ctx, doc, i); + + fprintf(stderr, " %s%d: ", i < 10 ? " " : "", i); + safe_print_pdf_obj(ctx, name, "(Unnamed)"); + fprintf(stderr, "\n"); + for (j = 0; j < m; j++) + { + pdf_portfolio_schema info; + pdf_obj *obj; + char *type; + + pdf_portfolio_schema_info(ctx, doc, j, &info); + obj = pdf_portfolio_entry_info(ctx, doc, i, j); + fprintf(stderr, " "); + safe_print_pdf_obj(ctx, info.name, "(Unnamed)"); + switch(info.type) + { + case PDF_SCHEMA_TEXT: + type = "T"; + break; + case PDF_SCHEMA_DATE: + type = "D"; + break; + case PDF_SCHEMA_NUMBER: + type = "N"; + break; + case PDF_SCHEMA_FILENAME: + type = "F"; + break; + case PDF_SCHEMA_DESC: + type = "E"; + break; + case PDF_SCHEMA_MODDATE: + type = "M"; + break; + case PDF_SCHEMA_CREATIONDATE: + type = "C"; + break; + case PDF_SCHEMA_SIZE: + type = "S"; + break; + default: + type = "?"; + break; + } + fprintf(stderr, ":%s:", type); + safe_print_pdf_obj(ctx, obj, ""); + if (info.editable) + fprintf(stderr, " (Editable)"); + if (info.visible) + fprintf(stderr, " (Visible)"); + fprintf(stderr, "\n"); + } + } + break; + } + case 'x': + { + int entry; + char *filename; + fz_buffer *buf; + unsigned char *data; + int len; + FILE *file; + + if (fz_optind > argc-2) + usage(); + + entry = fz_atoi(argv[fz_optind++]); + filename = argv[fz_optind++]; + + buf = pdf_portfolio_entry(ctx, doc, entry); + len = fz_buffer_storage(ctx, buf, &data); + + file = fopen(filename, "wb"); + if (file == NULL) + { + fprintf(stderr, "Failed to open '%s' for writing\n", filename); + exit(1); + } + fwrite(data, 1, len, file); + fclose(file); + fz_drop_buffer(ctx, buf); + break; + } + case 'e': + { + char *outfile; + char *filename; + char *ename; + fz_buffer *buf; + + if (fz_optind > argc-3) + usage(); + + outfile = argv[fz_optind++]; + filename = argv[fz_optind++]; + ename = argv[fz_optind++]; + + if (ename == NULL) + ename = filename; + + buf = fz_read_file(ctx, filename); + pdf_add_portfolio_entry(ctx, doc, + ename, strlen(ename), /* name */ + ename, strlen(ename), /* desc */ + ename, strlen(ename), /* filename */ + ename, strlen(ename), /* unifile */ + buf); + fz_drop_buffer(ctx, buf); + pdf_save_document(ctx, doc, outfile, NULL); + break; + } + } + } + fz_catch(ctx) + { + /* Swallow any errors */ + exit_code = 1; + } + + pdf_drop_document(ctx, doc); + fz_flush_warnings(ctx); + fz_drop_context(ctx); + return exit_code; +} |