static pdf_annot * pdf_create_highlight_annot(pdf_xref *xref, fz_obj *obj) { fz_context *ctx = xref->ctx; fz_buffer *content = fz_new_buffer(ctx, 512); fz_rect rect = pdf_to_rect(ctx, fz_dict_gets(ctx, obj, "Rect")); fz_obj *quad_points = fz_dict_gets(ctx, obj, "QuadPoints"); fz_obj *resources = pdf_dict_from_string(xref, ANNOT_HIGHLIGHT_AP_RESOURCES); fz_rect a, b; float rgb[3]; float skew; int i; for (i = 0; i < fz_array_len(ctx, quad_points) / 8; i++) { pdf_get_quadrilaterals(ctx, quad_points, i, &a, &b); skew = 0.15 * fabs(a.y0 - b.y0); b.x0 -= skew; b.x1 += skew; rect = fz_union_rect(rect, fz_union_rect(a, b)); } pdf_get_annot_color(ctx, obj, rgb); fz_buffer_printf(ctx, content, "q /GS gs %.4f %.4f %.4f rg 1 0 0 1 -%.4f -%.4f cm ", rgb[0], rgb[1], rgb[2], rect.x0, rect.y0); for (i = 0; i < fz_array_len(ctx, quad_points) / 8; i++) { pdf_get_quadrilaterals(ctx, quad_points, i, &a, &b); skew = 0.15 * fabs(a.y0 - b.y0); fz_buffer_printf(ctx, content, "%.4f %.4f m %.4f %.4f l %.4f %.4f l %.4f %.4f l h ", a.x0, a.y0, b.x1 + skew, b.y1, a.x1, a.y1, b.x0 - skew, b.y0); } fz_buffer_printf(ctx, content, "f Q"); return pdf_create_annot(ctx, rect, fz_keep_obj(obj), content, resources, 1); }
/* SumatraPDF: partial support for file attachment icons */ static pdf_annot * pdf_create_file_annot(pdf_xref *xref, fz_obj *obj) { fz_buffer *content = fz_new_buffer(xref->ctx, 512); fz_rect rect = pdf_to_rect(xref->ctx, fz_dict_gets(xref->ctx, obj, "Rect")); char *icon_name = fz_to_name(xref->ctx, fz_dict_gets(xref->ctx, obj, "Name")); char *content_ap = ANNOT_FILE_ATTACHMENT_AP_PUSHPIN; float rgb[3]; pdf_get_annot_color(xref->ctx, obj, rgb); if (!strcmp(icon_name, "Graph")) content_ap = ANNOT_FILE_ATTACHMENT_AP_GRAPH; else if (!strcmp(icon_name, "Paperclip")) content_ap = ANNOT_FILE_ATTACHMENT_AP_PAPERCLIP; else if (!strcmp(icon_name, "Tag")) content_ap = ANNOT_FILE_ATTACHMENT_AP_TAG; fz_buffer_printf(xref->ctx, content, "q %.4f 0 0 %.4f 0 0 cm ", (rect.x1 - rect.x0) / 24, (rect.y1 - rect.y0) / 24); fz_buffer_printf(xref->ctx, content, content_ap, 0.5, 0.5, 0.5); fz_buffer_printf(xref->ctx, content, " 1 0 0 1 0 1 cm "); fz_buffer_printf(xref->ctx, content, content_ap, rgb[0], rgb[1], rgb[2]); fz_buffer_printf(xref->ctx, content, " Q", content_ap); obj = pdf_clone_for_view_only(xref, obj); return pdf_create_annot(xref->ctx, rect, obj, content, NULL, 0); }
/* SumatraPDF: partial support for link borders */ static pdf_annot * pdf_create_link_annot(pdf_xref *xref, fz_obj *obj) { fz_obj *border, *dashes; fz_buffer *content; fz_rect rect; float rgb[3]; int i; border = fz_dict_gets(xref->ctx, obj, "Border"); if (fz_to_real(xref->ctx, fz_array_get(xref->ctx, border, 2)) <= 0) return NULL; pdf_get_annot_color(xref->ctx, obj, rgb); dashes = fz_array_get(xref->ctx, border, 3); rect = pdf_to_rect(xref->ctx, fz_dict_gets(xref->ctx, obj, "Rect")); obj = pdf_clone_for_view_only(xref, obj); // TODO: draw rounded rectangles if the first two /Border values are non-zero content = fz_new_buffer(xref->ctx, 128); fz_buffer_printf(xref->ctx, content, "q %.4f w [", fz_to_real(xref->ctx, fz_array_get(xref->ctx, border, 2))); for (i = 0; i < fz_array_len(xref->ctx, dashes); i++) fz_buffer_printf(xref->ctx, content, "%.4f ", fz_to_real(xref->ctx, fz_array_get(xref->ctx, dashes, i))); fz_buffer_printf(xref->ctx, content, "] 0 d %.4f %.4f %.4f RG 0 0 %.4f %.4f re S Q", rgb[0], rgb[1], rgb[2], rect.x1 - rect.x0, rect.y1 - rect.y0); return pdf_create_annot(xref->ctx, rect, obj, content, NULL, 0); }
static void pdf_string_to_Tj(fz_context *ctx, fz_buffer *content, unsigned short *ucs2, unsigned short *end) { fz_buffer_printf(ctx, content, "("); for (; ucs2 < end; ucs2++) { // TODO: convert to CID(?) if (*ucs2 < 0x20 || *ucs2 == '(' || *ucs2 == ')' || *ucs2 == '\\') fz_buffer_printf(ctx, content, "\\%03o", *ucs2); else fz_buffer_printf(ctx, content, "%c", *ucs2); } fz_buffer_printf(ctx, content, ") Tj "); }
void pdf_print_token(fz_context *ctx, fz_buffer *fzbuf, int tok, pdf_lexbuf *buf) { switch (tok) { case PDF_TOK_NAME: fz_buffer_printf(ctx, fzbuf, "/%s", buf->scratch); break; case PDF_TOK_STRING: if (buf->len >= buf->size) pdf_lexbuf_grow(buf); buf->scratch[buf->len] = 0; fz_buffer_cat_pdf_string(ctx, fzbuf, buf->scratch); break; case PDF_TOK_OPEN_DICT: fz_buffer_printf(ctx, fzbuf, "<<"); break; case PDF_TOK_CLOSE_DICT: fz_buffer_printf(ctx, fzbuf, ">>"); break; case PDF_TOK_OPEN_ARRAY: fz_buffer_printf(ctx, fzbuf, "["); break; case PDF_TOK_CLOSE_ARRAY: fz_buffer_printf(ctx, fzbuf, "]"); break; case PDF_TOK_OPEN_BRACE: fz_buffer_printf(ctx, fzbuf, "{"); break; case PDF_TOK_CLOSE_BRACE: fz_buffer_printf(ctx, fzbuf, "}"); break; case PDF_TOK_INT: fz_buffer_printf(ctx, fzbuf, "%d", buf->i); break; case PDF_TOK_REAL: { char sbuf[256]; sprintf(sbuf, "%g", buf->f); if (strchr(sbuf, 'e')) /* bad news! */ sprintf(sbuf, fabsf(buf->f) > 1 ? "%1.1f" : "%1.8f", buf->f); fz_buffer_printf(ctx, fzbuf, "%s", sbuf); } break; default: fz_buffer_printf(ctx, fzbuf, "%s", buf->scratch); break; } }
static void pdf_dev_path_curveto(fz_context *ctx, void *arg, float x1, float y1, float x2, float y2, float x3, float y3) { fz_buffer *buf = (fz_buffer *)arg; fz_buffer_printf(ctx, buf, "%f %f %f %f %f %f c\n", x1, y1, x2, y2, x3, y3); }
static void pdf_dev_path_close(fz_context *ctx, void *arg) { fz_buffer *buf = (fz_buffer *)arg; fz_buffer_printf(ctx, buf, "h\n"); }
static void pdf_dev_path_lineto(fz_context *ctx, void *arg, float x, float y) { fz_buffer *buf = (fz_buffer *)arg; fz_buffer_printf(ctx, buf, "%f %f l\n", x, y); }
void pdf_print_token(fz_context *ctx, fz_buffer *fzbuf, int tok, pdf_lexbuf *buf) { switch (tok) { case PDF_TOK_NAME: fz_buffer_printf(ctx, fzbuf, "/%s", buf->scratch); break; case PDF_TOK_STRING: if (buf->len >= buf->size) pdf_lexbuf_grow(buf); buf->scratch[buf->len] = 0; fz_buffer_cat_pdf_string(ctx, fzbuf, buf->scratch); break; case PDF_TOK_OPEN_DICT: fz_buffer_printf(ctx, fzbuf, "<<"); break; case PDF_TOK_CLOSE_DICT: fz_buffer_printf(ctx, fzbuf, ">>"); break; case PDF_TOK_OPEN_ARRAY: fz_buffer_printf(ctx, fzbuf, "["); break; case PDF_TOK_CLOSE_ARRAY: fz_buffer_printf(ctx, fzbuf, "]"); break; case PDF_TOK_OPEN_BRACE: fz_buffer_printf(ctx, fzbuf, "{"); break; case PDF_TOK_CLOSE_BRACE: fz_buffer_printf(ctx, fzbuf, "}"); break; case PDF_TOK_INT: fz_buffer_printf(ctx, fzbuf, "%d", buf->i); break; case PDF_TOK_REAL: { fz_buffer_printf(ctx, fzbuf, "%g", buf->f); } break; default: fz_buffer_printf(ctx, fzbuf, "%s", buf->scratch); break; } }
static pdf_annot * pdf_create_freetext_annot(pdf_xref *xref, fz_obj *obj) { fz_context *ctx = xref->ctx; fz_buffer *content = fz_new_buffer(ctx, 256); fz_buffer *base_ap = fz_new_buffer(ctx, 256); fz_obj *ap = fz_dict_gets(ctx, obj, "DA"); fz_obj *value = fz_dict_gets(ctx, obj, "Contents"); fz_rect rect = pdf_to_rect(ctx, fz_dict_gets(ctx, obj, "Rect")); int align = fz_to_int(ctx, fz_dict_gets(ctx, obj, "Q")); fz_obj *res = pdf_dict_from_string(xref, ANNOT_FREETEXT_AP_RESOURCES); unsigned short *ucs2, *rest; float x; char *font_name = NULL; float font_size = pdf_extract_font_size(xref, fz_to_str_buf(ctx, ap), &font_name); if (!font_size) font_size = 10; /* TODO: what resource dictionary does this font name refer to? */ if (font_name) { fz_obj *font = fz_dict_gets(ctx, res, "Font"); fz_dict_puts(ctx, font, font_name, fz_dict_gets(ctx, font, "Default")); fz_free(ctx, font_name); } fz_buffer_printf(ctx, content, "q 1 1 %.4f %.4f re W n BT %s ", rect.x1 - rect.x0 - 2.0f, rect.y1 - rect.y0 - 2.0f, fz_to_str_buf(ctx, ap)); fz_buffer_printf(ctx, base_ap, "q BT %s ", fz_to_str_buf(ctx, ap)); fz_buffer_printf(ctx, content, "/Default %.4f Tf ", font_size); fz_buffer_printf(ctx, base_ap, "/Default %.4f Tf ", font_size); fz_buffer_printf(ctx, content, "1 0 0 1 2 %.4f Tm ", rect.y1 - rect.y0 - 2); /* Adobe Reader seems to consider "[1 0 0] r" and "1 0 0 rg" to mean the same(?) */ if (strchr(base_ap->data, '[')) { float r, g, b; if (sscanf(strchr(base_ap->data, '['), "[%f %f %f] r", &r, &g, &b) == 3) fz_buffer_printf(ctx, content, "%.4f %.4f %.4f rg ", r, g, b); } ucs2 = pdf_to_ucs2(ctx, value); for (rest = ucs2; *rest; rest++) if (*rest > 0xFF) *rest = '?'; x = 0; rest = ucs2; while (*rest) rest = pdf_append_line(xref, res, content, base_ap, rest, font_size, align, rect.x1 - rect.x0 - 4.0f, 1, &x); fz_free(ctx, ucs2); fz_buffer_printf(ctx, content, "ET Q"); fz_drop_buffer(ctx, base_ap); return pdf_create_annot(ctx, rect, fz_keep_obj(obj), content, res, 0); }
/* SumatraPDF: partial support for text icons */ static pdf_annot * pdf_create_text_annot(pdf_xref *xref, fz_obj *obj) { fz_context *ctx = xref->ctx; fz_buffer *content = fz_new_buffer(ctx, 512); fz_rect rect = pdf_to_rect(ctx, fz_dict_gets(ctx, obj, "Rect")); char *icon_name = fz_to_name(ctx, fz_dict_gets(ctx, obj, "Name")); char *content_ap = ANNOT_TEXT_AP_NOTE; float rgb[3]; rect.x1 = rect.x0 + 24; rect.y0 = rect.y1 - 24; pdf_get_annot_color(ctx, obj, rgb); if (!strcmp(icon_name, "Comment")) content_ap = ANNOT_TEXT_AP_COMMENT; else if (!strcmp(icon_name, "Key")) content_ap = ANNOT_TEXT_AP_KEY; else if (!strcmp(icon_name, "Help")) content_ap = ANNOT_TEXT_AP_HELP; else if (!strcmp(icon_name, "Paragraph")) content_ap = ANNOT_TEXT_AP_PARAGRAPH; else if (!strcmp(icon_name, "NewParagraph")) content_ap = ANNOT_TEXT_AP_NEW_PARAGRAPH; else if (!strcmp(icon_name, "Insert")) content_ap = ANNOT_TEXT_AP_INSERT; else if (!strcmp(icon_name, "Cross")) content_ap = ANNOT_TEXT_AP_CROSS; else if (!strcmp(icon_name, "Circle")) content_ap = ANNOT_TEXT_AP_CIRCLE; // TODO: make icons semi-transparent (cf. pdf_create_highlight_annot)? fz_buffer_printf(ctx, content, "q "); fz_buffer_printf(ctx, content, content_ap, 0.5, 0.5, 0.5); fz_buffer_printf(ctx, content, " 1 0 0 1 0 1 cm "); fz_buffer_printf(ctx, content, content_ap, rgb[0], rgb[1], rgb[2]); fz_buffer_printf(ctx, content, " Q", content_ap); obj = pdf_clone_for_view_only(xref, obj); return pdf_create_annot(ctx, rect, obj, content, NULL, 0); }
static void pdf_dev_ctm(fz_context *ctx, pdf_device *pdev, const fz_matrix *ctm) { fz_matrix inverse; gstate *gs = CURRENT_GSTATE(pdev); if (memcmp(&gs->ctm, ctm, sizeof(*ctm)) == 0) return; fz_invert_matrix(&inverse, &gs->ctm); fz_concat(&inverse, ctm, &inverse); gs->ctm = *ctm; fz_buffer_printf(ctx, gs->buf, "%M cm\n", &inverse); }
static pdf_annot * pdf_create_markup_annot(pdf_xref *xref, fz_obj *obj, char *type) { fz_context *ctx = xref->ctx; fz_buffer *content = fz_new_buffer(ctx, 512); fz_rect rect = pdf_to_rect(ctx, fz_dict_gets(ctx, obj, "Rect")); fz_obj *quad_points = fz_dict_gets(ctx, obj, "QuadPoints"); fz_rect a, b; float rgb[3]; int i; for (i = 0; i < fz_array_len(ctx, quad_points) / 8; i++) { pdf_get_quadrilaterals(ctx, quad_points, i, &a, &b); b.y0 -= 0.25; a.y1 += 0.25; rect = fz_union_rect(rect, fz_union_rect(a, b)); } pdf_get_annot_color(ctx, obj, rgb); fz_buffer_printf(ctx, content, "q %.4f %.4f %.4f RG 1 0 0 1 -%.4f -%.4f cm 0.5 w ", rgb[0], rgb[1], rgb[2], rect.x0, rect.y0); if (!strcmp(type, "Squiggly")) fz_buffer_printf(ctx, content, "[1 1] d "); for (i = 0; i < fz_array_len(ctx, quad_points) / 8; i++) { pdf_get_quadrilaterals(ctx, quad_points, i, &a, &b); if (!strcmp(type, "StrikeOut")) fz_buffer_printf(ctx, content, "%.4f %.4f m %.4f %.4f l ", (a.x0 + b.x0) / 2, (a.y0 + b.y0) / 2, (a.x1 + b.x1) / 2, (a.y1 + b.y1) / 2); else fz_buffer_printf(ctx, content, "%.4f %.4f m %.4f %.4f l ", b.x0, b.y0, a.x1, a.y1); } fz_buffer_printf(ctx, content, "S Q"); return pdf_create_annot(ctx, rect, fz_keep_obj(obj), content, NULL, 0); }
static int pdf_get_string_width(pdf_xref *xref, fz_obj *res, fz_buffer *base, unsigned short *string, unsigned short *end) { fz_bbox bbox; fz_error error; int width, old_len = base->len; fz_device *dev = fz_new_bbox_device(xref->ctx, &bbox); pdf_string_to_Tj(xref->ctx, base, string, end); fz_buffer_printf(xref->ctx, base, "ET Q EMC"); error = pdf_run_glyph(xref, res, base, dev, fz_identity); width = error ? -1 : bbox.x1 - bbox.x0; base->len = old_len; fz_free_device(dev); return width; }
static void pdf_append_combed_line(pdf_xref *xref, fz_obj *res, fz_buffer *content, fz_buffer *base_ap, unsigned short *ucs2, float font_size, float width, int max_len) { float comb_width = max_len > 0 ? width / max_len : 0; unsigned short c[2] = { 0 }; float x = -2.0f; int i; fz_buffer_printf(xref->ctx, content, "0 %.4f Td ", -font_size); for (i = 0; i < max_len && ucs2[i]; i++) { *c = ucs2[i]; pdf_append_line(xref, res, content, base_ap, c, 0, 1 /* centered */, comb_width, 0, &x); x -= comb_width; } }
/* Helper functions */ static void pdf_dev_stroke_state(fz_context *ctx, pdf_device *pdev, const fz_stroke_state *stroke_state) { gstate *gs = CURRENT_GSTATE(pdev); if (stroke_state == gs->stroke_state) return; if (gs->stroke_state && !memcmp(stroke_state, gs->stroke_state, sizeof(*stroke_state))) return; if (!gs->stroke_state || gs->stroke_state->linewidth != stroke_state->linewidth) { fz_buffer_printf(ctx, gs->buf, "%f w\n", stroke_state->linewidth); } if (!gs->stroke_state || gs->stroke_state->start_cap != stroke_state->start_cap) { int cap = stroke_state->start_cap; /* FIXME: Triangle caps aren't supported in pdf */ if (cap == FZ_LINECAP_TRIANGLE) cap = FZ_LINECAP_BUTT; fz_buffer_printf(ctx, gs->buf, "%d J\n", cap); } if (!gs->stroke_state || gs->stroke_state->linejoin != stroke_state->linejoin) { int join = stroke_state->linejoin; if (join == FZ_LINEJOIN_MITER_XPS) join = FZ_LINEJOIN_MITER; fz_buffer_printf(ctx, gs->buf, "%d j\n", join); } if (!gs->stroke_state || gs->stroke_state->miterlimit != stroke_state->miterlimit) { fz_buffer_printf(ctx, gs->buf, "%f M\n", stroke_state->miterlimit); } if (gs->stroke_state == NULL && stroke_state->dash_len == 0) {} else if (!gs->stroke_state || gs->stroke_state->dash_phase != stroke_state->dash_phase || gs->stroke_state->dash_len != stroke_state->dash_len || memcmp(gs->stroke_state->dash_list, stroke_state->dash_list, sizeof(float)*stroke_state->dash_len)) { int i; if (stroke_state->dash_len == 0) fz_buffer_printf(ctx, gs->buf, "["); for (i = 0; i < stroke_state->dash_len; i++) fz_buffer_printf(ctx, gs->buf, "%c%f", (i == 0 ? '[' : ' '), stroke_state->dash_list[i]); fz_buffer_printf(ctx, gs->buf, "]%f d\n", stroke_state->dash_phase); } fz_drop_stroke_state(ctx, gs->stroke_state); gs->stroke_state = fz_keep_stroke_state(ctx, stroke_state); }
static unsigned short * pdf_append_line(pdf_xref *xref, fz_obj *res, fz_buffer *content, fz_buffer *base_ap, unsigned short *ucs2, float font_size, int align, float width, int is_multiline, float *x) { unsigned short *end, *keep; float x1 = 0; int w; if (is_multiline) { end = ucs2; do { if (*end == '\n' || *end == '\r' && *(end + 1) != '\n') break; for (keep = end + 1; *keep && !iswspace(*keep); keep++); w = pdf_get_string_width(xref, res, base_ap, ucs2, keep); if (w <= width || end == ucs2) end = keep; } while (w <= width && *end); } else end = ucs2 + wcslen(ucs2); if (align != 0) { w = pdf_get_string_width(xref, res, base_ap, ucs2, end); if (w < 0) fz_error_handle(xref->ctx, -1, "can't change the text's alignment"); else if (align == 1 /* centered */) x1 = (width - w) / 2; else if (align == 2 /* right-aligned */) x1 = width - w; else fz_warn(xref->ctx, "ignoring unknown quadding value %d", align); } fz_buffer_printf(xref->ctx, content, "%.4f %.4f Td ", x1 - *x, -font_size); pdf_string_to_Tj(xref->ctx, content, ucs2, end); *x = x1; return end + (*end ? 1 : 0); }
static int make_fake_doc(pdfapp_t *app) { fz_context *ctx = app->ctx; pdf_document *pdf = NULL; fz_buffer *contents = NULL; fz_try(ctx) { fz_rect mediabox = { 0, 0, app->winw, app->winh }; pdf_obj *page_obj; int i; contents = fz_new_buffer(ctx, 100); pdf = pdf_create_document(ctx); app->doc = (fz_document*)pdf; fz_buffer_printf(ctx, contents, "1 0 0 rg %f w 0 0 m %f %f l 0 %f m %f 0 l\n", fz_min(mediabox.x1, mediabox.y1) / 4, mediabox.x1, mediabox.y1, mediabox.y1, mediabox.x1); /* Create enough copies of our blank(ish) page so that the * page number is preserved if and when a subsequent load * works. */ page_obj = pdf_add_page(ctx, pdf, &mediabox, 0, contents, NULL); for (i = 0; i < app->pagecount; i++) pdf_insert_page(ctx, pdf, -1, page_obj); pdf_drop_obj(ctx, page_obj); } fz_always(ctx) { fz_drop_buffer(ctx, contents); } fz_catch(ctx) { fz_rethrow(ctx); } return 0; }
static void pdf_dev_color(fz_context *ctx, pdf_device *pdev, fz_colorspace *colorspace, const float *color, int stroke) { int diff = 0; int i; int cspace = 0; float rgb[FZ_MAX_COLORS]; gstate *gs = CURRENT_GSTATE(pdev); if (colorspace == fz_device_gray(ctx)) cspace = 1; else if (colorspace == fz_device_rgb(ctx)) cspace = 3; else if (colorspace == fz_device_cmyk(ctx)) cspace = 4; if (cspace == 0) { /* If it's an unknown colorspace, fallback to rgb */ colorspace->to_rgb(ctx, colorspace, color, rgb); color = rgb; colorspace = fz_device_rgb(ctx); } if (gs->colorspace[stroke] != colorspace) { gs->colorspace[stroke] = colorspace; diff = 1; } for (i=0; i < colorspace->n; i++) if (gs->color[stroke][i] != color[i]) { gs->color[stroke][i] = color[i]; diff = 1; } if (diff == 0) return; switch (cspace + stroke*8) { case 1: fz_buffer_printf(ctx, gs->buf, "%f g\n", color[0]); break; case 3: fz_buffer_printf(ctx, gs->buf, "%f %f %f rg\n", color[0], color[1], color[2]); break; case 4: fz_buffer_printf(ctx, gs->buf, "%f %f %f %f k\n", color[0], color[1], color[2], color[3]); break; case 1+8: fz_buffer_printf(ctx, gs->buf, "%f G\n", color[0]); break; case 3+8: fz_buffer_printf(ctx, gs->buf, "%f %f %f RG\n", color[0], color[1], color[2]); break; case 4+8: fz_buffer_printf(ctx, gs->buf, "%f %f %f %f K\n", color[0], color[1], color[2], color[3]); break; } }
static pdf_annot * pdf_update_tx_widget_annot(pdf_xref *xref, fz_obj *obj) { fz_obj *ap, *res, *value; fz_rect rect; fz_buffer *content, *base_ap; int flags, align, rotate, is_multiline; float font_size, x, y; char *font_name; unsigned short *ucs2, *rest; fz_context *ctx = xref->ctx; if (strcmp(fz_to_name(ctx, fz_dict_gets(ctx, obj, "Subtype")), "Widget") != 0) return NULL; if (!fz_to_bool(ctx, pdf_dict_get_inheritable(xref, NULL, "NeedAppearances")) && pdf_get_ap_stream(xref, obj)) return NULL; value = pdf_dict_get_inheritable(xref, obj, "FT"); if (strcmp(fz_to_name(ctx, value), "Tx") != 0) return NULL; ap = pdf_dict_get_inheritable(xref, obj, "DA"); value = pdf_dict_get_inheritable(xref, obj, "V"); if (!ap || !value) return NULL; res = pdf_dict_get_inheritable(xref, obj, "DR"); rect = pdf_to_rect(ctx, fz_dict_gets(ctx, obj, "Rect")); rotate = fz_to_int(ctx, fz_dict_gets(ctx, fz_dict_gets(ctx, obj, "MK"), "R")); rect = fz_transform_rect(fz_rotate(rotate), rect); flags = fz_to_int(ctx, fz_dict_gets(ctx, obj, "Ff")); is_multiline = (flags & (1 << 12)) != 0; if ((flags & (1 << 25) /* richtext */)) fz_warn(ctx, "missing support for richtext fields"); align = fz_to_int(ctx, fz_dict_gets(ctx, obj, "Q")); font_size = pdf_extract_font_size(xref, fz_to_str_buf(ctx, ap), &font_name); if (!font_size || !font_name) font_size = is_multiline ? 10 /* FIXME */ : floor(rect.y1 - rect.y0 - 2); content = fz_new_buffer(ctx, 256); base_ap = fz_new_buffer(ctx, 256); pdf_prepend_ap_background(content, xref, obj); fz_buffer_printf(ctx, content, "/Tx BMC q 1 1 %.4f %.4f re W n BT %s ", rect.x1 - rect.x0 - 2.0f, rect.y1 - rect.y0 - 2.0f, fz_to_str_buf(ctx, ap)); fz_buffer_printf(ctx, base_ap, "/Tx BMC q BT %s ", fz_to_str_buf(ctx, ap)); if (font_name) { fz_buffer_printf(ctx, content, "/%s %.4f Tf ", font_name, font_size); fz_buffer_printf(ctx, base_ap, "/%s %.4f Tf ", font_name, font_size); fz_free(ctx, font_name); } y = 0.5f * (rect.y1 - rect.y0) + 0.6f * font_size; if (is_multiline) y = rect.y1 - rect.y0 - 2; fz_buffer_printf(ctx, content, "1 0 0 1 2 %.4f Tm ", y); ucs2 = pdf_to_ucs2(ctx, value); for (rest = ucs2; *rest; rest++) if (*rest > 0xFF) *rest = '?'; if ((flags & (1 << 13) /* password */)) for (rest = ucs2; *rest; rest++) *rest = '*'; x = 0; rest = ucs2; if ((flags & (1 << 24) /* comb */)) { pdf_append_combed_line(xref, res, content, base_ap, ucs2, font_size, rect.x1 - rect.x0, fz_to_int(ctx, pdf_dict_get_inheritable(xref, obj, "MaxLen"))); rest = L""; } while (*rest) rest = pdf_append_line(xref, res, content, base_ap, rest, font_size, align, rect.x1 - rect.x0 - 4.0f, is_multiline, &x); fz_free(ctx, ucs2); fz_buffer_printf(ctx, content, "ET Q EMC"); fz_drop_buffer(ctx, base_ap); rect = fz_transform_rect(fz_rotate(-rotate), rect); return pdf_create_annot(ctx, rect, fz_keep_obj(obj), content, res ? fz_keep_obj(res) : NULL, 0); }