diff options
author | Tor Andersson <tor.andersson@artifex.com> | 2013-06-19 15:29:44 +0200 |
---|---|---|
committer | Tor Andersson <tor.andersson@artifex.com> | 2013-06-20 16:45:35 +0200 |
commit | 0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch) | |
tree | 3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/pdf/pdf-form.c | |
parent | 1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff) | |
download | mupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz |
Rearrange source files.
Diffstat (limited to 'source/pdf/pdf-form.c')
-rw-r--r-- | source/pdf/pdf-form.c | 2876 |
1 files changed, 2876 insertions, 0 deletions
diff --git a/source/pdf/pdf-form.c b/source/pdf/pdf-form.c new file mode 100644 index 00000000..6e1d26ce --- /dev/null +++ b/source/pdf/pdf-form.c @@ -0,0 +1,2876 @@ +#include "mupdf/pdf.h" + +#define MATRIX_COEFS (6) + +#define STRIKE_HEIGHT (0.375f) +#define UNDERLINE_HEIGHT (0.075f) +#define LINE_THICKNESS (0.07f) +#define SMALL_FLOAT (0.00001) + +enum +{ + F_Invisible = 1 << (1-1), + F_Hidden = 1 << (2-1), + F_Print = 1 << (3-1), + F_NoZoom = 1 << (4-1), + F_NoRotate = 1 << (5-1), + F_NoView = 1 << (6-1), + F_ReadOnly = 1 << (7-1), + F_Locked = 1 << (8-1), + F_ToggleNoView = 1 << (9-1), + F_LockedContents = 1 << (10-1) +}; + +enum +{ + BS_Solid, + BS_Dashed, + BS_Beveled, + BS_Inset, + BS_Underline +}; + +/* Must be kept in sync with definitions in pdf_util.js */ +enum +{ + Display_Visible, + Display_Hidden, + Display_NoPrint, + Display_NoView +}; + +enum +{ + Q_Left = 0, + Q_Cent = 1, + Q_Right = 2 +}; + +typedef struct da_info_s +{ + char *font_name; + int font_size; + float col[4]; + int col_size; +} da_info; + +typedef struct font_info_s +{ + da_info da_rec; + pdf_font_desc *font; +} font_info; + +typedef struct text_widget_info_s +{ + pdf_obj *dr; + pdf_obj *col; + font_info font_rec; + int q; + int multiline; + int comb; + int max_len; +} text_widget_info; + +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_Td = "%f %f Td\n"; +static const char *fmt_Tj = " Tj\n"; +static const char *fmt_ET = "ET\n"; +static const char *fmt_Q = "Q\n"; +static const char *fmt_EMC = "EMC\n"; + +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: + fz_pre_rotate(fz_translate(mat, width, 0), rot); + rect->x1 = height; + rect->y1 = width; + break; + case 180: + fz_pre_rotate(fz_translate(mat, width, height), rot); + break; + case 270: + fz_pre_rotate(fz_translate(mat, 0, height), rot); + rect->x1 = height; + rect->y1 = width; + break; + } +} + +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, (unsigned char **)&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; +} + +/* Find the point in a field hierarchy where all descendents + * share the same name */ +static pdf_obj *find_head_of_field_group(pdf_obj *obj) +{ + if (obj == NULL || pdf_dict_gets(obj, "T")) + return obj; + else + return find_head_of_field_group(pdf_dict_gets(obj, "Parent")); +} + +static void pdf_field_mark_dirty(fz_context *ctx, pdf_obj *field) +{ + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + if (kids) + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + pdf_field_mark_dirty(ctx, pdf_array_get(kids, i)); + } + else 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)) + pdf_dict_put(dst, key, pdf_dict_get_val(src, i)); + } +} + +static void da_info_fin(fz_context *ctx, da_info *di) +{ + fz_free(ctx, di->font_name); + di->font_name = NULL; +} + +static void da_check_stack(float *stack, int *top) +{ + if (*top == 32) + { + memmove(stack, stack + 1, 31 * sizeof(stack[0])); + *top = 31; + } +} + +static void parse_da(fz_context *ctx, char *da, da_info *di) +{ + float stack[32]; + int top = 0; + pdf_token tok; + char *name = NULL; + pdf_lexbuf lbuf; + fz_stream *str = fz_open_memory(ctx, (unsigned char *)da, strlen(da)); + + pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL); + + fz_var(str); + fz_var(name); + 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, name); + name = fz_strdup(ctx, lbuf.scratch); + break; + + case PDF_TOK_INT: + da_check_stack(stack, &top); + stack[top] = lbuf.i; + top ++; + break; + + case PDF_TOK_REAL: + da_check_stack(stack, &top); + stack[top] = lbuf.f; + top ++; + break; + + case PDF_TOK_KEYWORD: + if (!strcmp(lbuf.scratch, "Tf")) + { + di->font_size = stack[0]; + di->font_name = name; + name = NULL; + } + else if (!strcmp(lbuf.scratch, "rg")) + { + di->col[0] = stack[0]; + di->col[1] = stack[1]; + di->col[2] = stack[2]; + di->col_size = 3; + } + + fz_free(ctx, name); + name = NULL; + top = 0; + break; + + default: + break; + } + } + } + fz_always(ctx) + { + fz_free(ctx, name); + fz_close(str); + pdf_lexbuf_fin(&lbuf); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void get_font_info(pdf_document *doc, pdf_obj *dr, char *da, font_info *font_rec) +{ + fz_context *ctx = doc->ctx; + + parse_da(ctx, da, &font_rec->da_rec); + if (font_rec->da_rec.font_name == NULL) + fz_throw(ctx, FZ_ERROR_GENERIC, "No font name in default appearance"); + font_rec->font = pdf_load_font(doc, dr, pdf_dict_gets(pdf_dict_gets(dr, "Font"), font_rec->da_rec.font_name), 0); +} + +static void font_info_fin(fz_context *ctx, font_info *font_rec) +{ + pdf_drop_font(ctx, font_rec->font); + font_rec->font = NULL; + da_info_fin(ctx, &font_rec->da_rec); +} + +static void get_text_widget_info(pdf_document *doc, pdf_obj *widget, text_widget_info *info) +{ + char *da = pdf_to_str_buf(pdf_get_inheritable(doc, widget, "DA")); + int ff = pdf_get_field_flags(doc, widget); + pdf_obj *ml = pdf_get_inheritable(doc, widget, "MaxLen"); + + info->dr = pdf_get_inheritable(doc, widget, "DR"); + info->col = pdf_dict_getp(widget, "MK/BG"); + info->q = pdf_to_int(pdf_get_inheritable(doc, widget, "Q")); + info->multiline = (ff & Ff_Multiline) != 0; + info->comb = (ff & (Ff_Multiline|Ff_Password|Ff_FileSelect|Ff_Comb)) == Ff_Comb; + + if (ml == NULL) + info->comb = 0; + else + info->max_len = pdf_to_int(ml); + + get_font_info(doc, info->dr, da, &info->font_rec); +} + +static void fzbuf_print_da(fz_context *ctx, fz_buffer *fzbuf, da_info *di) +{ + if (di->font_name != NULL && di->font_size != 0) + fz_buffer_printf(ctx, fzbuf, "/%s %d Tf", di->font_name, di->font_size); + + switch (di->col_size) + { + case 1: + fz_buffer_printf(ctx, fzbuf, " %f g", di->col[0]); + break; + + case 3: + fz_buffer_printf(ctx, fzbuf, " %f %f %f rg", di->col[0], di->col[1], di->col[2]); + break; + + case 4: + fz_buffer_printf(ctx, fzbuf, " %f %f %f %f k", di->col[0], di->col[1], di->col[2], di->col[3]); + break; + + default: + fz_buffer_printf(ctx, fzbuf, " 0 g"); + break; + } +} + +static fz_rect *measure_text(pdf_document *doc, font_info *font_rec, const fz_matrix *tm, char *text, fz_rect *bbox) +{ + pdf_measure_text(doc->ctx, font_rec->font, (unsigned char *)text, strlen(text), bbox); + + bbox->x0 *= font_rec->da_rec.font_size * tm->a; + bbox->y0 *= font_rec->da_rec.font_size * tm->d; + bbox->x1 *= font_rec->da_rec.font_size * tm->a; + bbox->y1 *= font_rec->da_rec.font_size * tm->d; + + return bbox; +} + +static void 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 void fzbuf_print_text(fz_context *ctx, fz_buffer *fzbuf, const fz_rect *clip, pdf_obj *col, font_info *font_rec, const 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); + if (col) + { + fzbuf_print_color(ctx, fzbuf, col, 0, 0.0); + fz_buffer_printf(ctx, fzbuf, fmt_f); + } + else + { + fz_buffer_printf(ctx, fzbuf, fmt_n); + } + } + + fz_buffer_printf(ctx, fzbuf, fmt_BT); + + fzbuf_print_da(ctx, fzbuf, &font_rec->da_rec); + + 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_cat_pdf_string(ctx, fzbuf, text); + fz_buffer_printf(ctx, fzbuf, fmt_Tj); + fz_buffer_printf(ctx, fzbuf, fmt_ET); + fz_buffer_printf(ctx, fzbuf, fmt_Q); +} + +static fz_buffer *create_text_buffer(fz_context *ctx, const fz_rect *clip, text_widget_info *info, const 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, info->col, &info->font_rec, 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, const fz_rect *clip, text_widget_info *info, const fz_matrix *tm, char *text) +{ + fz_context *ctx = doc->ctx; + fz_matrix atm = *tm; + + if (info->q != Q_Left) + { + fz_rect rect; + + measure_text(doc, &info->font_rec, tm, text, &rect); + atm.e -= info->q == Q_Right ? rect.x1 : (rect.x1 - rect.x0) / 2; + } + + return create_text_buffer(ctx, clip, info, &atm, text); +} + +static void measure_ascent_descent(pdf_document *doc, font_info *finf, char *text, float *ascent, float *descent) +{ + fz_context *ctx = doc->ctx; + char *testtext = NULL; + fz_rect bbox; + font_info tinf = *finf; + + fz_var(testtext); + 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); + tinf.da_rec.font_size = 1; + measure_text(doc, &tinf, &fz_identity, testtext, &bbox); + *descent = -bbox.y0; + *ascent = bbox.y1; + } + fz_always(ctx) + { + fz_free(ctx, testtext); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +typedef struct text_splitter_s +{ + font_info *info; + float width; + float height; + float scale; + float unscaled_width; + float fontsize; + char *text; + int done; + float x_orig; + float y_orig; + float x; + float x_end; + int text_start; + int text_end; + int max_lines; + int retry; +} text_splitter; + +static void text_splitter_init(text_splitter *splitter, font_info *info, char *text, float width, float height, int variable) +{ + float fontsize = info->da_rec.font_size; + + memset(splitter, 0, sizeof(*splitter)); + splitter->info = info; + splitter->text = text; + splitter->width = width; + splitter->unscaled_width = width; + splitter->height = height; + splitter->fontsize = fontsize; + splitter->scale = 1.0; + /* RJW: The cast in the following line is important, as otherwise + * under MSVC in the variable = 0 case, splitter->max_lines becomes + * INT_MIN. */ + splitter->max_lines = variable ? (int)(height/fontsize) : INT_MAX; +} + +static void text_splitter_start_pass(text_splitter *splitter) +{ + splitter->text_end = 0; + splitter->x_orig = 0; + splitter->y_orig = 0; +} + +static void text_splitter_start_line(text_splitter *splitter) +{ + splitter->x_end = 0; +} + +static int text_splitter_layout(fz_context *ctx, text_splitter *splitter) +{ + char *text; + float room; + float stride; + int count; + int len; + float fontsize = splitter->info->da_rec.font_size; + + splitter->x = splitter->x_end; + splitter->text_start = splitter->text_end; + + text = splitter->text + splitter->text_start; + room = splitter->unscaled_width - splitter->x; + + if (strchr("\r\n", text[0])) + { + /* Consume return chars and report end of line */ + splitter->text_end += strspn(text, "\r\n"); + splitter->text_start = splitter->text_end; + splitter->done = (splitter->text[splitter->text_end] == '\0'); + return 0; + } + else if (text[0] == ' ') + { + /* Treat each space as a word */ + len = 1; + } + else + { + len = 0; + while (text[len] != '\0' && !strchr(" \r\n", text[len])) + len ++; + } + + stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, room, &count); + + /* If not a single char fits although the line is empty, then force one char */ + if (count == 0 && splitter->x == 0.0) + stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, 1, FLT_MAX, &count); + + if (count < len && splitter->retry) + { + /* The word didn't fit and we are in retry mode. Work out the + * least additional scaling that may help */ + float fitwidth; /* width if we force the word in */ + float hstretchwidth; /* width if we just bump by 10% */ + float vstretchwidth; /* width resulting from forcing in another line */ + float bestwidth; + + fitwidth = splitter->x + + pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, FLT_MAX, &count); + /* FIXME: temporary fiddle factor. Would be better to work in integers */ + fitwidth *= 1.001f; + + /* Stretching by 10% is worth trying only if processing the first word on the line */ + hstretchwidth = splitter->x == 0.0 + ? splitter->width * 1.1 / splitter->scale + : FLT_MAX; + + vstretchwidth = splitter->width * (splitter->max_lines + 1) * splitter->fontsize + / splitter->height; + + bestwidth = fz_min(fitwidth, fz_min(hstretchwidth, vstretchwidth)); + + if (bestwidth == vstretchwidth) + splitter->max_lines ++; + + splitter->scale = splitter->width / bestwidth; + splitter->unscaled_width = bestwidth; + + splitter->retry = 0; + + /* Try again */ + room = splitter->unscaled_width - splitter->x; + stride = pdf_text_stride(ctx, splitter->info->font, fontsize, (unsigned char *)text, len, room, &count); + } + + /* This is not the first word on the line. Best to give up on this line and push + * the word onto the next */ + if (count < len && splitter->x > 0.0) + return 0; + + splitter->text_end = splitter->text_start + count; + splitter->x_end = splitter->x + stride; + splitter->done = (splitter->text[splitter->text_end] == '\0'); + return 1; +} + +static void text_splitter_move(text_splitter *splitter, float newy, float *relx, float *rely) +{ + *relx = splitter->x - splitter->x_orig; + *rely = newy - splitter->y_orig; + + splitter->x_orig = splitter->x; + splitter->y_orig = newy; +} + +static void text_splitter_retry(text_splitter *splitter) +{ + if (splitter->retry) + { + /* Already tried expanding lines. Overflow must + * be caused by carriage control */ + splitter->max_lines ++; + splitter->retry = 0; + splitter->unscaled_width = splitter->width * splitter->max_lines * splitter->fontsize + / splitter->height; + splitter->scale = splitter->width / splitter->unscaled_width; + } + else + { + splitter->retry = 1; + } +} + +static void fzbuf_print_text_start(fz_context *ctx, fz_buffer *fzbuf, const fz_rect *clip, pdf_obj *col, font_info *font, const fz_matrix *tm) +{ + fz_buffer_printf(ctx, fzbuf, fmt_Tx_BMC); + 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); + if (col) + { + fzbuf_print_color(ctx, fzbuf, col, 0, 0.0); + fz_buffer_printf(ctx, fzbuf, fmt_f); + } + else + { + fz_buffer_printf(ctx, fzbuf, fmt_n); + } + } + + fz_buffer_printf(ctx, fzbuf, fmt_BT); + + fzbuf_print_da(ctx, fzbuf, &font->da_rec); + fz_buffer_printf(ctx, fzbuf, "\n"); + + fz_buffer_printf(ctx, fzbuf, fmt_Tm, tm->a, tm->b, tm->c, tm->d, tm->e, tm->f); +} + +static void fzbuf_print_text_end(fz_context *ctx, fz_buffer *fzbuf) +{ + fz_buffer_printf(ctx, fzbuf, fmt_ET); + fz_buffer_printf(ctx, fzbuf, fmt_Q); + fz_buffer_printf(ctx, fzbuf, fmt_EMC); +} + +static void fzbuf_print_text_word(fz_context *ctx, fz_buffer *fzbuf, float x, float y, char *text, int count) +{ + int i; + + fz_buffer_printf(ctx, fzbuf, fmt_Td, x, y); + fz_buffer_printf(ctx, fzbuf, "("); + + for (i = 0; i < count; i++) + fz_buffer_printf(ctx, fzbuf, "%c", text[i]); + + fz_buffer_printf(ctx, fzbuf, ") Tj\n"); +} + +static fz_buffer *create_text_appearance(pdf_document *doc, const fz_rect *bbox, const fz_matrix *oldtm, text_widget_info *info, char *text) +{ + fz_context *ctx = doc->ctx; + int fontsize; + int variable; + float height, width, full_width; + fz_buffer *fzbuf = NULL; + fz_buffer *fztmp = NULL; + fz_rect rect; + fz_rect tbox; + rect = *bbox; + + if (rect.x1 - rect.x0 > 3.0 && rect.y1 - rect.y0 > 3.0) + { + rect.x0 += 1.0; + rect.x1 -= 1.0; + rect.y0 += 1.0; + rect.y1 -= 1.0; + } + + height = rect.y1 - rect.y0; + width = rect.x1 - rect.x0; + full_width = bbox->x1 - bbox->x0; + + fz_var(fzbuf); + fz_var(fztmp); + fz_try(ctx) + { + float ascent, descent; + fz_matrix tm; + + variable = (info->font_rec.da_rec.font_size == 0); + fontsize = variable + ? (info->multiline ? 14.0 : floor(height)) + : info->font_rec.da_rec.font_size; + + info->font_rec.da_rec.font_size = fontsize; + + measure_ascent_descent(doc, &info->font_rec, text, &ascent, &descent); + + if (info->multiline) + { + text_splitter splitter; + + text_splitter_init(&splitter, &info->font_rec, text, width, height, variable); + + while (!splitter.done) + { + /* Try a layout pass */ + int line = 0; + + fz_drop_buffer(ctx, fztmp); + fztmp = NULL; + fztmp = fz_new_buffer(ctx, 0); + + text_splitter_start_pass(&splitter); + + /* Layout unscaled text to a scaled-up width, so that + * the scaled-down text will fit the unscaled width */ + + while (!splitter.done && line < splitter.max_lines) + { + /* Layout a line */ + text_splitter_start_line(&splitter); + + while (!splitter.done && text_splitter_layout(ctx, &splitter)) + { + if (splitter.text[splitter.text_start] != ' ') + { + float x, y; + char *word = text+splitter.text_start; + int wordlen = splitter.text_end-splitter.text_start; + + text_splitter_move(&splitter, -line*fontsize, &x, &y); + fzbuf_print_text_word(ctx, fztmp, x, y, word, wordlen); + } + } + + line ++; + } + + if (!splitter.done) + text_splitter_retry(&splitter); + } + + fzbuf = fz_new_buffer(ctx, 0); + + tm.a = splitter.scale; + tm.b = 0.0; + tm.c = 0.0; + tm.d = splitter.scale; + tm.e = rect.x0; + tm.f = rect.y1 - (1.0+ascent-descent)*fontsize*splitter.scale/2.0; + + fzbuf_print_text_start(ctx, fzbuf, &rect, info->col, &info->font_rec, &tm); + + fz_buffer_cat(ctx, fzbuf, fztmp); + + fzbuf_print_text_end(ctx, fzbuf); + } + else if (info->comb) + { + int i, n = fz_mini((int)strlen(text), info->max_len); + float comb_width = full_width/info->max_len; + float char_width = pdf_text_stride(ctx, info->font_rec.font, fontsize, (unsigned char *)"M", 1, FLT_MAX, NULL); + float init_skip = (comb_width - char_width)/2.0; + + fz_translate(&tm, rect.x0, rect.y1 - (height+(ascent-descent)*fontsize)/2.0); + + fzbuf = fz_new_buffer(ctx, 0); + + fzbuf_print_text_start(ctx, fzbuf, &rect, info->col, &info->font_rec, &tm); + + for (i = 0; i < n; i++) + fzbuf_print_text_word(ctx, fzbuf, i == 0 ? init_skip : comb_width, 0.0, text+i, 1); + + fzbuf_print_text_end(ctx, fzbuf); + } + else + { + if (oldtm) + { + tm = *oldtm; + } + else + { + fz_translate(&tm, rect.x0, rect.y1 - (height+(ascent-descent)*fontsize)/2.0); + + switch (info->q) + { + case Q_Right: tm.e += width; break; + case Q_Cent: tm.e += width/2; break; + } + } + + if (variable) + { + measure_text(doc, &info->font_rec, &tm, text, &tbox); + + if (tbox.x1 - tbox.x0 > width) + { + /* 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, info, &tm, text); + } + } + fz_always(ctx) + { + fz_drop_buffer(ctx, fztmp); + } + 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; + pdf_token tok; + pdf_lexbuf lbuf; + fz_stream *str_outer = NULL; + fz_stream *str_inner = NULL; + unsigned char *buf; + int len; + fz_buffer *newbuf = NULL; + + pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL); + + 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); + str_outer = pdf_open_stream(doc, pdf_to_num(form->contents), pdf_to_gen(form->contents)); + 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_update_xobject_contents(doc, form, newbuf); + } + fz_always(ctx) + { + fz_close(str_outer); + fz_close(str_inner); + fz_drop_buffer(ctx, newbuf); + pdf_lexbuf_fin(&lbuf); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static int get_matrix(pdf_document *doc, pdf_xobject *form, int q, fz_matrix *mt) +{ + fz_context *ctx = doc->ctx; + int found = 0; + pdf_lexbuf lbuf; + fz_stream *str; + + str = pdf_open_stream(doc, pdf_to_num(form->contents), pdf_to_gen(form->contents)); + + pdf_lexbuf_init(ctx, &lbuf, PDF_LEXBUF_SMALL); + + 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) + { + fz_rect bbox; + pdf_to_rect(ctx, pdf_dict_gets(form->contents, "BBox"), &bbox); + + switch (q) + { + case Q_Left: + mt->e = bbox.x0 + 1; + break; + + case Q_Cent: + mt->e = (bbox.x1 - bbox.x0) / 2; + break; + + case Q_Right: + mt->e = bbox.x1 - 1; + break; + } + } + } + fz_always(ctx) + { + fz_close(str); + pdf_lexbuf_fin(&lbuf); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + return found; +} + +static void update_field_value(fz_context *ctx, pdf_obj *obj, char *text) +{ + pdf_obj *sobj = NULL; + pdf_obj *grp; + + if (!text) + text = ""; + + /* All fields of the same name should be updated, so + * set the value at the head of the group */ + grp = find_head_of_field_group(obj); + if (grp) + obj = grp; + + 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); + } + + pdf_field_mark_dirty(ctx, obj); +} + +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; + fz_matrix mat; + int rot; + pdf_obj *formobj = NULL; + pdf_xobject *form = NULL; + char *dn = "N"; + fz_buffer *fzbuf = NULL; + int create_form = 0; + + fz_var(formobj); + fz_var(form); + fz_var(fzbuf); + fz_try(ctx) + { + rot = pdf_to_int(pdf_dict_getp(obj, "MK/R")); + pdf_to_rect(ctx, pdf_dict_gets(obj, "Rect"), 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) + { + ap = pdf_new_dict(ctx, 1); + pdf_dict_puts_drop(obj, "AP", ap); + } + + formobj = pdf_dict_gets(ap, dn); + if (formobj == NULL) + { + formobj = pdf_new_xobject(doc, rect, &mat); + pdf_dict_puts_drop(ap, dn, formobj); + create_form = 1; + } + + form = pdf_load_xobject(doc, formobj); + if (create_form) + { + fzbuf = fz_new_buffer(ctx, 1); + pdf_update_xobject_contents(doc, form, fzbuf); + } + + copy_resources(form->resources, pdf_get_inheritable(doc, obj, "DR")); + } + fz_always(ctx) + { + fz_drop_buffer(ctx, fzbuf); + } + fz_catch(ctx) + { + pdf_drop_xobject(ctx, form); + fz_rethrow(ctx); + } + + return form; +} + +static char *to_font_encoding(fz_context *ctx, pdf_font_desc *font, char *utf8) +{ + int i; + int needs_converting = 0; + + /* Temporay partial solution. We are using a slow lookup in the conversion + * below, so we avoid performing the conversion unnecessarily. We check for + * top-bit-set chars, and convert only if they are present. We should also + * check that the font encoding is one that agrees with utf8 from 0 to 7f, + * but for now we get away without doing so. This is after all an improvement + * on just strdup */ + for (i = 0; utf8[i] != '\0'; i++) + { + if (utf8[i] & 0x80) + needs_converting = 1; + } + + /* Even if we need to convert, we cannot do so if the font has no cid_to_ucs mapping */ + if (needs_converting && font->cid_to_ucs) + { + char *buf = fz_malloc(ctx, strlen(utf8) + 1); + char *bufp = buf; + + fz_try(ctx) + { + while(*utf8) + { + if (*utf8 & 0x80) + { + int rune; + + utf8 += fz_chartorune(&rune, utf8); + + /* Slow search for the cid that maps to the unicode value held in 'rune" */ + for (i = 0; i < font->cid_to_ucs_len && font->cid_to_ucs[i] != rune; i++) + ; + + /* If found store the cid */ + if (i < font->cid_to_ucs_len) + *bufp++ = i; + } + else + { + *bufp++ = *utf8++; + } + } + + *bufp = '\0'; + } + fz_catch(ctx) + { + fz_free(ctx, buf); + fz_rethrow(ctx); + } + + return buf; + } + else + { + /* If either no conversion is needed or the font has no cid_to_ucs + * mapping then leave unconverted, although in the latter case the result + * is likely incorrect */ + return fz_strdup(ctx, utf8); + } +} + +static void update_text_appearance(pdf_document *doc, pdf_obj *obj, char *eventValue) +{ + fz_context *ctx = doc->ctx; + text_widget_info info; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + fz_matrix tm; + fz_rect rect; + int has_tm; + char *text = NULL; + + memset(&info, 0, sizeof(info)); + + fz_var(info); + fz_var(form); + fz_var(fzbuf); + fz_var(text); + fz_try(ctx) + { + get_text_widget_info(doc, obj, &info); + + if (eventValue) + text = to_font_encoding(ctx, info.font_rec.font, eventValue); + else + text = pdf_field_value(doc, obj); + + form = load_or_create_form(doc, obj, &rect); + + has_tm = get_matrix(doc, form, info.q, &tm); + fzbuf = create_text_appearance(doc, &form->bbox, has_tm ? &tm : NULL, &info, + text?text:""); + update_marked_content(doc, form, fzbuf); + } + fz_always(ctx) + { + fz_free(ctx, text); + pdf_drop_xobject(ctx, form); + fz_drop_buffer(ctx, fzbuf); + font_info_fin(ctx, &info.font_rec); + } + fz_catch(ctx) + { + fz_warn(ctx, "update_text_appearance failed"); + } +} + +static void update_combobox_appearance(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + text_widget_info info; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + fz_matrix tm; + fz_rect rect; + int has_tm; + pdf_obj *val; + char *text; + + memset(&info, 0, sizeof(info)); + + fz_var(info); + fz_var(form); + fz_var(fzbuf); + fz_try(ctx) + { + get_text_widget_info(doc, obj, &info); + + val = pdf_get_inheritable(doc, obj, "V"); + + if (pdf_is_array(val)) + val = pdf_array_get(val, 0); + + text = pdf_to_str_buf(val); + + if (!text) + text = ""; + + form = load_or_create_form(doc, obj, &rect); + + has_tm = get_matrix(doc, form, info.q, &tm); + fzbuf = create_text_appearance(doc, &form->bbox, has_tm ? &tm : NULL, &info, + text?text:""); + update_marked_content(doc, form, fzbuf); + } + fz_always(ctx) + { + pdf_drop_xobject(ctx, form); + fz_drop_buffer(ctx, fzbuf); + font_info_fin(ctx, &info.font_rec); + } + fz_catch(ctx) + { + fz_warn(ctx, "update_text_appearance failed"); + } +} + +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_appearance(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + fz_rect rect; + pdf_xobject *form = NULL; + fz_buffer *fzbuf = NULL; + pdf_obj *tobj = NULL; + font_info font_rec; + int bstyle; + float bwidth; + float btotal; + + memset(&font_rec, 0, sizeof(font_rec)); + + fz_var(font_rec); + fz_var(form); + fz_var(fzbuf); + 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_get_inheritable(doc, obj, "DA")); + char *text = pdf_to_str_buf(tobj); + + clip.x0 += btotal; + clip.y0 += btotal; + clip.x1 -= btotal; + clip.y1 -= btotal; + + get_font_info(doc, form->resources, da, &font_rec); + measure_text(doc, &font_rec, &fz_identity, text, &bounds); + fz_translate(&mat, (rect.x1 - bounds.x1)/2, (rect.y1 - bounds.y1)/2); + fzbuf_print_text(ctx, fzbuf, &clip, NULL, &font_rec, &mat, text); + } + + pdf_update_xobject_contents(doc, form, fzbuf); + } + fz_always(ctx) + { + font_info_fin(ctx, &font_rec); + fz_drop_buffer(ctx, fzbuf); + pdf_drop_xobject(ctx, form); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static pdf_obj *find_field(pdf_obj *dict, char *name, int len) +{ + pdf_obj *field; + + int i, n = pdf_array_len(dict); + + for (i = 0; i < n; i++) + { + char *part; + + field = pdf_array_get(dict, i); + part = pdf_to_str_buf(pdf_dict_gets(field, "T")); + if (strlen(part) == len && !memcmp(part, name, len)) + return field; + } + + return NULL; +} + +pdf_obj *pdf_lookup_field(pdf_obj *form, char *name) +{ + char *dot; + char *namep; + pdf_obj *dict = NULL; + int len; + + /* Process the fully qualified field name which has + * the partial names delimited by '.'. Pretend there + * was a preceding '.' to simplify the loop */ + dot = name - 1; + + while (dot && form) + { + namep = dot + 1; + dot = strchr(namep, '.'); + len = dot ? dot - namep : strlen(namep); + dict = find_field(form, namep, len); + if (dot) + form = pdf_dict_gets(dict, "Kids"); + } + + return dict; +} + +static void reset_field(pdf_document *doc, pdf_obj *field) +{ + fz_context *ctx = doc->ctx; + /* Set V to DV whereever DV is present, and delete V where DV is not. + * FIXME: we assume for now that V has not been set unequal + * to DV higher in the hierarchy than "field". + * + * At the bottom of the hierarchy we may find widget annotations + * that aren't also fields, but DV and V will not be present in their + * dictionaries, and attempts to remove V will be harmless. */ + pdf_obj *dv = pdf_dict_gets(field, "DV"); + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + + if (dv) + pdf_dict_puts(field, "V", dv); + else + pdf_dict_dels(field, "V"); + + if (kids == NULL) + { + /* The leaves of the tree are widget annotations + * In some cases we need to update the appearance state; + * in others we need to mark the field as dirty so that + * the appearance stream will be regenerated. */ + switch (pdf_field_type(doc, field)) + { + case PDF_WIDGET_TYPE_RADIOBUTTON: + case PDF_WIDGET_TYPE_CHECKBOX: + { + pdf_obj *leafv = pdf_get_inheritable(doc, field, "V"); + + if (leafv) + pdf_keep_obj(leafv); + else + leafv = pdf_new_name(ctx, "Off"); + + fz_try(ctx) + { + pdf_dict_puts(field, "AS", leafv); + } + fz_always(ctx) + { + pdf_drop_obj(leafv); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } + break; + + case PDF_WIDGET_TYPE_PUSHBUTTON: + break; + + default: + pdf_field_mark_dirty(ctx, field); + break; + } + } + + doc->dirty = 1; +} + +void pdf_field_reset(pdf_document *doc, pdf_obj *field) +{ + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + + reset_field(doc, field); + + if (kids) + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + pdf_field_reset(doc, pdf_array_get(kids, i)); + } +} + +static void add_field_hierarchy_to_array(pdf_obj *array, pdf_obj *field) +{ + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + pdf_obj *exclude = pdf_dict_gets(field, "Exclude"); + + if (exclude) + return; + + pdf_array_push(array, field); + + if (kids) + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + add_field_hierarchy_to_array(array, pdf_array_get(kids, i)); + } +} + +/* + When resetting or submitting a form, the fields to act upon are defined + by an array of either field references or field names, plus a flag determining + whether to act upon the fields in the array, or all fields other than those in + the array. specified_fields interprets this information and produces the array + of fields to be acted upon. +*/ +static pdf_obj *specified_fields(pdf_document *doc, pdf_obj *fields, int exclude) +{ + fz_context *ctx = doc->ctx; + pdf_obj *form = pdf_dict_getp(pdf_trailer(doc), "Root/AcroForm/Fields"); + int i, n; + pdf_obj *result = pdf_new_array(ctx, 0); + pdf_obj *nil = NULL; + + fz_var(nil); + fz_try(ctx) + { + /* The 'fields' array not being present signals that all fields + * should be acted upon, so handle it using the exclude case - excluding none */ + if (exclude || !fields) + { + /* mark the fields we don't want to act upon */ + nil = pdf_new_null(ctx); + + n = pdf_array_len(fields); + + for (i = 0; i < n; i++) + { + pdf_obj *field = pdf_array_get(fields, i); + + if (pdf_is_string(field)) + field = pdf_lookup_field(form, pdf_to_str_buf(field)); + + if (field) + pdf_dict_puts(field, "Exclude", nil); + } + + /* Act upon all unmarked fields */ + n = pdf_array_len(form); + + for (i = 0; i < n; i++) + add_field_hierarchy_to_array(result, pdf_array_get(form, i)); + + /* Unmark the marked fields */ + n = pdf_array_len(fields); + + for (i = 0; i < n; i++) + { + pdf_obj *field = pdf_array_get(fields, i); + + if (pdf_is_string(field)) + field = pdf_lookup_field(form, pdf_to_str_buf(field)); + + if (field) + pdf_dict_dels(field, "Exclude"); + } + } + else + { + n = pdf_array_len(fields); + + for (i = 0; i < n; i++) + { + pdf_obj *field = pdf_array_get(fields, i); + + if (pdf_is_string(field)) + field = pdf_lookup_field(form, pdf_to_str_buf(field)); + + if (field) + add_field_hierarchy_to_array(result, field); + } + } + } + fz_always(ctx) + { + pdf_drop_obj(nil); + } + fz_catch(ctx) + { + pdf_drop_obj(result); + fz_rethrow(ctx); + } + + return result; +} + +static void reset_form(pdf_document *doc, pdf_obj *fields, int exclude) +{ + fz_context *ctx = doc->ctx; + pdf_obj *sfields = specified_fields(doc, fields, exclude); + + fz_try(ctx) + { + int i, n = pdf_array_len(sfields); + + for (i = 0; i < n; i++) + reset_field(doc, pdf_array_get(sfields, i)); + } + fz_always(ctx) + { + pdf_drop_obj(sfields); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void execute_action(pdf_document *doc, pdf_obj *obj, pdf_obj *a) +{ + fz_context *ctx = doc->ctx; + if (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 = pdf_to_utf8(doc, js); + fz_try(ctx) + { + pdf_js_execute(doc->js, code); + } + fz_always(ctx) + { + fz_free(ctx, code); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } + } + else if (!strcmp(type, "ResetForm")) + { + reset_form(doc, pdf_dict_gets(a, "Fields"), pdf_to_int(pdf_dict_gets(a, "Flags")) & 1); + } + else if (!strcmp(type, "Named")) + { + char *name = pdf_to_name(pdf_dict_gets(a, "N")); + + if (!strcmp(name, "Print")) + pdf_event_issue_print(doc); + } + } +} + +static void update_text_markup_appearance(pdf_document *doc, pdf_obj *annot, fz_annot_type type) +{ + float color[3]; + float alpha; + float line_height; + float line_thickness; + + switch (type) + { + case FZ_ANNOT_HIGHLIGHT: + color[0] = 1.0; + color[1] = 1.0; + color[2] = 0.0; + alpha = 0.5; + line_thickness = 1.0; + line_height = 0.5; + break; + case FZ_ANNOT_UNDERLINE: + color[0] = 0.0; + color[1] = 0.0; + color[2] = 1.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = UNDERLINE_HEIGHT; + break; + case FZ_ANNOT_STRIKEOUT: + color[0] = 1.0; + color[1] = 0.0; + color[2] = 0.0; + alpha = 1.0; + line_thickness = LINE_THICKNESS; + line_height = STRIKE_HEIGHT; + break; + default: + return; + } + + pdf_set_markup_obj_appearance(doc, annot, color, alpha, line_thickness, line_height); +} + +void pdf_update_appearance(pdf_document *doc, pdf_obj *obj) +{ + if (!pdf_dict_gets(obj, "AP") || pdf_dict_gets(obj, "Dirty")) + { + fz_annot_type type = pdf_annot_obj_type(obj); + switch (type) + { + case FZ_ANNOT_WIDGET: + switch (pdf_field_type(doc, obj)) + { + case PDF_WIDGET_TYPE_TEXT: + { + pdf_obj *formatting = pdf_dict_getp(obj, "AA/F"); + if (formatting && doc->js) + { + /* Apply formatting */ + pdf_js_event e; + + e.target = obj; + e.value = pdf_field_value(doc, obj); + pdf_js_setup_event(doc->js, &e); + execute_action(doc, obj, formatting); + /* Update appearance from JS event.value */ + update_text_appearance(doc, obj, pdf_js_get_event(doc->js)->value); + } + else + { + /* Update appearance from field value */ + update_text_appearance(doc, obj, NULL); + } + } + break; + case PDF_WIDGET_TYPE_PUSHBUTTON: + update_pushbutton_appearance(doc, obj); + break; + case PDF_WIDGET_TYPE_LISTBOX: + case PDF_WIDGET_TYPE_COMBOBOX: + /* Treating listbox and combobox identically for now, + * and the behaviour is most appropriate for a combobox */ + update_combobox_appearance(doc, obj); + break; + } + break; + case FZ_ANNOT_STRIKEOUT: + case FZ_ANNOT_UNDERLINE: + case FZ_ANNOT_HIGHLIGHT: + update_text_markup_appearance(doc, obj, type); + break; + case FZ_ANNOT_INK: + pdf_set_ink_obj_appearance(doc, obj); + break; + default: + break; + } + + pdf_dict_dels(obj, "Dirty"); + } +} + +static void execute_action_chain(pdf_document *doc, pdf_obj *obj) +{ + pdf_obj *a = pdf_dict_gets(obj, "A"); + pdf_js_event e; + + e.target = obj; + e.value = ""; + pdf_js_setup_event(doc->js, &e); + + while (a) + { + execute_action(doc, obj, a); + a = pdf_dict_gets(a, "Next"); + } +} + +static void execute_additional_action(pdf_document *doc, pdf_obj *obj, char *act) +{ + pdf_obj *a = pdf_dict_getp(obj, act); + + if (a) + { + pdf_js_event e; + + e.target = obj; + e.value = ""; + pdf_js_setup_event(doc->js, &e); + execute_action(doc, obj, a); + } +} + +static void check_off(fz_context *ctx, pdf_obj *obj) +{ + pdf_obj *off = NULL; + + fz_var(off); + fz_try(ctx); + { + off = pdf_new_name(ctx, "Off"); + pdf_dict_puts(obj, "AS", off); + } + fz_always(ctx) + { + pdf_drop_obj(off); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void set_check(fz_context *ctx, pdf_obj *chk, char *name) +{ + pdf_obj *n = pdf_dict_getp(chk, "AP/N"); + pdf_obj *val = NULL; + + fz_var(val); + fz_try(ctx) + { + /* If name is a possible value of this check + * box then use it, otherwise use "Off" */ + if (pdf_dict_gets(n, name)) + val = pdf_new_name(ctx, name); + else + val = pdf_new_name(ctx, "Off"); + + pdf_dict_puts(chk, "AS", val); + } + fz_always(ctx) + { + pdf_drop_obj(val); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +/* Set the values of all fields in a group defined by a node + * in the hierarchy */ +static void set_check_grp(fz_context *ctx, pdf_obj *grp, char *val) +{ + pdf_obj *kids = pdf_dict_gets(grp, "Kids"); + + if (kids == NULL) + { + set_check(ctx, grp, val); + } + else + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + set_check_grp(ctx, pdf_array_get(kids, i), val); + } +} + +static void recalculate(pdf_document *doc) +{ + fz_context *ctx = doc->ctx; + + if (doc->recalculating) + return; + + doc->recalculating = 1; + fz_try(ctx) + { + pdf_obj *co = pdf_dict_getp(pdf_trailer(doc), "Root/AcroForm/CO"); + + if (co && doc->js) + { + int i, n = pdf_array_len(co); + + for (i = 0; i < n; i++) + { + pdf_obj *field = pdf_array_get(co, i); + pdf_obj *calc = pdf_dict_getp(field, "AA/C"); + + if (calc) + { + pdf_js_event e; + + e.target = field; + e.value = pdf_field_value(doc, field); + pdf_js_setup_event(doc->js, &e); + execute_action(doc, field, calc); + /* A calculate action, updates event.value. We need + * to place the value in the field */ + update_field_value(doc->ctx, field, pdf_js_get_event(doc->js)->value); + } + } + } + } + fz_always(ctx) + { + doc->recalculating = 0; + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } +} + +static void toggle_check_box(pdf_document *doc, pdf_obj *obj) +{ + fz_context *ctx = doc->ctx; + pdf_obj *as = pdf_dict_gets(obj, "AS"); + int ff = pdf_get_field_flags(doc, obj); + int radio = ((ff & (Ff_Pushbutton|Ff_Radio)) == Ff_Radio); + char *val = NULL; + pdf_obj *grp = radio ? pdf_dict_gets(obj, "Parent") : find_head_of_field_group(obj); + + if (!grp) + grp = obj; + + if (as && strcmp(pdf_to_name(as), "Off")) + { + /* "as" neither missing nor set to Off. Set it to Off, unless + * this is a non-toggle-off radio button. */ + if ((ff & (Ff_Pushbutton|Ff_NoToggleToOff|Ff_Radio)) != (Ff_NoToggleToOff|Ff_Radio)) + { + check_off(ctx, obj); + val = "Off"; + } + } + else + { + pdf_obj *n, *key = NULL; + int len, i; + + n = pdf_dict_getp(obj, "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")) + break; + } + + /* If we found no alternative value to Off then we have no value to use */ + if (!key) + return; + + val = pdf_to_name(key); + + if (radio) + { + /* For radio buttons, first turn off all buttons in the group and + * then set the one that was clicked */ + pdf_obj *kids = pdf_dict_gets(grp, "Kids"); + + len = pdf_array_len(kids); + for (i = 0; i < len; i++) + check_off(ctx, pdf_array_get(kids, i)); + + pdf_dict_puts(obj, "AS", key); + } + else + { + /* For check boxes, we have located the node of the field hierarchy + * below which all fields share a name with the clicked one. Set + * all to the same value. This may cause the group to act like + * radio buttons, if each have distinct "On" values */ + if (grp) + set_check_grp(doc->ctx, grp, val); + else + set_check(doc->ctx, obj, val); + } + } + + if (val && grp) + { + pdf_obj *v = NULL; + + fz_var(v); + fz_try(ctx) + { + v = pdf_new_string(ctx, val, strlen(val)); + pdf_dict_puts(grp, "V", v); + } + fz_always(ctx) + { + pdf_drop_obj(v); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + + recalculate(doc); + } +} + +int pdf_has_unsaved_changes(pdf_document *doc) +{ + return doc->dirty; +} + +int pdf_pass_event(pdf_document *doc, pdf_page *page, pdf_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; + } + + if (annot) + { + int f = pdf_to_int(pdf_dict_gets(annot->obj, "F")); + + if (f & (F_Hidden|F_NoView)) + annot = NULL; + } + + switch (ui_event->etype) + { + case PDF_EVENT_TYPE_POINTER: + { + switch (ui_event->event.pointer.ptype) + { + case PDF_POINTER_DOWN: + if (doc->focus_obj) + { + /* Execute the blur action */ + execute_additional_action(doc, doc->focus_obj, "AA/Bl"); + doc->focus = NULL; + pdf_drop_obj(doc->focus_obj); + doc->focus_obj = NULL; + } + + if (annot) + { + doc->focus = annot; + doc->focus_obj = pdf_keep_obj(annot->obj); + + hp->num = pdf_to_num(annot->obj); + hp->gen = pdf_to_gen(annot->obj); + hp->state = HOTSPOT_POINTER_DOWN; + changed = 1; + /* Exectute the down and focus actions */ + execute_additional_action(doc, annot->obj, "AA/Fo"); + execute_additional_action(doc, annot->obj, "AA/D"); + } + break; + + case PDF_POINTER_UP: + if (hp->state != 0) + changed = 1; + + hp->num = 0; + hp->gen = 0; + hp->state = 0; + + if (annot) + { + switch (annot->widget_type) + { + case PDF_WIDGET_TYPE_RADIOBUTTON: + case PDF_WIDGET_TYPE_CHECKBOX: + /* FIXME: treating radio buttons like check boxes, for now */ + toggle_check_box(doc, annot->obj); + changed = 1; + break; + } + + /* Execute the up action */ + execute_additional_action(doc, annot->obj, "AA/U"); + /* Execute the main action chain */ + execute_action_chain(doc, annot->obj); + } + break; + } + } + break; + } + + return changed; +} + +void pdf_update_page(pdf_document *doc, pdf_page *page) +{ + fz_context *ctx = doc->ctx; + pdf_annot *annot; + + /* Reset changed_annots to empty */ + page->changed_annots = NULL; + + /* + Free all annots in tmp_annots, since these were + referenced only from changed_annots. + */ + if (page->tmp_annots) + { + pdf_free_annot(ctx, page->tmp_annots); + page->tmp_annots = NULL; + } + + /* Add all changed annots to the list */ + for (annot = page->annots; annot; annot = annot->next) + { + pdf_xobject *ap = pdf_keep_xobject(ctx, annot->ap); + int ap_iteration = annot->ap_iteration; + + fz_try(ctx) + { + pdf_update_annot(doc, annot); + + if ((ap != annot->ap || ap_iteration != annot->ap_iteration)) + { + annot->next_changed = page->changed_annots; + page->changed_annots = annot; + } + } + fz_always(ctx) + { + pdf_drop_xobject(ctx, ap); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } + + /* + Add all deleted annots to the list, since these also + warrant a screen update + */ + for (annot = page->deleted_annots; annot; annot = annot->next) + { + annot->next_changed = page->changed_annots; + page->changed_annots = annot; + } + + /* + Move deleted_annots to tmp_annots to keep them separate + from any future deleted ones. They cannot yet be freed + since they are linked into changed_annots + */ + page->tmp_annots = page->deleted_annots; + page->deleted_annots = NULL; +} + +pdf_annot *pdf_poll_changed_annot(pdf_document *idoc, pdf_page *page) +{ + pdf_annot *annot = page->changed_annots; + + if (annot) + page->changed_annots = annot->next_changed; + + return annot; +} + +pdf_widget *pdf_focused_widget(pdf_document *doc) +{ + return (pdf_widget *)doc->focus; +} + +pdf_widget *pdf_first_widget(pdf_document *doc, pdf_page *page) +{ + pdf_annot *annot = page->annots; + + while (annot && annot->widget_type == PDF_WIDGET_TYPE_NOT_WIDGET) + annot = annot->next; + + return (pdf_widget *)annot; +} + +pdf_widget *pdf_next_widget(pdf_widget *previous) +{ + pdf_annot *annot = (pdf_annot *)previous; + + if (annot) + annot = annot->next; + + while (annot && annot->widget_type == PDF_WIDGET_TYPE_NOT_WIDGET) + annot = annot->next; + + return (pdf_widget *)annot; +} + +int pdf_widget_get_type(pdf_widget *widget) +{ + pdf_annot *annot = (pdf_annot *)widget; + return annot->widget_type; +} + +char *pdf_field_value(pdf_document *doc, pdf_obj *field) +{ + return get_string_or_stream(doc, pdf_get_inheritable(doc, field, "V")); +} + +static int set_text_field_value(pdf_document *doc, pdf_obj *field, char *text) +{ + pdf_obj *v = pdf_dict_getp(field, "AA/V"); + + if (v && doc->js) + { + pdf_js_event e; + + e.target = field; + e.value = text; + pdf_js_setup_event(doc->js, &e); + execute_action(doc, field, v); + + if (!pdf_js_get_event(doc->js)->rc) + return 0; + + text = pdf_js_get_event(doc->js)->value; + } + + doc->dirty = 1; + update_field_value(doc->ctx, field, text); + + return 1; +} + +static void update_checkbox_selector(pdf_document *doc, pdf_obj *field, char *val) +{ + fz_context *ctx = doc->ctx; + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + + if (kids) + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + update_checkbox_selector(doc, pdf_array_get(kids, i), val); + } + else + { + pdf_obj *n = pdf_dict_getp(field, "AP/N"); + pdf_obj *oval = NULL; + + fz_var(oval); + fz_try(ctx) + { + if (pdf_dict_gets(n, val)) + oval = pdf_new_name(ctx, val); + else + oval = pdf_new_name(ctx, "Off"); + + pdf_dict_puts(field, "AS", oval); + } + fz_always(ctx) + { + pdf_drop_obj(oval); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } +} + +static int set_checkbox_value(pdf_document *doc, pdf_obj *field, char *val) +{ + update_checkbox_selector(doc, field, val); + update_field_value(doc->ctx, field, val); + return 1; +} + +int pdf_field_set_value(pdf_document *doc, pdf_obj *field, char *text) +{ + int res = 0; + + switch (pdf_field_type(doc, field)) + { + case PDF_WIDGET_TYPE_TEXT: + res = set_text_field_value(doc, field, text); + break; + + case PDF_WIDGET_TYPE_CHECKBOX: + case PDF_WIDGET_TYPE_RADIOBUTTON: + res = set_checkbox_value(doc, field, text); + break; + + default: + /* text updater will do in most cases */ + update_field_value(doc->ctx, field, text); + res = 1; + break; + } + + recalculate(doc); + + return res; +} + +char *pdf_field_border_style(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_set_border_style(pdf_document *doc, pdf_obj *field, char *text) +{ + fz_context *ctx = doc->ctx; + pdf_obj *val = NULL; + + if (!strcmp(text, "Solid")) + val = pdf_new_name(ctx, "S"); + else if (!strcmp(text, "Dashed")) + val = pdf_new_name(ctx, "D"); + else if (!strcmp(text, "Beveled")) + val = pdf_new_name(ctx, "B"); + else if (!strcmp(text, "Inset")) + val = pdf_new_name(ctx, "I"); + else if (!strcmp(text, "Underline")) + val = pdf_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_set_button_caption(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 (pdf_field_type(doc, field) == PDF_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); + } +} + +int pdf_field_display(pdf_document *doc, pdf_obj *field) +{ + pdf_obj *kids; + int f, res = Display_Visible; + + /* Base response on first of children. Not ideal, + * but not clear how to handle children with + * differing values */ + while ((kids = pdf_dict_gets(field, "Kids")) != NULL) + field = pdf_array_get(kids, 0); + + f = pdf_to_int(pdf_dict_gets(field, "F")); + + if (f & F_Hidden) + { + res = Display_Hidden; + } + else if (f & F_Print) + { + if (f & F_NoView) + res = Display_NoView; + } + else + { + if (f & F_NoView) + res = Display_Hidden; + else + res = Display_NoPrint; + } + + return res; +} + +/* + * get the field name in a char buffer that has spare room to + * add more characters at the end. + */ +static char *get_field_name(pdf_document *doc, pdf_obj *field, int spare) +{ + fz_context *ctx = doc->ctx; + char *res = NULL; + pdf_obj *parent = pdf_dict_gets(field, "Parent"); + char *lname = pdf_to_str_buf(pdf_dict_gets(field, "T")); + int llen = strlen(lname); + + /* + * If we found a name at this point in the field hierarchy + * then we'll need extra space for it and a dot + */ + if (llen) + spare += llen+1; + + if (parent) + { + res = get_field_name(doc, parent, spare); + } + else + { + res = fz_malloc(ctx, spare+1); + res[0] = 0; + } + + if (llen) + { + if (res[0]) + strcat(res, "."); + + strcat(res, lname); + } + + return res; +} + +char *pdf_field_name(pdf_document *doc, pdf_obj *field) +{ + return get_field_name(doc, field, 0); +} + +void pdf_field_set_display(pdf_document *doc, pdf_obj *field, int d) +{ + fz_context *ctx = doc->ctx; + pdf_obj *kids = pdf_dict_gets(field, "Kids"); + + if (!kids) + { + int mask = (F_Hidden|F_Print|F_NoView); + int f = pdf_to_int(pdf_dict_gets(field, "F")) & ~mask; + pdf_obj *fo = NULL; + + switch (d) + { + case Display_Visible: + f |= F_Print; + break; + case Display_Hidden: + f |= F_Hidden; + break; + case Display_NoView: + f |= (F_Print|F_NoView); + break; + case Display_NoPrint: + break; + } + + fz_var(fo); + fz_try(ctx) + { + fo = pdf_new_int(ctx, f); + pdf_dict_puts(field, "F", fo); + } + fz_always(ctx) + { + pdf_drop_obj(fo); + } + fz_catch(ctx) + { + fz_rethrow(ctx); + } + } + else + { + int i, n = pdf_array_len(kids); + + for (i = 0; i < n; i++) + pdf_field_set_display(doc, pdf_array_get(kids, i), d); + } +} + +void pdf_field_set_fill_color(pdf_document *doc, pdf_obj *field, pdf_obj *col) +{ + /* col == NULL mean transparent, but we can simply pass it on as with + * non-NULL values because pdf_dict_putp interprets a NULL value as + * delete */ + pdf_dict_putp(field, "MK/BG", col); + pdf_field_mark_dirty(doc->ctx, field); +} + +void pdf_field_set_text_color(pdf_document *doc, pdf_obj *field, pdf_obj *col) +{ + fz_context *ctx = doc->ctx; + da_info di; + fz_buffer *fzbuf = NULL; + char *da = pdf_to_str_buf(pdf_get_inheritable(doc, field, "DA")); + unsigned char *buf; + int len; + pdf_obj *daobj = NULL; + + memset(&di, 0, sizeof(di)); + + fz_var(fzbuf); + fz_var(di); + fz_var(daobj); + fz_try(ctx) + { + int i; + + parse_da(ctx, da, &di); + di.col_size = pdf_array_len(col); + + len = fz_mini(di.col_size, nelem(di.col)); + for (i = 0; i < len; i++) + di.col[i] = pdf_to_real(pdf_array_get(col, i)); + + fzbuf = fz_new_buffer(ctx, 0); + fzbuf_print_da(ctx, fzbuf, &di); + len = fz_buffer_storage(ctx, fzbuf, &buf); + daobj = pdf_new_string(ctx, (char *)buf, len); + pdf_dict_puts(field, "DA", daobj); + pdf_field_mark_dirty(ctx, field); + } + fz_always(ctx) + { + da_info_fin(ctx, &di); + fz_drop_buffer(ctx, fzbuf); + pdf_drop_obj(daobj); + } + fz_catch(ctx) + { + fz_warn(ctx, "%s", fz_caught_message(ctx)); + } +} + +fz_rect *pdf_bound_widget(pdf_widget *widget, fz_rect *rect) +{ + pdf_annot *annot = (pdf_annot *)widget; + + if (rect == NULL) + return NULL; + *rect = annot->pagerect; + + return rect; +} + +char *pdf_text_widget_text(pdf_document *doc, pdf_widget *tw) +{ + pdf_annot *annot = (pdf_annot *)tw; + fz_context *ctx = doc->ctx; + char *text = NULL; + + fz_var(text); + fz_try(ctx) + { + text = pdf_field_value(doc, annot->obj); + } + fz_catch(ctx) + { + fz_warn(ctx, "failed allocation in fz_text_widget_text"); + } + + return text; +} + +int pdf_text_widget_max_len(pdf_document *doc, pdf_widget *tw) +{ + pdf_annot *annot = (pdf_annot *)tw; + + return pdf_to_int(pdf_get_inheritable(doc, annot->obj, "MaxLen")); +} + +int pdf_text_widget_content_type(pdf_document *doc, pdf_widget *tw) +{ + pdf_annot *annot = (pdf_annot *)tw; + fz_context *ctx = doc->ctx; + char *code = NULL; + int type = PDF_WIDGET_CONTENT_UNRESTRAINED; + + fz_var(code); + fz_try(ctx) + { + code = get_string_or_stream(doc, pdf_dict_getp(annot->obj, "AA/F/JS")); + if (code) + { + if (strstr(code, "AFNumber_Format")) + type = PDF_WIDGET_CONTENT_NUMBER; + else if (strstr(code, "AFSpecial_Format")) + type = PDF_WIDGET_CONTENT_SPECIAL; + else if (strstr(code, "AFDate_FormatEx")) + type = PDF_WIDGET_CONTENT_DATE; + else if (strstr(code, "AFTime_FormatEx")) + type = PDF_WIDGET_CONTENT_TIME; + } + } + fz_always(ctx) + { + fz_free(ctx, code); + } + fz_catch(ctx) + { + fz_warn(ctx, "failure in fz_text_widget_content_type"); + } + + return type; +} + +static int run_keystroke(pdf_document *doc, pdf_obj *field, char **text) +{ + pdf_obj *k = pdf_dict_getp(field, "AA/K"); + + if (k && doc->js) + { + pdf_js_event e; + + e.target = field; + e.value = *text; + pdf_js_setup_event(doc->js, &e); + execute_action(doc, field, k); + + if (!pdf_js_get_event(doc->js)->rc) + return 0; + + *text = pdf_js_get_event(doc->js)->value; + } + + return 1; +} + +int pdf_text_widget_set_text(pdf_document *doc, pdf_widget *tw, char *text) +{ + pdf_annot *annot = (pdf_annot *)tw; + fz_context *ctx = doc->ctx; + int accepted = 0; + + fz_try(ctx) + { + accepted = run_keystroke(doc, annot->obj, &text); + if (accepted) + accepted = pdf_field_set_value(doc, annot->obj, text); + } + fz_catch(ctx) + { + fz_warn(ctx, "fz_text_widget_set_text failed"); + } + + return accepted; +} + +int pdf_choice_widget_options(pdf_document *doc, pdf_widget *tw, char *opts[]) +{ + pdf_annot *annot = (pdf_annot *)tw; + pdf_obj *optarr; + int i, n; + + if (!annot) + return 0; + + optarr = pdf_dict_gets(annot->obj, "Opt"); + n = pdf_array_len(optarr); + + if (opts) + { + for (i = 0; i < n; i++) + { + opts[i] = pdf_to_str_buf(pdf_array_get(optarr, i)); + } + } + + return n; +} + +int pdf_choice_widget_is_multiselect(pdf_document *doc, pdf_widget *tw) +{ + pdf_annot *annot = (pdf_annot *)tw; + + if (!annot) return 0; + + switch (pdf_field_type(doc, annot->obj)) + { + case PDF_WIDGET_TYPE_LISTBOX: + case PDF_WIDGET_TYPE_COMBOBOX: + return (pdf_get_field_flags(doc, annot->obj) & Ff_MultiSelect) != 0; + default: + return 0; + } +} + +int pdf_choice_widget_value(pdf_document *doc, pdf_widget *tw, char *opts[]) +{ + pdf_annot *annot = (pdf_annot *)tw; + pdf_obj *optarr; + int i, n; + + if (!annot) + return 0; + + optarr = pdf_dict_gets(annot->obj, "V"); + + if (pdf_is_string(optarr)) + { + if (opts) + opts[0] = pdf_to_str_buf(optarr); + + return 1; + } + else + { + n = pdf_array_len(optarr); + + if (opts) + { + for (i = 0; i < n; i++) + { + pdf_obj *elem = pdf_array_get(optarr, i); + + if (pdf_is_array(elem)) + elem = pdf_array_get(elem, 1); + + opts[i] = pdf_to_str_buf(elem); + } + } + + return n; + } +} + +void pdf_choice_widget_set_value(pdf_document *doc, pdf_widget *tw, int n, char *opts[]) +{ + fz_context *ctx = doc->ctx; + pdf_annot *annot = (pdf_annot *)tw; + pdf_obj *optarr = NULL, *opt = NULL; + int i; + + if (!annot) + return; + + fz_var(optarr); + fz_var(opt); + fz_try(ctx) + { + if (n != 1) + { + optarr = pdf_new_array(ctx, n); + + for (i = 0; i < n; i++) + { + opt = pdf_new_string(ctx, opts[i], strlen(opts[i])); + pdf_array_push(optarr, opt); + pdf_drop_obj(opt); + opt = NULL; + } + + pdf_dict_puts(annot->obj, "V", optarr); + pdf_drop_obj(optarr); + } + else + { + opt = pdf_new_string(ctx, opts[0], strlen(opts[0])); + pdf_dict_puts(annot->obj, "V", opt); + pdf_drop_obj(opt); + } + + /* FIXME: when n > 1, we should be regenerating the indexes */ + pdf_dict_dels(annot->obj, "I"); + + pdf_field_mark_dirty(ctx, annot->obj); + doc->dirty = 1; + } + fz_catch(ctx) + { + pdf_drop_obj(optarr); + pdf_drop_obj(opt); + fz_rethrow(ctx); + } +} + +int pdf_signature_widget_byte_range(pdf_document *doc, pdf_widget *widget, int (*byte_range)[2]) +{ + pdf_annot *annot = (pdf_annot *)widget; + pdf_obj *br = pdf_dict_getp(annot->obj, "V/ByteRange"); + int i, n = pdf_array_len(br)/2; + + if (byte_range) + { + for (i = 0; i < n; i++) + { + byte_range[i][0] = pdf_to_int(pdf_array_get(br, 2*i)); + byte_range[i][1] = pdf_to_int(pdf_array_get(br, 2*i+1)); + } + } + + return n; +} + +int pdf_signature_widget_contents(pdf_document *doc, pdf_widget *widget, char **contents) +{ + pdf_annot *annot = (pdf_annot *)widget; + pdf_obj *c = pdf_dict_getp(annot->obj, "V/Contents"); + if (contents) + *contents = pdf_to_str_buf(c); + return pdf_to_str_len(c); +} |