summaryrefslogtreecommitdiff
path: root/source/pdf/pdf-form.c
diff options
context:
space:
mode:
authorTor Andersson <tor.andersson@artifex.com>2013-06-19 15:29:44 +0200
committerTor Andersson <tor.andersson@artifex.com>2013-06-20 16:45:35 +0200
commit0a927854a10e1e6b9770a81e2e1d9f3093631757 (patch)
tree3d65d820d9fdba2d0d394d99c36290c851b78ca0 /source/pdf/pdf-form.c
parent1ae8f19179c5f0f8c6352b3c7855465325d5449a (diff)
downloadmupdf-0a927854a10e1e6b9770a81e2e1d9f3093631757.tar.xz
Rearrange source files.
Diffstat (limited to 'source/pdf/pdf-form.c')
-rw-r--r--source/pdf/pdf-form.c2876
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);
+}