summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--include/mupdf/pdf/document.h257
-rw-r--r--platform/win32/libmupdf.vcproj8
-rw-r--r--platform/win32/mutool.vcproj4
-rw-r--r--resources/pdf/names.txt10
-rw-r--r--source/pdf/pdf-imp.h2
-rw-r--r--source/pdf/pdf-portfolio.c740
-rw-r--r--source/pdf/pdf-xref.c1
-rw-r--r--source/tools/mutool.c2
-rw-r--r--source/tools/pdfportfolio.c245
10 files changed, 1270 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 7b7eccda..c3ff2482 100644
--- a/Makefile
+++ b/Makefile
@@ -270,7 +270,7 @@ $(OUT)/cmapdump.o : include/mupdf/pdf/cmap.h source/fitz/context.c source/fitz/e
MUTOOL := $(OUT)/mutool
MUTOOL_OBJ := $(addprefix $(OUT)/tools/, mutool.o muconvert.o mudraw.o murun.o)
-MUTOOL_OBJ += $(addprefix $(OUT)/tools/, pdfclean.o pdfcreate.o pdfextract.o pdfinfo.o pdfmerge.o pdfposter.o pdfpages.o pdfshow.o)
+MUTOOL_OBJ += $(addprefix $(OUT)/tools/, pdfclean.o pdfcreate.o pdfextract.o pdfinfo.o pdfmerge.o pdfposter.o pdfpages.o pdfshow.o pdfportfolio.o)
$(MUTOOL_OBJ): $(FITZ_HDR) $(PDF_HDR)
MUTOOL_LIB = $(OUT)/libmutools.a
$(MUTOOL_LIB) : $(MUTOOL_OBJ)
diff --git a/include/mupdf/pdf/document.h b/include/mupdf/pdf/document.h
index dc74ec7b..bf5d61b8 100644
--- a/include/mupdf/pdf/document.h
+++ b/include/mupdf/pdf/document.h
@@ -6,6 +6,7 @@ typedef struct pdf_lexbuf_large_s pdf_lexbuf_large;
typedef struct pdf_xref_s pdf_xref;
typedef struct pdf_crypt_s pdf_crypt;
typedef struct pdf_ocg_descriptor_s pdf_ocg_descriptor;
+typedef struct pdf_portfolio_s pdf_portfolio;
typedef struct pdf_page_s pdf_page;
typedef struct pdf_annot_s pdf_annot;
@@ -244,6 +245,261 @@ void pdf_layer_config_ui_info(fz_context *ctx, pdf_document *doc, int ui, pdf_la
void pdf_set_layer_config_as_default(fz_context *ctx, pdf_document *doc);
/*
+ PDF portfolios (or collections) are embedded files. They can
+ be thought of as tables of information, with an embedded
+ file per row. For instance a PDF portfolio of an email box might
+ contain:
+
+ From To Cc Date
+ message1.pdf ... ... ... ...
+ message2.pdf ... ... ... ...
+
+ etc. The details of the 'column headings' are known as the Schema.
+ This includes the order to use for the headings.
+
+ Each row in the table is a portfolio (or collection) entry.
+*/
+
+/*
+ pdf_count_portfolio_schema: Get the number of entries in the
+ portfolio schema used in this document.
+
+ doc: The document in question.
+*/
+int pdf_count_portfolio_schema(fz_context *ctx, pdf_document *doc);
+
+typedef enum
+{
+ PDF_SCHEMA_NUMBER,
+ PDF_SCHEMA_SIZE,
+ PDF_SCHEMA_TEXT,
+ PDF_SCHEMA_DATE,
+ PDF_SCHEMA_DESC,
+ PDF_SCHEMA_MODDATE,
+ PDF_SCHEMA_CREATIONDATE,
+ PDF_SCHEMA_FILENAME,
+ PDF_SCHEMA_UNKNOWN
+} pdf_portfolio_schema_type;
+
+typedef struct
+{
+ pdf_portfolio_schema_type type;
+ int visible;
+ int editable;
+ pdf_obj *name;
+} pdf_portfolio_schema;
+
+/*
+ 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_reorder_portfolio_schema: Reorder the portfolio schema.
+
+ 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 - the
+ position of the entry to move.
+
+ new_pos: A value in the 0..n-1 range, where n is the
+ value returned from pdf_count_portfolio_schema - the
+ position to move the entry to.
+*/
+void pdf_reorder_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, int new_pos);
+
+/*
+ pdf_rename_portfolio_schema: rename a given portfolio
+ schema entry.
+
+ doc: The document in question.
+
+ entry: The entry to renumber.
+
+ name: The new name for the portfolio schema
+
+ name_len: The byte length of the name.
+*/
+void pdf_rename_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, const char *name, int name_len);
+
+/*
+ pdf_delete_portfolio_schema: delete a given portfolio
+ schema entry.
+
+ doc: The document in question.
+
+ entry: The entry to delete.
+*/
+void pdf_delete_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry);
+
+/*
+ pdf_add_portfolio_schema: Add a new portfolio schema
+ entry.
+
+ doc: The document in question.
+
+ entry: The point in the ordering at which to insert the new
+ schema entry.
+
+ info: Details of the schema entry.
+*/
+void pdf_add_portfolio_schema(fz_context *ctx, pdf_document *doc, int entry, const pdf_portfolio_schema *info);
+
+/*
+ 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_portfolio_entry: Create a buffer containing
+ a decoded portfolio entry.
+
+ doc: The document in question.
+
+ entry: A value in the 0..m-1 range, where m is the
+ value returned from pdf_count_portfolio_entries.
+
+ Returns a buffer containing the decoded portfolio
+ entry. Ownership of the buffer passes to the caller.
+*/
+fz_buffer *pdf_portfolio_entry(fz_context *ctx, pdf_document *doc, int entry);
+
+/*
+ pdf_portfolio_entry_obj_name: Retrieve the object and
+ name of a given portfolio entry.
+
+ doc: The document in question.
+
+ entry: A value in the 0..m-1 range, where m is the
+ value returned from pdf_count_portfolio_entries.
+
+ name: Pointer to a place to store the pointer to the
+ object representing the name. This is a borrowed
+ reference - do not drop it.
+
+ Returns a pointer to the pdf_object representing the
+ object. This is a borrowed reference - do not drop
+ it.
+*/
+pdf_obj *pdf_portfolio_entry_obj_name(fz_context *ctx, pdf_document *doc, int entry, pdf_obj **name);
+
+/*
+ pdf_portfolio_entry_obj: Retrieve the object
+ representing a given portfolio entry.
+
+ doc: The document in question.
+
+ entry: A value in the 0..m-1 range, where m is the
+ value returned from pdf_count_portfolio_entries.
+
+ Returns a pointer to the pdf_object representing the
+ object. This is a borrowed reference - do not drop
+ it.
+*/
+pdf_obj *pdf_portfolio_entry_obj(fz_context *ctx, pdf_document *doc, int entry);
+
+/*
+ pdf_portfolio_entry_name: Retrieve the name of
+ a given portfolio entry.
+
+ doc: The document in question.
+
+ entry: A value in the 0..m-1 range, where m is the
+ value returned from pdf_count_portfolio_entries.
+
+ name: Pointer to a place to store the pointer to the
+ object representing the name. This is a borrowed
+ reference - do not drop it.
+
+ Returns a pointer to the pdf_object representing the
+ name of the entry. This is a borrowed reference - do not drop
+ it.
+*/
+pdf_obj *pdf_portfolio_entry_name(fz_context *ctx, pdf_document *doc, int entry);
+
+/*
+ pdf_portfolio_entry_info: Fetch information about a given
+ portfolio entry.
+
+ doc: The document in question.
+
+ entry: A value in the 0..m-1 range, where m is the
+ value returned from pdf_count_portfolio_entries.
+
+ info: Pointer to structure to fill in. Pointers within
+ this structure may be set to NULL if no information is
+ available.
+*/
+pdf_obj *pdf_portfolio_entry_info(fz_context *ctx, pdf_document *doc, int entry, int schema_entry);
+
+/*
+ pdf_add_portfolio_entry: Add a new portfolio entry.
+
+ doc: The document in question.
+
+ name: The name to use for this entry (as used in the
+ PDF name tree for the collection).
+
+ name_len: The byte length of name.
+
+ desc: The description to use for this entry (as used
+ in the 'Desc' entry in the Collection entry).
+
+ desc_len: The byte length of desc.
+
+ filename: The filename to use for this entry (as used
+ in the 'F' entry in the collection entry).
+
+ filename_len: The byte length of filename.
+
+ unifilename: The filename to use for this entry (as used
+ in the 'UF' entry in the collection entry).
+
+ unifilename_len: The byte length of unifilename.
+
+ buf: The buffer containing the embedded file to add.
+
+ Returns the entry number for this new 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);
+
+/*
+ pdf_set_portfolio_entry_info: Set part of the entry
+ information for a given portfolio entry.
+
+ doc: The document in question.
+
+ entry: The portfolio entry to set information for.
+ In the range 0..m-1, where m is the value returned
+ from pdf_count_portfolio_entries.
+
+ schema_entry: Which schema entry to set (in the
+ range 0..n-1, where n is the value returned from
+ pdf_count_portfolio_schema.
+
+ data: The value to set.
+*/
+void pdf_set_portfolio_entry_info(fz_context *ctx, pdf_document *doc, int entry, int schema_entry, pdf_obj *data);
+
+/*
pdf_update_page: update a page for the sake of changes caused by a call
to pdf_pass_event. pdf_update_page regenerates any appearance streams that
are out of date, checks for cases where different appearance streams
@@ -292,6 +548,7 @@ struct pdf_document_s
fz_off_t file_size;
pdf_crypt *crypt;
pdf_ocg_descriptor *ocg;
+ pdf_portfolio *portfolio;
pdf_hotspot hotspot;
int max_xref_len;
diff --git a/platform/win32/libmupdf.vcproj b/platform/win32/libmupdf.vcproj
index a6e83c6f..dace16a5 100644
--- a/platform/win32/libmupdf.vcproj
+++ b/platform/win32/libmupdf.vcproj
@@ -2111,6 +2111,10 @@
>
</File>
<File
+ RelativePath="..\..\source\pdf\pdf-portfolio.c"
+ >
+ </File>
+ <File
RelativePath="..\..\source\pdf\pdf-repair.c"
>
</File>
@@ -2426,6 +2430,10 @@
>
</File>
<File
+ RelativePath="..\..\include\mupdf\fitz\text-shaper.h"
+ >
+ </File>
+ <File
RelativePath="..\..\include\mupdf\fitz\text.h"
>
</File>
diff --git a/platform/win32/mutool.vcproj b/platform/win32/mutool.vcproj
index 5646725d..413beca8 100644
--- a/platform/win32/mutool.vcproj
+++ b/platform/win32/mutool.vcproj
@@ -1089,6 +1089,10 @@
>
</File>
<File
+ RelativePath="..\..\source\tools\pdfportfolio.c"
+ >
+ </File>
+ <File
RelativePath="..\..\source\tools\pdfposter.c"
>
</File>
diff --git a/resources/pdf/names.txt b/resources/pdf/names.txt
index 5a45a4b5..071e7f2e 100644
--- a/resources/pdf/names.txt
+++ b/resources/pdf/names.txt
@@ -54,6 +54,7 @@ CCF
CCITTFaxDecode
CF
CFM
+CI
CIDFontType0
CIDFontType0C
CIDFontType2
@@ -70,6 +71,7 @@ Catalog
Ch
CharProcs
Circle
+Collection
ColorSpace
ColorTransform
Colors
@@ -88,6 +90,7 @@ DA
DC
DCT
DCTDecode
+DL
DOS
DP
DR
@@ -98,6 +101,7 @@ DamagedRowsBeforeError
Decode
DecodeParms
Default
+Desc
DescendantFonts
Descent
Design
@@ -114,7 +118,9 @@ Dm
Domain
Dur
E
+EF
EarlyChange
+EmbeddedFiles
Encode
EncodedByteAlign
Encoding
@@ -134,6 +140,7 @@ Fade
Ff
Fields
FileAttachment
+Filespec
Filter
First
FirstChar
@@ -229,6 +236,7 @@ Matte
MaxLen
MediaBox
MissingWidth
+ModDate
Movie
N
Name
@@ -262,6 +270,7 @@ Page
PageMode
Pages
PaintType
+Params
Parent
Pattern
PatternType
@@ -296,6 +305,7 @@ RunLengthDecode
S
SMask
SMaskInData
+Schema
Screen
Separation
Shading
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;
+}