diff options
Diffstat (limited to 'pdf/pdf_form.c')
-rw-r--r-- | pdf/pdf_form.c | 1525 |
1 files changed, 1525 insertions, 0 deletions
diff --git a/pdf/pdf_form.c b/pdf/pdf_form.c new file mode 100644 index 00000000..a517c339 --- /dev/null +++ b/pdf/pdf_form.c @@ -0,0 +1,1525 @@ +#include "fitz-internal.h" +#include "mupdf-internal.h" + +#define MEASURE_SCALE (10.0) +#define MATRIX_COEFS (6) + +enum +{ + Ff_NoToggleToOff = 1 << (15-1), + Ff_Radio = 1 << (16-1), + Ff_Pushbutton = 1 << (17-1), + Ff_RadioInUnison = 1 << (26-1), + Ff_Combo = 1 << (18-1) +}; + +enum +{ + BS_Solid, + BS_Dashed, + BS_Beveled, + BS_Inset, + BS_Underline +}; + +enum +{ + Q_Left = 0, + Q_Cent = 1, + Q_Right = 2 +}; + +struct fz_widget_s +{ + pdf_document *doc; + int type; + pdf_obj *obj; +}; + +struct fz_widget_text_s +{ + fz_widget super; + char *text; +}; + +typedef struct da_parse_state_s +{ + char *name; + float stack[32]; + int top; + char *font_name; + int font_size; + float col[4]; + int col_size; +} da_parse_state; + +static const char *fmt_re = "%f %f %f %f re\n"; +static const char *fmt_f = "f\n"; +static const char *fmt_s = "s\n"; +static const char *fmt_g = "%f g\n"; +static const char *fmt_m = "%f %f m\n"; +static const char *fmt_l = "%f %f l\n"; +static const char *fmt_w = "%f w\n"; +static const char *fmt_Tx_BMC = "/Tx BMC\n"; +static const char *fmt_q = "q\n"; +static const char *fmt_W = "W\n"; +static const char *fmt_n = "n\n"; +static const char *fmt_BT = "BT\n"; +static const char *fmt_Tm = "%1.2f %1.2f %1.2f %1.2f %1.2f %1.2f Tm\n"; +static const char *fmt_Tj = "(%s) Tj\n"; +static const char *fmt_ET = "ET\n"; +static const char *fmt_Q = "Q\n"; +static const char *fmt_EMC = "EMC\n"; + +static fz_buffer *form_contents(pdf_document *doc, pdf_xobject *form) +{ + return pdf_get_stream(doc, pdf_to_num(form->contents)); +} + +static void account_for_rot(fz_rect *rect, fz_matrix *mat, int rot) +{ + float width = rect->x1; + float height = rect->y1; + + switch (rot) + { + default: + *mat = fz_identity; + break; + case 90: + *mat = fz_concat(fz_rotate(rot), fz_translate(width, 0)); + rect->x1 = height; + rect->y1 = width; + break; + case 180: + *mat = fz_concat(fz_rotate(rot), fz_translate(width, height)); + break; + case 270: + *mat = fz_concat(fz_rotate(rot), fz_translate(0, height)); + rect->x1 = height; + rect->y1 = width; + break; + } +} + +static pdf_obj *get_inheritable(pdf_document *doc, pdf_obj *obj, char *key) +{ + pdf_obj *fobj = NULL; + + while (!fobj && obj) + { + fobj = pdf_dict_gets(obj, key); + + if (!fobj) + obj = pdf_dict_gets(obj, "Parent"); + } + + return fobj ? fobj + : pdf_dict_gets(pdf_dict_gets(pdf_dict_gets(doc->trailer, "Root"), "AcroForm"), key); +} + +static char *get_string_or_stream(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + int len = 0; + char *buf = NULL; + fz_buffer *strmbuf = NULL; + char *text = NULL; + + fz_var(strmbuf); + fz_var(text); + fz_try(ctx) + { + if (pdf_is_string(obj)) + { + len = pdf_to_str_len(obj); + buf = pdf_to_str_buf(obj); + } + else if (pdf_is_stream(doc, pdf_to_num(obj), pdf_to_gen(obj))) + { + strmbuf = pdf_load_stream(doc, pdf_to_num(obj), pdf_to_gen(obj)); + len = fz_buffer_storage(ctx, strmbuf, &buf); + } + + if (buf) + { + text = fz_malloc(ctx, len+1); + memcpy(text, buf, len); + text[len] = 0; + } + } + fz_always(ctx) + { + fz_drop_buffer(ctx, strmbuf); + } + fz_catch(ctx) + { + fz_free(ctx, text); + fz_rethrow(ctx); + } + + return text; +} + +static char *get_field_type_name(pdf_document *doc, pdf_obj *obj) +{ + pdf_obj *type = get_inheritable(doc, obj, "FT"); + + return type ? pdf_to_name(type) + : NULL; +} + +static int get_field_flags(pdf_document *doc, pdf_obj *obj) +{ + pdf_obj *flags = get_inheritable(doc, obj, "Ff"); + + return flags ? pdf_to_int(flags) + : 0; +} + +static int get_field_type(pdf_document *doc, pdf_obj *obj) +{ + char *type = get_field_type_name(doc, obj); + int flags = get_field_flags(doc, obj); + + if (!strcmp(type, "Btn")) + { + if (flags & Ff_Pushbutton) + return FZ_WIDGET_TYPE_PUSHBUTTON; + else if (flags & Ff_Radio) + return FZ_WIDGET_TYPE_RADIOBUTTON; + else + return FZ_WIDGET_TYPE_CHECKBOX; + } + else if (!strcmp(type, "Tx")) + return FZ_WIDGET_TYPE_TEXT; + else if (!strcmp(type, "Ch")) + { + if (flags & Ff_Combo) + return FZ_WIDGET_TYPE_COMBOBOX; + else + return FZ_WIDGET_TYPE_LISTBOX; + } + else + return -1; +} + +static void pdf_field_mark_dirty(fz_context *ctx, pdf_obj *field) +{ + if (!pdf_dict_gets(field, "Dirty")) + { + pdf_obj *nullobj = pdf_new_null(ctx); + fz_try(ctx) + { + pdf_dict_puts(field, "Dirty", nullobj); + } + fz_always(ctx) + { + pdf_drop_obj(nullobj); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } +} + +static void copy_resources(pdf_obj *dst, pdf_obj *src) +{ + int i, len; + + len = pdf_dict_len(src); + for (i = 0; i < len; i++) + { + pdf_obj *key = pdf_dict_get_key(src, i); + + if (!pdf_dict_get(dst, key)) + fz_dict_put(dst, key, pdf_dict_get_val(src, i)); + } +} + +static fz_widget *new_widget(pdf_document *doc, pdf_obj *obj) +{ + fz_widget *widget = NULL; + + fz_try(doc->ctx) + { + int type = get_field_type(doc, obj); + + switch(type) + { + case FZ_WIDGET_TYPE_TEXT: + widget = &(fz_malloc_struct(doc->ctx, fz_widget_text)->super); + break; + default: + widget = fz_malloc_struct(doc->ctx, fz_widget); + break; + } + + widget->doc = doc; + widget->type = type; + widget->obj = pdf_keep_obj(obj); + } + fz_catch(doc->ctx) + { + fz_warn(doc->ctx, "failed to load foccussed widget"); + } + + return widget; +} + +static int read_font_size_from_da(fz_context *ctx, char *da) +{ + int tok, fontsize = 0; + pdf_lexbuf lbuf; + fz_stream *str = fz_open_memory(ctx, da, strlen(da)); + + memset(lbuf.scratch, 0, sizeof(lbuf.scratch)); + lbuf.size = sizeof(lbuf.scratch); + fz_try(ctx) + { + int last_tok_was_int = 0; + int last_int_tok_val = 0; + + for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf)) + { + if (last_tok_was_int) + { + if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "Tf")) + fontsize = last_int_tok_val; + + last_tok_was_int = 0; + } + + if (tok == PDF_TOK_INT) + { + last_tok_was_int = 1; + last_int_tok_val = lbuf.i; + } + } + } + fz_always(ctx) + { + fz_close(str); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + return fontsize; +} + +static void da_init(fz_context *ctx, da_parse_state *da_state) +{ + da_state->name = NULL; + da_state->top = 0; + da_state->font_name = NULL; + da_state->font_size = 0; + da_state->col_size = 0; +} + +static void da_fin(fz_context *ctx, da_parse_state *da_state) +{ + fz_free(ctx, da_state->name); + da_state->name = NULL; + fz_free(ctx, da_state->font_name); + da_state->font_name = NULL; +} + +static void da_reset(fz_context *ctx, da_parse_state *da_state) +{ + fz_free(ctx, da_state->name); + da_state->name = NULL; + da_state->top = 0; +} + +static void da_check_stack(da_parse_state *da_state) +{ + if (da_state->top == 32) + { + memmove(da_state->stack, da_state->stack + 1, + 31 * sizeof(da_state->stack[0])); + da_state->top = 31; + } +} + +static void parse_da(fz_context *ctx, char *da, da_parse_state *da_state) +{ + int tok; + pdf_lexbuf lbuf; + fz_stream *str = fz_open_memory(ctx, da, strlen(da)); + + memset(lbuf.scratch, 0, sizeof(lbuf.scratch)); + lbuf.size = sizeof(lbuf.scratch); + fz_try(ctx) + { + for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf)) + { + switch (tok) + { + case PDF_TOK_NAME: + fz_free(ctx, da_state->name); + da_state->name = fz_strdup(ctx, lbuf.scratch); + break; + + case PDF_TOK_INT: + da_check_stack(da_state); + da_state->stack[da_state->top] = lbuf.i; + da_state->top ++; + break; + + case PDF_TOK_REAL: + da_check_stack(da_state); + da_state->stack[da_state->top] = lbuf.f; + da_state->top ++; + break; + + case PDF_TOK_KEYWORD: + if (!strcmp(lbuf.scratch, "Tf")) + { + da_state->font_size = da_state->stack[0]; + da_state->font_name = da_state->name; + da_state->name = NULL; + } + else if (!strcmp(lbuf.scratch, "rg")) + { + da_state->col[0] = da_state->stack[0]; + da_state->col[1] = da_state->stack[1]; + da_state->col[2] = da_state->stack[2]; + da_state->col_size = 3; + } + + da_reset(ctx, da_state); + break; + } + } + } + fz_always(ctx) + { + fz_close(str); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void fzbuf_print_da(fz_context *ctx, fz_buffer *fzbuf, da_parse_state *da) +{ + if (da->font_name != NULL && da->font_size != 0) + fz_buffer_printf(ctx, fzbuf, "/%s %d Tf", da->font_name, da->font_size); + + if (da->col_size != 0) + fz_buffer_printf(ctx, fzbuf, " %f %f %f rg", da->col[0], da->col[1], da->col[2]); +} + +static void copy_da_with_altered_size(fz_context *ctx, fz_buffer *fzbuf, char *da, int size) +{ + int tok; + pdf_lexbuf lbuf; + fz_stream *str = fz_open_memory(ctx, da, strlen(da)); + + memset(lbuf.scratch, 0, sizeof(lbuf.scratch)); + lbuf.size = sizeof(lbuf.scratch); + fz_try(ctx) + { + int last_tok_was_int = 0; + int last_int_tok_val = 0; + + for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf)) + { + if (last_tok_was_int) + { + if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "Tf")) + fz_buffer_printf(ctx, fzbuf, " %d", size); + else + fz_buffer_printf(ctx, fzbuf, " %d", last_int_tok_val); + + last_tok_was_int = 0; + } + + if (tok == PDF_TOK_INT) + { + last_tok_was_int = 1; + last_int_tok_val = lbuf.i; + } + else + { + fz_buffer_printf(ctx, fzbuf, " "); + pdf_print_token(ctx, fzbuf, tok, &lbuf); + } + } + } + fz_always(ctx) + { + fz_close(str); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static fz_rect measure_text(pdf_document *doc, pdf_obj *dr, fz_buffer *fzbuf) +{ + fz_context *ctx = doc->ctx; + fz_device *dev = NULL; + fz_bbox bbox = fz_empty_bbox; + fz_rect rect; + + fz_try(ctx) + { + dev = fz_new_bbox_device(doc->ctx, &bbox); + pdf_run_glyph(doc, dr, fzbuf, dev, fz_scale(MEASURE_SCALE, MEASURE_SCALE), NULL); + } + fz_always(ctx) + { + fz_free_device(dev); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + rect.x0 = bbox.x0 / MEASURE_SCALE; + rect.x1 = bbox.x1 / MEASURE_SCALE; + rect.y0 = bbox.y0 / MEASURE_SCALE; + rect.y1 = bbox.y1 / MEASURE_SCALE; + + return rect; +} + +static void fzbuf_print_text(fz_context *ctx, fz_buffer *fzbuf, fz_rect *clip, char *da, int fontsize, fz_matrix *tm, char *text) +{ + fz_buffer_printf(ctx, fzbuf, fmt_q); + if (clip) + { + fz_buffer_printf(ctx, fzbuf, fmt_re, clip->x0, clip->y0, clip->x1 - clip->x0, clip->y1 - clip->y0); + fz_buffer_printf(ctx, fzbuf, fmt_W); + fz_buffer_printf(ctx, fzbuf, fmt_n); + } + + fz_buffer_printf(ctx, fzbuf, fmt_BT); + + if (fontsize > 0) + copy_da_with_altered_size(ctx, fzbuf, da, fontsize); + else + fz_buffer_printf(ctx, fzbuf, "%s\n", da); + + fz_buffer_printf(ctx, fzbuf, "\n"); + if (tm) + fz_buffer_printf(ctx, fzbuf, fmt_Tm, tm->a, tm->b, tm->c, tm->d, tm->e, tm->f); + + fz_buffer_printf(ctx, fzbuf, fmt_Tj, text); + fz_buffer_printf(ctx, fzbuf, fmt_ET); + fz_buffer_printf(ctx, fzbuf, fmt_Q); +} + +static fz_buffer *create_text_buffer(fz_context *ctx, fz_rect *clip, char *da, int fontsize, fz_matrix *tm, char *text) +{ + fz_buffer *fzbuf = fz_new_buffer(ctx, 0); + + fz_try(ctx) + { + fz_buffer_printf(ctx, fzbuf, fmt_Tx_BMC); + fzbuf_print_text(ctx, fzbuf, clip, da, fontsize, tm, text); + fz_buffer_printf(ctx, fzbuf, fmt_EMC); + } + fz_catch(ctx) + { + fz_drop_buffer(ctx, fzbuf); + fz_rethrow(ctx); + } + + return fzbuf; +} + +static fz_buffer *create_aligned_text_buffer(pdf_document *doc, fz_rect *clip, pdf_obj *dr, char *da, int fontsize, fz_matrix *tm, int q, char *text) +{ + fz_context *ctx = doc->ctx; + fz_buffer *fzbuf = create_text_buffer(ctx, clip, da, fontsize, tm, text); + + if (q != Q_Left) + { + fz_matrix atm = *tm; + fz_rect rect = measure_text(doc, dr, fzbuf); + + atm.e -= q == Q_Right ? (rect.x1 - rect.x0) + : (rect.x1 - rect.x0) / 2; + + fz_drop_buffer(ctx, fzbuf); + fzbuf = create_text_buffer(ctx, clip, da, fontsize, &atm, text); + } + + return fzbuf; +} + +static void measure_ascent_descent(pdf_document *doc, pdf_obj *dr, char *da, char *text, float *ascent, float *descent) +{ + fz_context *ctx = doc->ctx; + char *testtext = NULL; + fz_buffer *fzbuf = NULL; + fz_rect bbox; + + fz_var(testtext); + fz_var(fzbuf); + fz_try(ctx) + { + /* Heuristic: adding "My" to text will in most cases + * produce a measurement that will encompass all chars */ + testtext = fz_malloc(ctx, strlen(text) + 3); + strcpy(testtext, "My"); + strcat(testtext, text); + /* Use large font size for increased accuracy */ + fzbuf = create_text_buffer(ctx, NULL, da, 10, &fz_identity, testtext); + bbox = measure_text(doc, dr, fzbuf); + *descent = -bbox.y0 / 10.0; + *ascent = bbox.y1 / 10.0; + } + fz_always(ctx) + { + fz_drop_buffer(ctx, fzbuf); + fz_free(ctx, testtext); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +fz_buffer *create_text_appearance(pdf_document *doc, fz_rect *bbox, fz_matrix *oldtm, int q, pdf_obj *dr, char *da, char *text) +{ + fz_context *ctx = doc->ctx; + int fontsize, da_fontsize; + float height, width; + fz_buffer *fzbuf = NULL; + fz_rect rect; + fz_rect tbox; + rect = *bbox; + + if (rect.x1 - rect.x0 >= 2.0 && rect.y1 - rect.y0 >= 2.0) + { + rect.x0 += 1.0; + rect.x1 -= 1.0; + rect.y0 += 1.0; + rect.y1 -= 1.0; + } + + height = MAX(bbox->y1 - bbox->y0 - 4.0, 1); + width = MAX(bbox->x1 - bbox->x0 - 4.0, 1); + + fz_var(fzbuf); + fz_try(ctx) + { + float ascent, descent; + fz_matrix tm; + + da_fontsize = read_font_size_from_da(ctx, da); + fontsize = da_fontsize ? da_fontsize : floor(height); + + if (oldtm) + { + tm = *oldtm; + } + else + { + measure_ascent_descent(doc, dr, da, text, &ascent, &descent); + tm = fz_identity; + tm.e = 2.0; + tm.f = 2.0 + fontsize * descent; + + switch(q) + { + case Q_Right: tm.e += width; break; + case Q_Cent: tm.e += width/2; break; + } + } + + fzbuf = create_aligned_text_buffer(doc, &rect, dr, da, fontsize, &tm, q, text); + + if (!da_fontsize) + { + tbox = measure_text(doc, dr, fzbuf); + + if (tbox.x1 - tbox.x0 > width) + { + /* Text doesn't fit. Regenerate with a calculated font size */ + fz_drop_buffer(ctx, fzbuf); + fzbuf = NULL; + /* Scale the text to fit but use the same offset + * to keep the baseline constant */ + tm.a *= width / (tbox.x1 - tbox.x0); + tm.d *= width / (tbox.x1 - tbox.x0); + fzbuf = create_aligned_text_buffer(doc, &rect, dr, da, fontsize, &tm, q, text); + } + } + } + fz_catch(ctx) + { + fz_drop_buffer(ctx, fzbuf); + fz_rethrow(ctx); + } + + return fzbuf; +} + +static void update_marked_content(pdf_document *doc, pdf_xobject *form, fz_buffer *fzbuf) +{ + fz_context *ctx = doc->ctx; + int tok; + pdf_lexbuf lbuf; + fz_stream *str_outer = NULL; + fz_stream *str_inner = NULL; + unsigned char *buf; + int len; + fz_buffer *newbuf = NULL; + + memset(lbuf.scratch, 0, sizeof(lbuf.scratch)); + lbuf.size = sizeof(lbuf.scratch); + + fz_var(str_outer); + fz_var(str_inner); + fz_var(newbuf); + fz_try(ctx) + { + int bmc_found; + int first = 1; + + newbuf = fz_new_buffer(ctx, 0); + len = fz_buffer_storage(ctx, form_contents(doc, form), &buf); + str_outer = fz_open_memory(ctx, buf, len); + len = fz_buffer_storage(ctx, fzbuf, &buf); + str_inner = fz_open_memory(ctx, buf, len); + + /* Copy the existing appearance stream to newbuf while looking for BMC */ + for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf)) + { + if (first) + first = 0; + else + fz_buffer_printf(ctx, newbuf, " "); + + pdf_print_token(ctx, newbuf, tok, &lbuf); + if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "BMC")) + break; + } + + bmc_found = (tok != PDF_TOK_EOF); + + if (bmc_found) + { + /* Drop Tx BMC from the replacement appearance stream */ + (void)pdf_lex(str_inner, &lbuf); + (void)pdf_lex(str_inner, &lbuf); + } + + /* Copy the replacement appearance stream to newbuf */ + for (tok = pdf_lex(str_inner, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_inner, &lbuf)) + { + fz_buffer_printf(ctx, newbuf, " "); + pdf_print_token(ctx, newbuf, tok, &lbuf); + } + + if (bmc_found) + { + /* Drop the rest of the existing appearance stream until EMC found */ + for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf)) + { + if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "EMC")) + break; + } + + /* Copy the rest of the existing appearance stream to newbuf */ + for (tok = pdf_lex(str_outer, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str_outer, &lbuf)) + { + fz_buffer_printf(ctx, newbuf, " "); + pdf_print_token(ctx, newbuf, tok, &lbuf); + } + } + + /* Use newbuf in place of the existing appearance stream */ + pdf_xobject_set_contents(doc, form, newbuf); + } + fz_always(ctx) + { + fz_close(str_outer); + fz_close(str_inner); + fz_drop_buffer(ctx, newbuf); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +int get_matrix(pdf_document *doc, pdf_xobject *form, int q, fz_matrix *mt) +{ + fz_context *ctx = doc->ctx; + int found = 0; + unsigned char *buf; + int bufsize; + pdf_lexbuf lbuf; + fz_stream *str; + + bufsize = fz_buffer_storage(ctx, form_contents(doc, form), &buf); + str = fz_open_memory(ctx, buf, bufsize); + + memset(lbuf.scratch, 0, sizeof(lbuf.scratch)); + lbuf.size = sizeof(lbuf.scratch); + + fz_try(ctx) + { + int tok; + float coefs[MATRIX_COEFS]; + int coef_i = 0; + + /* Look for the text matrix Tm in the stream */ + for (tok = pdf_lex(str, &lbuf); tok != PDF_TOK_EOF; tok = pdf_lex(str, &lbuf)) + { + if (tok == PDF_TOK_INT || tok == PDF_TOK_REAL) + { + if (coef_i >= MATRIX_COEFS) + { + int i; + for (i = 0; i < MATRIX_COEFS-1; i++) + coefs[i] = coefs[i+1]; + + coef_i = MATRIX_COEFS-1; + } + + coefs[coef_i++] = tok == PDF_TOK_INT ? lbuf.i + : lbuf.f; + } + else + { + if (tok == PDF_TOK_KEYWORD && !strcmp(lbuf.scratch, "Tm") && coef_i == MATRIX_COEFS) + { + found = 1; + mt->a = coefs[0]; + mt->b = coefs[1]; + mt->c = coefs[2]; + mt->d = coefs[3]; + mt->e = coefs[4]; + mt->f = coefs[5]; + } + + coef_i = 0; + } + } + + if (found) + { + if (q != Q_Left) + { + /* Offset the matrix to refer to the alignment position */ + fz_rect bbox = measure_text(doc, form->resources, form_contents(doc, form)); + mt->e += q == Q_Right ? (bbox.x1 - bbox.x0) + : (bbox.x1 - bbox.x0) / 2; + } + } + else + { + } + } + fz_always(ctx) + { + fz_close(str); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + return found; +} + +static void update_text_appearance(pdf_document *doc, pdf_obj *obj, char *text) +{ + fz_context *ctx = doc->ctx; + pdf_obj *ap, *n, *dr, *da; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + fz_matrix tm; + int q, has_tm; + + fz_var(form); + fz_var(fzbuf); + + fz_try(ctx) + { + dr = get_inheritable(doc, obj, "DR"); + da = get_inheritable(doc, obj, "DA"); + ap = pdf_dict_gets(obj, "AP"); + q = pdf_to_int(get_inheritable(doc, obj, "Q")); + if (pdf_is_dict(ap)) + { + n = pdf_dict_gets(ap, "N"); + + if (pdf_is_stream(doc, pdf_to_num(n), pdf_to_gen(n))) + { + form = pdf_load_xobject(doc, n); + + /* copy the default resources to the xobject */ + copy_resources(form->resources, dr); + + has_tm = get_matrix(doc, form, q, &tm); + fzbuf = create_text_appearance(doc, &form->bbox, has_tm ? &tm : NULL, q, dr, pdf_to_str_buf(da), text); + update_marked_content(doc, form, fzbuf); + } + } + } + fz_always(ctx) + { + pdf_drop_xobject(ctx, form); + fz_drop_buffer(ctx, fzbuf); + } + fz_catch(ctx) + { + fz_warn(ctx, "update_text_appearance failed"); + } +} + +static void update_text_field_value(fz_context *ctx, pdf_obj *obj, char *text) +{ + pdf_obj *parent = pdf_dict_gets(obj, "Parent"); + pdf_obj *sobj = NULL; + + if (parent) + obj = parent; + + fz_var(sobj); + fz_try(ctx) + { + sobj = pdf_new_string(ctx, text, strlen(text)); + pdf_dict_puts(obj, "V", sobj); + } + fz_always(ctx) + { + pdf_drop_obj(sobj); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void synthesize_text_widget(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + pdf_obj *ap = NULL; + fz_rect rect; + pdf_obj *formobj = NULL; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + + fz_var(formobj); + fz_var(ap); + fz_var(form); + fz_var(fzbuf); + fz_try(ctx) + { + rect = pdf_to_rect(ctx, pdf_dict_gets(obj, "Rect")); + rect.x1 -= rect.x0; + rect.y1 -= rect.y0; + rect.x0 = rect.y0 = 0; + formobj = pdf_new_xobject(doc, &rect, &fz_identity); + form = pdf_load_xobject(doc, formobj); + fzbuf = fz_new_buffer(ctx, 0); + fz_buffer_printf(ctx, fzbuf, "/Tx BMC EMC"); + pdf_xobject_set_contents(doc, form, fzbuf); + + ap = pdf_new_dict(ctx, 1); + pdf_dict_puts(ap, "N", formobj); + pdf_dict_puts(obj, "AP", ap); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, fzbuf); + pdf_drop_xobject(ctx, form); + pdf_drop_obj(formobj); + pdf_drop_obj(ap); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static pdf_xobject *load_or_create_form(pdf_document *doc, pdf_obj *obj, fz_rect *rect) +{ + fz_context *ctx = doc->ctx; + pdf_obj *ap = NULL; + pdf_obj *tobj = NULL; + fz_matrix mat; + int rot; + pdf_obj *formobj = NULL; + pdf_xobject *form = NULL; + const char *dn; + + fz_var(formobj); + fz_var(tobj); + fz_var(form); + fz_try(ctx) + { + pdf_hotspot *hp = &doc->hotspot; + if (hp->num == pdf_to_num(obj) + && hp->gen == pdf_to_gen(obj) + && (hp->state & HOTSPOT_POINTER_DOWN)) + { + dn = "D"; + } + else + { + dn = "N"; + } + + rot = pdf_to_int(pdf_dict_getp(obj, "MK/R")); + *rect = pdf_to_rect(ctx, pdf_dict_gets(obj, "Rect")); + rect->x1 -= rect->x0; + rect->y1 -= rect->y0; + rect->x0 = rect->y0 = 0; + account_for_rot(rect, &mat, rot); + + ap = pdf_dict_gets(obj, "AP"); + if (ap == NULL) + { + tobj = pdf_new_dict(ctx, 1); + pdf_dict_puts(obj, "AP", tobj); + ap = tobj; + tobj = NULL; + } + + formobj = pdf_dict_gets(ap, dn); + if (formobj == NULL) + { + tobj = pdf_new_xobject(doc, rect, &mat); + pdf_dict_puts(ap, dn, tobj); + formobj = tobj; + tobj = NULL; + } + + form = pdf_load_xobject(doc, formobj); + + copy_resources(form->resources, get_inheritable(doc, obj, "DR")); + } + fz_always(ctx) + { + pdf_drop_obj(tobj); + } + fz_catch(ctx) + { + pdf_drop_xobject(ctx, form); + fz_rethrow(ctx); + } + + return form; +} + +static fzbuf_print_color(fz_context *ctx, fz_buffer *fzbuf, pdf_obj *arr, int stroke, float adj) +{ + switch(pdf_array_len(arr)) + { + case 1: + fz_buffer_printf(ctx, fzbuf, stroke?"%f G\n":"%f g\n", + pdf_to_real(pdf_array_get(arr, 0)) + adj); + break; + case 3: + fz_buffer_printf(ctx, fzbuf, stroke?"%f %f %f rg\n":"%f %f %f rg\n", + pdf_to_real(pdf_array_get(arr, 0)) + adj, + pdf_to_real(pdf_array_get(arr, 1)) + adj, + pdf_to_real(pdf_array_get(arr, 2)) + adj); + break; + case 4: + fz_buffer_printf(ctx, fzbuf, stroke?"%f %f %f %f k\n":"%f %f %f %f k\n", + pdf_to_real(pdf_array_get(arr, 0)), + pdf_to_real(pdf_array_get(arr, 1)), + pdf_to_real(pdf_array_get(arr, 2)), + pdf_to_real(pdf_array_get(arr, 3))); + break; + } +} + +static int get_border_style(pdf_obj *obj) +{ + char *sname = pdf_to_name(pdf_dict_getp(obj, "BS/S")); + + if (!strcmp(sname, "D")) + return BS_Dashed; + else if (!strcmp(sname, "B")) + return BS_Beveled; + else if (!strcmp(sname, "I")) + return BS_Inset; + else if (!strcmp(sname, "U")) + return BS_Underline; + else + return BS_Solid; +} + +static float get_border_width(pdf_obj *obj) +{ + float w = pdf_to_real(pdf_dict_getp(obj, "BS/W")); + return w == 0.0 ? 1.0 : w; +} + +static void update_pushbutton_widget(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + fz_rect rect; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + fz_buffer *measure_buf = NULL; + pdf_obj *tobj = NULL; + int bstyle; + float bwidth; + float btotal; + + fz_var(form); + fz_var(fzbuf); + fz_var(measure_buf); + fz_try(ctx) + { + form = load_or_create_form(doc, obj, &rect); + fzbuf = fz_new_buffer(ctx, 0); + tobj = pdf_dict_getp(obj, "MK/BG"); + if (pdf_is_array(tobj)) + { + fzbuf_print_color(ctx, fzbuf, tobj, 0, 0.0); + fz_buffer_printf(ctx, fzbuf, fmt_re, + rect.x0, rect.y0, rect.x1, rect.y1); + fz_buffer_printf(ctx, fzbuf, fmt_f); + } + bstyle = get_border_style(obj); + bwidth = get_border_width(obj); + btotal = bwidth; + if (bstyle == BS_Beveled || bstyle == BS_Inset) + { + btotal += bwidth; + + if (bstyle == BS_Beveled) + fz_buffer_printf(ctx, fzbuf, fmt_g, 1.0); + else + fz_buffer_printf(ctx, fzbuf, fmt_g, 0.33); + fz_buffer_printf(ctx, fzbuf, fmt_m, bwidth, bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, bwidth, rect.y1 - bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - bwidth, rect.y1 - bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, rect.y1 - 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, rect.y1 - 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_f); + if (bstyle == BS_Beveled) + fzbuf_print_color(ctx, fzbuf, tobj, 0, -0.25); + else + fz_buffer_printf(ctx, fzbuf, fmt_g, 0.66); + fz_buffer_printf(ctx, fzbuf, fmt_m, rect.x1 - bwidth, rect.y1 - bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - bwidth, bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, bwidth, bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, 2 * bwidth, 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_l, rect.x1 - 2 * bwidth, rect.y1 - 2 * bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_f); + } + + tobj = pdf_dict_getp(obj, "MK/BC"); + if (tobj) + { + fzbuf_print_color(ctx, fzbuf, tobj, 1, 0.0); + fz_buffer_printf(ctx, fzbuf, fmt_w, bwidth); + fz_buffer_printf(ctx, fzbuf, fmt_re, + bwidth/2, bwidth/2, + rect.x1 -bwidth/2, rect.y1 - bwidth/2); + fz_buffer_printf(ctx, fzbuf, fmt_s); + } + + tobj = pdf_dict_getp(obj, "MK/CA"); + if (tobj) + { + fz_rect clip = rect; + fz_rect bounds; + fz_matrix mat; + char *da = pdf_to_str_buf(pdf_dict_gets(obj, "DA")); + char *text = pdf_to_str_buf(tobj); + + clip.x0 += btotal; + clip.y0 += btotal; + clip.x1 -= btotal; + clip.y1 -= btotal; + + measure_buf = create_text_buffer(ctx, NULL, da, 0, NULL, text); + bounds = measure_text(doc, form->resources, measure_buf); + mat = fz_translate((rect.x1 - bounds.x1)/2, (rect.y1 - bounds.y1)/2); + fzbuf_print_text(ctx, fzbuf, &clip, da, 0, &mat, text); + } + + pdf_xobject_set_contents(doc, form, fzbuf); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, fzbuf); + fz_drop_buffer(ctx, measure_buf); + pdf_drop_xobject(ctx, form); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +void pdf_update_appearance(pdf_document *doc, pdf_obj *obj) +{ + if (!pdf_dict_gets(obj, "AP") || pdf_dict_gets(obj, "Dirty")) + { + if (!strcmp(pdf_to_name(pdf_dict_gets(obj, "Subtype")), "Widget")) + { + switch(get_field_type(doc, obj)) + { + case FZ_WIDGET_TYPE_TEXT: + synthesize_text_widget(doc, obj); + break; + case FZ_WIDGET_TYPE_PUSHBUTTON: + update_pushbutton_widget(doc, obj); + break; + } + } + + pdf_dict_dels(obj, "Dirty"); + } +} + +static void execute_action(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + pdf_obj *a; + + a = pdf_dict_gets(obj, "A"); + while (a) + { + char *type = pdf_to_name(pdf_dict_gets(a, "S")); + + if (!strcmp(type, "JavaScript")) + { + pdf_obj *js = pdf_dict_gets(a, "JS"); + if (js) + { + char *code = get_string_or_stream(doc, js); + fz_try(ctx) + { + pdf_js_setup_event(doc->js, obj); + pdf_js_execute(doc->js, code); + } + fz_always(ctx) + { + fz_free(ctx, code); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } + } + + a = pdf_dict_gets(a, "Next"); + } +} + +static void toggle_check_box(pdf_document *doc, pdf_obj *obj) +{ + pdf_obj *as; + + as = pdf_dict_gets(obj, "AS"); + + if (strcmp(pdf_to_name(as), "Off")) + { + /* "as" neither missing nor set to Off. Set it to Off. */ + pdf_obj *off = fz_new_name(doc->ctx, "Off"); + pdf_dict_puts(obj, "AS", off); + pdf_drop_obj(off); + } + else + { + pdf_obj *ap, *n, *key; + int len, i; + + ap = pdf_dict_gets(obj, "AP"); + n = pdf_dict_gets(ap, "N"); + + /* Look for a key that isn't "Off" */ + len = pdf_dict_len(n); + for (i = 0; i < len; i++) + { + key = pdf_dict_get_key(n, i); + if (pdf_is_name(key) && strcmp(pdf_to_name(key), "Off")) + { + pdf_dict_puts(obj, "AS", key); + break; + } + } + } + + /* FIXME: should probably update the V entry in the field dictionary too */ +} + +int pdf_pass_event(pdf_document *doc, pdf_page *page, fz_ui_event *ui_event) +{ + pdf_annot *annot; + pdf_hotspot *hp = &doc->hotspot; + fz_point *pt = &(ui_event->event.pointer.pt); + int changed = 0; + + for (annot = page->annots; annot; annot = annot->next) + { + if (pt->x >= annot->pagerect.x0 && pt->x <= annot->pagerect.x1) + if (pt->y >= annot->pagerect.y0 && pt->y <= annot->pagerect.y1) + break; + } + + switch (ui_event->etype) + { + case FZ_EVENT_TYPE_POINTER: + { + switch (ui_event->event.pointer.ptype) + { + case FZ_POINTER_DOWN: + if (doc->focus) + { + fz_free_widget(doc->ctx, doc->focus); + doc->focus = NULL; + } + + if (annot) + { + doc->focus = new_widget(doc, annot->obj); + hp->num = pdf_to_num(annot->obj); + hp->gen = pdf_to_gen(annot->obj); + hp->state = HOTSPOT_POINTER_DOWN; + changed = 1; + } + break; + + case FZ_POINTER_UP: + if (hp->state != 0) + changed = 1; + + hp->num = 0; + hp->gen = 0; + hp->state = 0; + + if (annot) + { + switch(get_field_type(doc, annot->obj)) + { + case FZ_WIDGET_TYPE_RADIOBUTTON: + case FZ_WIDGET_TYPE_CHECKBOX: + /* FIXME: treating radio buttons like check boxes, for now */ + toggle_check_box(doc, annot->obj); + changed = 1; + break; + } + + execute_action(doc, annot->obj); + } + break; + } + } + break; + } + + return changed; +} + +fz_rect *pdf_get_screen_update(pdf_document *doc) +{ + return NULL; +} + +fz_widget *pdf_get_focussed_widget(pdf_document *doc) +{ + return doc->focus; +} + +void fz_free_widget(fz_context *ctx, fz_widget *widget) +{ + if (widget) + { + switch(widget->type) + { + case FZ_WIDGET_TYPE_TEXT: + fz_free(ctx, ((fz_widget_text *)widget)->text); + break; + } + + pdf_drop_obj(widget->obj); + fz_free(ctx, widget); + } +} + +int fz_widget_get_type(fz_widget *widget) +{ + return widget->type; +} + +char *pdf_field_getValue(pdf_document *doc, pdf_obj *field) +{ + return get_string_or_stream(doc, get_inheritable(doc, field, "V")); +} + +void pdf_field_setValue(pdf_document *doc, pdf_obj *field, char *text) +{ + update_text_appearance(doc, field, text); + update_text_field_value(doc->ctx, field, text); +} + +char *pdf_field_getBorderStyle(pdf_document *doc, pdf_obj *field) +{ + char *bs = pdf_to_name(pdf_dict_getp(field, "BS/S")); + + switch (*bs) + { + case 'S': return "Solid"; + case 'D': return "Dashed"; + case 'B': return "Beveled"; + case 'I': return "Inset"; + case 'U': return "Underline"; + } + + return "Solid"; +} + +void pdf_field_setBorderStyle(pdf_document *doc, pdf_obj *field, char *text) +{ + fz_context *ctx = doc->ctx; + pdf_obj *val = NULL; + + if (!strcmp(text, "Solid")) + val = fz_new_name(ctx, "S"); + else if (!strcmp(text, "Dashed")) + val = fz_new_name(ctx, "D"); + else if (!strcmp(text, "Beveled")) + val = fz_new_name(ctx, "B"); + else if (!strcmp(text, "Inset")) + val = fz_new_name(ctx, "I"); + else if (!strcmp(text, "Underline")) + val = fz_new_name(ctx, "U"); + else + return; + + fz_try(ctx); + { + pdf_dict_putp(field, "BS/S", val); + pdf_field_mark_dirty(ctx, field); + } + fz_always(ctx) + { + pdf_drop_obj(val); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +void pdf_field_buttonSetCaption(pdf_document *doc, pdf_obj *field, char *text) +{ + fz_context *ctx = doc->ctx; + pdf_obj *val = pdf_new_string(ctx, text, strlen(text)); + + fz_try(ctx); + { + if (get_field_type(doc, field) == FZ_WIDGET_TYPE_PUSHBUTTON) + { + pdf_dict_putp(field, "MK/CA", val); + pdf_field_mark_dirty(ctx, field); + } + } + fz_always(ctx) + { + pdf_drop_obj(val); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +void pdf_field_setFillColor(pdf_document *doc, pdf_obj *field, pdf_obj *col) +{ + pdf_dict_putp(field, "MK/BG", col); + pdf_field_mark_dirty(doc->ctx, field); +} + +void pdf_field_setTextColor(pdf_document *doc, pdf_obj *field, pdf_obj *col) +{ + fz_context *ctx = doc->ctx; + da_parse_state da_state; + fz_buffer *fzbuf = NULL; + char *da = pdf_to_str_buf(pdf_dict_gets(field, "DA")); + unsigned char *buf; + int len; + pdf_obj *daobj = NULL; + + da_init(ctx, &da_state); + + fz_var(fzbuf); + fz_var(da_state); + fz_var(daobj); + fz_try(ctx) + { + parse_da(ctx, da, &da_state); + da_state.col_size = 3; + da_state.col[0] = pdf_to_real(pdf_array_get(col, 0)); + da_state.col[1] = pdf_to_real(pdf_array_get(col, 1)); + da_state.col[2] = pdf_to_real(pdf_array_get(col, 2)); + fzbuf = fz_new_buffer(ctx, 0); + fzbuf_print_da(ctx, fzbuf, &da_state); + len = fz_buffer_storage(ctx, fzbuf, &buf); + daobj = pdf_new_string(ctx, buf, len); + pdf_dict_puts(field, "DA", daobj); + pdf_field_mark_dirty(ctx, field); + } + fz_always(ctx) + { + da_fin(ctx, &da_state); + fz_drop_buffer(ctx, fzbuf); + pdf_drop_obj(daobj); + } + fz_catch(ctx) + { + fz_warn(ctx, "%s", ctx->error->message); + } +} + +char *fz_widget_text_get_text(fz_widget_text *tw) +{ + pdf_document *doc = tw->super.doc; + fz_context *ctx = doc->ctx; + + fz_free(ctx, tw->text); + tw->text = NULL; + + fz_try(ctx) + { + tw->text = pdf_field_getValue(doc, tw->super.obj); + } + fz_catch(ctx) + { + fz_warn(ctx, "failed allocation in fz_widget_text_get_text"); + } + + return tw->text; +} + +void fz_widget_text_set_text(fz_widget_text *tw, char *text) +{ + fz_context *ctx = tw->super.doc->ctx; + + fz_try(ctx) + { + pdf_field_setValue(tw->super.doc, tw->super.obj, text); + fz_free(ctx, tw->text); + tw->text = fz_strdup(ctx, text); + } + fz_catch(ctx) + { + fz_warn(ctx, "fz_widget_text_set_text failed"); + } +} |