Skip to content

Commit

Permalink
Add Snapshot option to PDF saving.
Browse files Browse the repository at this point in the history
The purpose of a "snapshot" save is to allow us to dump the current
state of a PDF document (including edits, but excluding undo/redo
history) in such a way that the version in memory remains unchanged.

There are a couple of use cases for this:

1) Load a form, fill in some fields, print it. In order to do the
print, we need to save the document as a standard valid PDF to send
to a remote print service. After printing, if we then edit the
document some more and save it out, we only want to see 1 incremental
section used, rather than 2 (i.e. the saving for printing should not
cause the 'underlying' document to be updated).

2) When running as an app on a mobile device, when we are put into
the background, we need to save our state so that if the app is killed
and later restarted, we can pick up where we left off. Again this
should not involved writing a new incremental section to the document.

This commit solves for case #1.

Case #2 will require this, plus both the ability to save undo/redo
history, and the ability to 'reopen' the last incremental update.
  • Loading branch information
robinwatts committed May 21, 2021
1 parent 37a0c03 commit 75f22a9
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 13 deletions.
15 changes: 15 additions & 0 deletions include/mupdf/pdf/document.h
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,7 @@ typedef struct
int permissions; /* Document encryption permissions. */
char opwd_utf8[128]; /* Owner password. */
char upwd_utf8[128]; /* User password. */
int do_snapshot; /* Do not use directly. Use the snapshot functions. */
} pdf_write_options;

extern const pdf_write_options pdf_default_write_options;
Expand Down Expand Up @@ -596,6 +597,20 @@ void pdf_write_document(fz_context *ctx, pdf_document *doc, fz_output *out, cons
*/
void pdf_save_document(fz_context *ctx, pdf_document *doc, const char *filename, const pdf_write_options *opts);

/*
Snapshot the document to a file. This does not cause the
incremental xref to be finalized, so the document in memory
remains (essentially) unchanged.
*/
void pdf_save_snapshot(fz_context *ctx, pdf_document *doc, const char *filename);

/*
Snapshot the document to an output stream. This does not cause
the incremental xref to be finalized, so the document in memory
remains (essentially) unchanged.
*/
void pdf_write_snapshot(fz_context *ctx, pdf_document *doc, fz_output *out);

char *pdf_format_write_options(fz_context *ctx, char *buffer, size_t buffer_len, const pdf_write_options *opts);

/*
Expand Down
23 changes: 17 additions & 6 deletions platform/gl/gl-annotate.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ static struct input ocr_language_input;
static int ocr_language_input_initialised = 0;
static pdf_document *pdf_has_redactions_doc = NULL;
static int pdf_has_redactions;
static int do_snapshot;

static int pdf_filter(const char *fn)
{
Expand Down Expand Up @@ -73,6 +74,10 @@ static void save_pdf_options(void)

can_be_incremental = pdf_can_be_saved_incrementally(ctx, pdf);

ui_checkbox("Snapshot", &do_snapshot);
if (do_snapshot)
return; /* ignore normal PDF options */

ui_checkbox("High Security", &do_high_security);
if (do_high_security)
{
Expand Down Expand Up @@ -102,8 +107,7 @@ static void save_pdf_options(void)
return; /* ignore normal PDF options */
}

if (can_be_incremental)
ui_checkbox("Incremental", &save_opts.do_incremental);
ui_checkbox_aux("Incremental", &save_opts.do_incremental, !can_be_incremental);

fz_try(ctx)
{
Expand Down Expand Up @@ -353,8 +357,10 @@ static void save_high_security(void)
static void do_save_pdf_dialog(int for_signing)
{
if (ui_save_file(save_filename, save_pdf_options,
do_snapshot ?
"Select where to save the snapshot:" :
do_high_security ?
"Select where to save the redacted document" :
"Select where to save the redacted document:" :
for_signing ?
"Select where to save the signed document:" :
"Select where to save the document:"))
Expand All @@ -376,9 +382,14 @@ static void do_save_pdf_dialog(int for_signing)
static char opts_string[4096];
pdf_format_write_options(ctx, opts_string, sizeof(opts_string), &save_opts);
trace_action("doc.save(%q,%q);\n", save_filename, opts_string);
pdf_save_document(ctx, pdf, save_filename, &save_opts);
fz_strlcpy(filename, save_filename, PATH_MAX);
reload_document();
if (do_snapshot)
pdf_save_snapshot(ctx, pdf, save_filename);
else
{
pdf_save_document(ctx, pdf, save_filename, &save_opts);
fz_strlcpy(filename, save_filename, PATH_MAX);
reload_document();
}
}
fz_catch(ctx)
{
Expand Down
98 changes: 91 additions & 7 deletions source/pdf/pdf-write.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ typedef struct
int do_clean;
int do_encrypt;
int dont_regenerate_id;
int do_snapshot;

int list_len;
int *use_list;
Expand Down Expand Up @@ -2216,7 +2217,8 @@ static void writexref(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
trailer = pdf_keep_obj(ctx, pdf_trailer(ctx, doc));
pdf_dict_put_int(ctx, trailer, PDF_NAME(Size), pdf_xref_len(ctx, doc));
pdf_dict_put_int(ctx, trailer, PDF_NAME(Prev), doc->startxref);
doc->startxref = startxref;
if (!opts->do_snapshot)
doc->startxref = startxref;
}
else
{
Expand Down Expand Up @@ -2329,7 +2331,8 @@ static void writexrefstream(fz_context *ctx, pdf_document *doc, pdf_write_state
if (opts->do_incremental)
{
pdf_dict_put_int(ctx, dict, PDF_NAME(Prev), doc->startxref);
doc->startxref = startxref;
if (!opts->do_snapshot)
doc->startxref = startxref;
}
else
{
Expand Down Expand Up @@ -2383,6 +2386,9 @@ static void writexrefstream(fz_context *ctx, pdf_document *doc, pdf_write_state

writeobject(ctx, doc, opts, num, 0, 0, 1);
fz_write_printf(ctx, opts->out, "startxref\n%lu\n%%%%EOF\n", startxref);

if (opts->do_snapshot)
pdf_delete_object(ctx, doc, num);
}
fz_always(ctx)
{
Expand Down Expand Up @@ -3048,6 +3054,7 @@ static void initialise_write_state(fz_context *ctx, pdf_document *doc, const pdf
opts->do_compress = in_opts->do_compress;
opts->do_compress_images = in_opts->do_compress_images;
opts->do_compress_fonts = in_opts->do_compress_fonts;
opts->do_snapshot = in_opts->do_snapshot;

opts->do_garbage = in_opts->do_garbage;
opts->do_linear = in_opts->do_linear;
Expand Down Expand Up @@ -3112,6 +3119,28 @@ const pdf_write_options pdf_default_write_options = {
~0, /* permissions */
"", /* opwd_utf8[128] */
"", /* upwd_utf8[128] */
0 /* do_snapshot */
};

static const pdf_write_options pdf_snapshot_write_options = {
1, /* do_incremental */
0, /* do_pretty */
0, /* do_ascii */
0, /* do_compress */
0, /* do_compress_images */
0, /* do_compress_fonts */
0, /* do_decompress */
0, /* do_garbage */
0, /* do_linear */
0, /* do_clean */
0, /* do_sanitize */
0, /* do_appearance */
0, /* do_encrypt */
1, /* dont_regenerate_id */
~0, /* permissions */
"", /* opwd_utf8[128] */
"", /* upwd_utf8[128] */
1 /* do_snapshot */
};

const char *fz_pdf_write_options_usage =
Expand Down Expand Up @@ -3242,7 +3271,8 @@ prepare_for_save(fz_context *ctx, pdf_document *doc, const pdf_write_options *in
the signature dictionary is updated. */
doc->save_in_progress = 1;

presize_unsaved_signature_byteranges(ctx, doc);
if (!in_opts->do_snapshot)
presize_unsaved_signature_byteranges(ctx, doc);
}

static pdf_obj *
Expand Down Expand Up @@ -3443,6 +3473,7 @@ do_pdf_save_document(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
/* Remove encryption dictionary if saving without encryption. */
if (opts->do_encrypt == PDF_ENCRYPT_NONE)
{
assert(!in_opts->do_snapshot);
pdf_dict_del(ctx, pdf_trailer(ctx, doc), PDF_NAME(Encrypt));
}

Expand All @@ -3455,6 +3486,7 @@ do_pdf_save_document(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
/* Create encryption dictionary if saving with new encryption. */
else
{
assert(!opts->do_snapshot);
if (!id)
id = new_identity(ctx, doc);
id1 = pdf_array_get(ctx, id, 0);
Expand All @@ -3465,6 +3497,11 @@ do_pdf_save_document(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
/* Stash Encrypt entry in the writer state, in case a repair pass throws away the old trailer. */
opts->crypt_obj = pdf_keep_obj(ctx, pdf_dict_get(ctx, pdf_trailer(ctx, doc), PDF_NAME(Encrypt)));

/* If we're writing a snapshot, we can't be doing garbage
* collection, or linearisation, and must be writing
* incrementally. */
assert(!opts->do_snapshot || (opts->do_garbage == 0 && !opts->do_linear));

/* Make sure any objects hidden in compressed streams have been loaded */
if (!opts->do_incremental)
{
Expand Down Expand Up @@ -3506,7 +3543,8 @@ do_pdf_save_document(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
renumberobjs(ctx, doc, opts);

/* Truncate the xref after compacting and renumbering */
if ((opts->do_garbage >= 2 || opts->do_linear) && !opts->do_incremental)
if ((opts->do_garbage >= 2 || opts->do_linear) &&
!opts->do_incremental)
{
xref_len = pdf_xref_len(ctx, doc); /* May have changed due to repair */
expand_lists(ctx, opts, xref_len);
Expand Down Expand Up @@ -3606,9 +3644,11 @@ do_pdf_save_document(fz_context *ctx, pdf_document *doc, pdf_write_state *opts,
doc->xref_sections[0].end_ofs = fz_tell_output(ctx, opts->out);
}

complete_signatures(ctx, doc, opts);

doc->dirty = 0;
if (!in_opts->do_snapshot)
{
complete_signatures(ctx, doc, opts);
doc->dirty = 0;
}
}
fz_always(ctx)
{
Expand Down Expand Up @@ -3660,6 +3700,23 @@ void pdf_write_document(fz_context *ctx, pdf_document *doc, fz_output *out, cons
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't do incremental writes with linearisation");
if (in_opts->do_incremental && in_opts->do_encrypt != PDF_ENCRYPT_KEEP)
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't do incremental writes when changing encryption");
if (in_opts->do_snapshot)
{
if (in_opts->do_incremental == 0 ||
in_opts->do_pretty ||
in_opts->do_ascii ||
in_opts->do_compress ||
in_opts->do_compress_images ||
in_opts->do_compress_fonts ||
in_opts->do_decompress ||
in_opts->do_garbage ||
in_opts->do_linear ||
in_opts->do_clean ||
in_opts->do_sanitize ||
in_opts->do_appearance ||
in_opts->do_encrypt != PDF_ENCRYPT_KEEP)
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't use these options when snapshotting!");
}
if (pdf_has_unsaved_sigs(ctx, doc) && !fz_output_supports_stream(ctx, out))
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't write pdf that has unsaved sigs to a fz_output unless it supports fz_stream_from_output!");

Expand Down Expand Up @@ -3691,6 +3748,23 @@ void pdf_save_document(fz_context *ctx, pdf_document *doc, const char *filename,
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't do incremental writes with linearisation");
if (in_opts->do_incremental && in_opts->do_encrypt != PDF_ENCRYPT_KEEP)
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't do incremental writes when changing encryption");
if (in_opts->do_snapshot)
{
if (in_opts->do_incremental == 0 ||
in_opts->do_pretty ||
in_opts->do_ascii ||
in_opts->do_compress ||
in_opts->do_compress_images ||
in_opts->do_compress_fonts ||
in_opts->do_decompress ||
in_opts->do_garbage ||
in_opts->do_linear ||
in_opts->do_clean ||
in_opts->do_sanitize ||
in_opts->do_appearance ||
in_opts->do_encrypt != PDF_ENCRYPT_KEEP)
fz_throw(ctx, FZ_ERROR_GENERIC, "Can't use these options when snapshotting!");
}

if (in_opts->do_appearance > 0)
{
Expand Down Expand Up @@ -3743,6 +3817,16 @@ void pdf_save_document(fz_context *ctx, pdf_document *doc, const char *filename,
}
}

void pdf_save_snapshot(fz_context *ctx, pdf_document *doc, const char *filename)
{
pdf_save_document(ctx, doc, filename, &pdf_snapshot_write_options);
}

void pdf_write_snapshot(fz_context *ctx, pdf_document *doc, fz_output *out)
{
pdf_write_document(ctx, doc, out, &pdf_snapshot_write_options);
}

char *
pdf_format_write_options(fz_context *ctx, char *buffer, size_t buffer_len, const pdf_write_options *opts)
{
Expand Down

0 comments on commit 75f22a9

Please sign in to comment.