summaryrefslogtreecommitdiff
path: root/source/pdf/pdf-annot.c
diff options
context:
space:
mode:
Diffstat (limited to 'source/pdf/pdf-annot.c')
-rw-r--r--source/pdf/pdf-annot.c1497
1 files changed, 1497 insertions, 0 deletions
diff --git a/source/pdf/pdf-annot.c b/source/pdf/pdf-annot.c
new file mode 100644
index 00000000..30afe149
--- /dev/null
+++ b/source/pdf/pdf-annot.c
@@ -0,0 +1,1497 @@
+#include "mupdf/fitz.h"
+#include "mupdf/pdf.h"
+
+#include <string.h>
+#include <time.h>
+
+#ifdef _WIN32
+#define timegm _mkgmtime
+#endif
+
+#define TEXT_ANNOT_SIZE (25.0f)
+
+#define isdigit(c) (c >= '0' && c <= '9')
+
+static void
+pdf_drop_annot_imp(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_drop_obj(ctx, annot->ap);
+ pdf_drop_obj(ctx, annot->obj);
+}
+
+void
+pdf_drop_annots(fz_context *ctx, pdf_annot *annot)
+{
+ while (annot)
+ {
+ pdf_annot *next = annot->next;
+ fz_drop_annot(ctx, &annot->super);
+ annot = next;
+ }
+}
+
+/* Create transform to fit appearance stream to annotation Rect */
+void
+pdf_annot_transform(fz_context *ctx, pdf_annot *annot, fz_matrix *annot_ctm)
+{
+ fz_rect bbox, rect;
+ fz_matrix matrix;
+ float w, h, x, y;
+
+ pdf_to_rect(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(Rect)), &rect);
+ pdf_xobject_bbox(ctx, annot->ap, &bbox);
+ pdf_xobject_matrix(ctx, annot->ap, &matrix);
+
+ fz_transform_rect(&bbox, &matrix);
+ if (bbox.x1 == bbox.x0)
+ w = 0;
+ else
+ w = (rect.x1 - rect.x0) / (bbox.x1 - bbox.x0);
+ if (bbox.y1 == bbox.y0)
+ h = 0;
+ else
+ h = (rect.y1 - rect.y0) / (bbox.y1 - bbox.y0);
+ x = rect.x0 - bbox.x0;
+ y = rect.y0 - bbox.y0;
+
+ fz_pre_scale(fz_translate(annot_ctm, x, y), w, h);
+}
+
+pdf_annot *pdf_new_annot(fz_context *ctx, pdf_page *page, pdf_obj *obj)
+{
+ pdf_annot *annot;
+
+ annot = fz_new_derived_annot(ctx, pdf_annot);
+
+ annot->super.drop_annot = (fz_annot_drop_fn*)pdf_drop_annot_imp;
+ annot->super.bound_annot = (fz_annot_bound_fn*)pdf_bound_annot;
+ annot->super.run_annot = (fz_annot_run_fn*)pdf_run_annot;
+ annot->super.next_annot = (fz_annot_next_fn*)pdf_next_annot;
+
+ annot->page = page; /* only borrowed, as the page owns the annot */
+ annot->obj = pdf_keep_obj(ctx, obj);
+
+ return annot;
+}
+
+void
+pdf_load_annots(fz_context *ctx, pdf_page *page, pdf_obj *annots)
+{
+ pdf_document *doc = page->doc;
+ pdf_annot *annot;
+ pdf_obj *subtype;
+ int i, n;
+
+ n = pdf_array_len(ctx, annots);
+ for (i = 0; i < n; ++i)
+ {
+ pdf_obj *obj = pdf_array_get(ctx, annots, i);
+ if (obj)
+ {
+ subtype = pdf_dict_get(ctx, obj, PDF_NAME(Subtype));
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(Link)))
+ continue;
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(Popup)))
+ continue;
+
+ annot = pdf_new_annot(ctx, page, obj);
+ fz_try(ctx)
+ {
+ pdf_update_annot(ctx, annot);
+ annot->has_new_ap = 0;
+ }
+ fz_catch(ctx)
+ fz_warn(ctx, "could not update appearance for annotation");
+
+ if (doc->focus_obj == obj)
+ doc->focus = annot;
+
+ *page->annot_tailp = annot;
+ page->annot_tailp = &annot->next;
+ }
+ }
+}
+
+pdf_annot *
+pdf_first_annot(fz_context *ctx, pdf_page *page)
+{
+ return page ? page->annots : NULL;
+}
+
+pdf_annot *
+pdf_next_annot(fz_context *ctx, pdf_annot *annot)
+{
+ return annot ? annot->next : NULL;
+}
+
+fz_rect *
+pdf_bound_annot(fz_context *ctx, pdf_annot *annot, fz_rect *rect)
+{
+ pdf_obj *obj = pdf_dict_get(ctx, annot->obj, PDF_NAME(Rect));
+ fz_rect mediabox;
+ fz_matrix page_ctm;
+ pdf_to_rect(ctx, obj, rect);
+ pdf_page_transform(ctx, annot->page, &mediabox, &page_ctm);
+ fz_transform_rect(rect, &page_ctm);
+ return rect;
+}
+
+void
+pdf_dirty_annot(fz_context *ctx, pdf_annot *annot)
+{
+ annot->needs_new_ap = 1;
+ if (annot->page && annot->page->doc)
+ annot->page->doc->dirty = 1;
+}
+
+const char *
+pdf_string_from_annot_type(fz_context *ctx, enum pdf_annot_type type)
+{
+ switch (type)
+ {
+ case PDF_ANNOT_TEXT: return "Text";
+ case PDF_ANNOT_LINK: return "Link";
+ case PDF_ANNOT_FREE_TEXT: return "FreeText";
+ case PDF_ANNOT_LINE: return "Line";
+ case PDF_ANNOT_SQUARE: return "Square";
+ case PDF_ANNOT_CIRCLE: return "Circle";
+ case PDF_ANNOT_POLYGON: return "Polygon";
+ case PDF_ANNOT_POLY_LINE: return "PolyLine";
+ case PDF_ANNOT_HIGHLIGHT: return "Highlight";
+ case PDF_ANNOT_UNDERLINE: return "Underline";
+ case PDF_ANNOT_SQUIGGLY: return "Squiggly";
+ case PDF_ANNOT_STRIKE_OUT: return "StrikeOut";
+ case PDF_ANNOT_STAMP: return "Stamp";
+ case PDF_ANNOT_CARET: return "Caret";
+ case PDF_ANNOT_INK: return "Ink";
+ case PDF_ANNOT_POPUP: return "Popup";
+ case PDF_ANNOT_FILE_ATTACHMENT: return "FileAttachment";
+ case PDF_ANNOT_SOUND: return "Sound";
+ case PDF_ANNOT_MOVIE: return "Movie";
+ case PDF_ANNOT_WIDGET: return "Widget";
+ case PDF_ANNOT_SCREEN: return "Screen";
+ case PDF_ANNOT_PRINTER_MARK: return "PrinterMark";
+ case PDF_ANNOT_TRAP_NET: return "TrapNet";
+ case PDF_ANNOT_WATERMARK: return "Watermark";
+ case PDF_ANNOT_3D: return "3D";
+ default: return "UNKNOWN";
+ }
+}
+
+int
+pdf_annot_type_from_string(fz_context *ctx, const char *subtype)
+{
+ if (!strcmp("Text", subtype)) return PDF_ANNOT_TEXT;
+ if (!strcmp("Link", subtype)) return PDF_ANNOT_LINK;
+ if (!strcmp("FreeText", subtype)) return PDF_ANNOT_FREE_TEXT;
+ if (!strcmp("Line", subtype)) return PDF_ANNOT_LINE;
+ if (!strcmp("Square", subtype)) return PDF_ANNOT_SQUARE;
+ if (!strcmp("Circle", subtype)) return PDF_ANNOT_CIRCLE;
+ if (!strcmp("Polygon", subtype)) return PDF_ANNOT_POLYGON;
+ if (!strcmp("PolyLine", subtype)) return PDF_ANNOT_POLY_LINE;
+ if (!strcmp("Highlight", subtype)) return PDF_ANNOT_HIGHLIGHT;
+ if (!strcmp("Underline", subtype)) return PDF_ANNOT_UNDERLINE;
+ if (!strcmp("Squiggly", subtype)) return PDF_ANNOT_SQUIGGLY;
+ if (!strcmp("StrikeOut", subtype)) return PDF_ANNOT_STRIKE_OUT;
+ if (!strcmp("Stamp", subtype)) return PDF_ANNOT_STAMP;
+ if (!strcmp("Caret", subtype)) return PDF_ANNOT_CARET;
+ if (!strcmp("Ink", subtype)) return PDF_ANNOT_INK;
+ if (!strcmp("Popup", subtype)) return PDF_ANNOT_POPUP;
+ if (!strcmp("FileAttachment", subtype)) return PDF_ANNOT_FILE_ATTACHMENT;
+ if (!strcmp("Sound", subtype)) return PDF_ANNOT_SOUND;
+ if (!strcmp("Movie", subtype)) return PDF_ANNOT_MOVIE;
+ if (!strcmp("Widget", subtype)) return PDF_ANNOT_WIDGET;
+ if (!strcmp("Screen", subtype)) return PDF_ANNOT_SCREEN;
+ if (!strcmp("PrinterMark", subtype)) return PDF_ANNOT_PRINTER_MARK;
+ if (!strcmp("TrapNet", subtype)) return PDF_ANNOT_TRAP_NET;
+ if (!strcmp("Watermark", subtype)) return PDF_ANNOT_WATERMARK;
+ if (!strcmp("3D", subtype)) return PDF_ANNOT_3D;
+ return PDF_ANNOT_UNKNOWN;
+}
+
+static int is_allowed_subtype(fz_context *ctx, pdf_annot *annot, pdf_obj *property, pdf_obj **allowed)
+{
+ pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
+ while (*allowed) {
+ if (pdf_name_eq(ctx, subtype, *allowed))
+ return 1;
+ allowed++;
+ }
+
+ return 0;
+}
+
+static void check_allowed_subtypes(fz_context *ctx, pdf_annot *annot, pdf_obj *property, pdf_obj **allowed)
+{
+ pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
+ if (!is_allowed_subtype(ctx, annot, property, allowed))
+ fz_throw(ctx, FZ_ERROR_GENERIC, "%s annotations have no %s property", pdf_to_name(ctx, subtype), pdf_to_name(ctx, property));
+}
+
+pdf_annot *
+pdf_create_annot(fz_context *ctx, pdf_page *page, enum pdf_annot_type type)
+{
+ pdf_annot *annot = NULL;
+ pdf_document *doc = page->doc;
+ pdf_obj *annot_obj = pdf_new_dict(ctx, doc, 0);
+ pdf_obj *ind_obj = NULL;
+
+ fz_var(annot);
+ fz_var(ind_obj);
+ fz_try(ctx)
+ {
+ int ind_obj_num;
+ const char *type_str;
+ pdf_obj *annot_arr;
+
+ type_str = pdf_string_from_annot_type(ctx, type);
+ if (type == PDF_ANNOT_UNKNOWN)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "cannot create unknown annotation");
+
+ annot_arr = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
+ if (annot_arr == NULL)
+ {
+ annot_arr = pdf_new_array(ctx, doc, 0);
+ pdf_dict_put_drop(ctx, page->obj, PDF_NAME(Annots), annot_arr);
+ }
+
+ pdf_dict_put(ctx, annot_obj, PDF_NAME(Type), PDF_NAME(Annot));
+ pdf_dict_put_name(ctx, annot_obj, PDF_NAME(Subtype), type_str);
+
+ /* Make printable as default */
+ pdf_dict_put_int(ctx, annot_obj, PDF_NAME(F), PDF_ANNOT_IS_PRINT);
+
+ /*
+ Both annotation object and annotation structure are now created.
+ Insert the object in the hierarchy and the structure in the
+ page's array.
+ */
+ ind_obj_num = pdf_create_object(ctx, doc);
+ pdf_update_object(ctx, doc, ind_obj_num, annot_obj);
+ ind_obj = pdf_new_indirect(ctx, doc, ind_obj_num, 0);
+ pdf_array_push(ctx, annot_arr, ind_obj);
+
+ annot = pdf_new_annot(ctx, page, ind_obj);
+ annot->ap = NULL;
+
+ /*
+ Linking must be done after any call that might throw because
+ pdf_drop_annots below actually frees a list. Put the new annot
+ at the end of the list, so that it will be drawn last.
+ */
+ *page->annot_tailp = annot;
+ page->annot_tailp = &annot->next;
+
+ doc->dirty = 1;
+ }
+ fz_always(ctx)
+ {
+ pdf_drop_obj(ctx, annot_obj);
+ pdf_drop_obj(ctx, ind_obj);
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_annots(ctx, annot);
+ fz_rethrow(ctx);
+ }
+
+ return annot;
+}
+
+void
+pdf_delete_annot(fz_context *ctx, pdf_page *page, pdf_annot *annot)
+{
+ pdf_document *doc = annot->page->doc;
+ pdf_annot **annotptr;
+ pdf_obj *annot_arr;
+ int i;
+
+ if (annot == NULL)
+ return;
+
+ /* Remove annot from page's list */
+ for (annotptr = &page->annots; *annotptr; annotptr = &(*annotptr)->next)
+ {
+ if (*annotptr == annot)
+ break;
+ }
+
+ /* Check the passed annotation was of this page */
+ if (*annotptr == NULL)
+ return;
+
+ *annotptr = annot->next;
+
+ /* If the removed annotation was the last in the list adjust the end pointer */
+ if (*annotptr == NULL)
+ page->annot_tailp = annotptr;
+
+ /* If the removed annotation has the focus, blur it. */
+ if (doc->focus == annot)
+ {
+ doc->focus = NULL;
+ doc->focus_obj = NULL;
+ }
+
+ /* Remove the annot from the "Annots" array. */
+ annot_arr = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
+ i = pdf_array_find(ctx, annot_arr, annot->obj);
+ if (i >= 0)
+ pdf_array_delete(ctx, annot_arr, i);
+
+ /* The garbage collection pass when saving will remove the annot object,
+ * removing it here may break files if multiple pages use the same annot. */
+
+ /* And free it. */
+ fz_drop_annot(ctx, &annot->super);
+
+ doc->dirty = 1;
+}
+
+int
+pdf_annot_type(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *obj = annot->obj;
+ pdf_obj *subtype = pdf_dict_get(ctx, obj, PDF_NAME(Subtype));
+ return pdf_annot_type_from_string(ctx, pdf_to_name(ctx, subtype));
+}
+
+int
+pdf_annot_flags(fz_context *ctx, pdf_annot *annot)
+{
+ return pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
+}
+
+void
+pdf_set_annot_flags(fz_context *ctx, pdf_annot *annot, int flags)
+{
+ pdf_dict_put_int(ctx, annot->obj, PDF_NAME(F), flags);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_annot_rect(fz_context *ctx, pdf_annot *annot, fz_rect *rect)
+{
+ fz_matrix page_ctm;
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ pdf_to_rect(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(Rect)), rect);
+ fz_transform_rect(rect, &page_ctm);
+}
+
+void
+pdf_set_annot_rect(fz_context *ctx, pdf_annot *annot, const fz_rect *rect)
+{
+ fz_rect trect = *rect;
+ fz_matrix page_ctm, inv_page_ctm;
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+ fz_transform_rect(&trect, &inv_page_ctm);
+
+ pdf_dict_put_rect(ctx, annot->obj, PDF_NAME(Rect), &trect);
+ pdf_dirty_annot(ctx, annot);
+}
+
+char *
+pdf_copy_annot_contents(fz_context *ctx, pdf_annot *annot)
+{
+ return pdf_to_utf8(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(Contents)));
+}
+
+void
+pdf_set_annot_contents(fz_context *ctx, pdf_annot *annot, const char *text)
+{
+ pdf_dict_put_text_string(ctx, annot->obj, PDF_NAME(Contents), text);
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(RC)); /* not supported */
+ pdf_dirty_annot(ctx, annot);
+}
+
+static pdf_obj *open_subtypes[] = {
+ PDF_NAME(Popup),
+ PDF_NAME(Text),
+ NULL,
+};
+
+int
+pdf_annot_has_open(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(Open), open_subtypes);
+}
+
+int
+pdf_annot_is_open(fz_context *ctx, pdf_annot *annot)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Open), open_subtypes);
+ return pdf_dict_get_bool(ctx, annot->obj, PDF_NAME(Open));
+}
+
+void
+pdf_set_annot_is_open(fz_context *ctx, pdf_annot *annot, int is_open)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Open), open_subtypes);
+ pdf_dict_put_bool(ctx, annot->obj, PDF_NAME(Open), is_open);
+ pdf_dirty_annot(ctx, annot);
+}
+
+static pdf_obj *icon_name_subtypes[] = {
+ PDF_NAME(FileAttachment),
+ PDF_NAME(Sound),
+ PDF_NAME(Stamp),
+ PDF_NAME(Text),
+ NULL,
+};
+
+int
+pdf_annot_has_icon_name(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
+}
+
+const char *
+pdf_annot_icon_name(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *name;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
+ name = pdf_dict_get(ctx, annot->obj, PDF_NAME(Name));
+ if (!name)
+ {
+ pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(Text)))
+ return "Note";
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(Stamp)))
+ return "Draft";
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(FileAttachment)))
+ return "PushPin";
+ if (pdf_name_eq(ctx, subtype, PDF_NAME(Sound)))
+ return "Speaker";
+ }
+ return pdf_to_name(ctx, name);
+}
+
+void
+pdf_set_annot_icon_name(fz_context *ctx, pdf_annot *annot, const char *name)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
+ pdf_dict_put_name(ctx, annot->obj, PDF_NAME(Name), name);
+ pdf_dirty_annot(ctx, annot);
+}
+
+enum pdf_line_ending pdf_line_ending_from_name(fz_context *ctx, pdf_obj *end)
+{
+ if (pdf_name_eq(ctx, end, PDF_NAME(None))) return PDF_ANNOT_LE_NONE;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(Square))) return PDF_ANNOT_LE_SQUARE;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(Circle))) return PDF_ANNOT_LE_CIRCLE;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(Diamond))) return PDF_ANNOT_LE_DIAMOND;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(OpenArrow))) return PDF_ANNOT_LE_OPEN_ARROW;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(ClosedArrow))) return PDF_ANNOT_LE_CLOSED_ARROW;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(Butt))) return PDF_ANNOT_LE_BUTT;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(ROpenArrow))) return PDF_ANNOT_LE_R_OPEN_ARROW;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(RClosedArrow))) return PDF_ANNOT_LE_R_CLOSED_ARROW;
+ else if (pdf_name_eq(ctx, end, PDF_NAME(Slash))) return PDF_ANNOT_LE_SLASH;
+ else return PDF_ANNOT_LE_NONE;
+}
+
+enum pdf_line_ending pdf_line_ending_from_string(fz_context *ctx, const char *end)
+{
+ if (!strcmp(end, "None")) return PDF_ANNOT_LE_NONE;
+ else if (!strcmp(end, "Square")) return PDF_ANNOT_LE_SQUARE;
+ else if (!strcmp(end, "Circle")) return PDF_ANNOT_LE_CIRCLE;
+ else if (!strcmp(end, "Diamond")) return PDF_ANNOT_LE_DIAMOND;
+ else if (!strcmp(end, "OpenArrow")) return PDF_ANNOT_LE_OPEN_ARROW;
+ else if (!strcmp(end, "ClosedArrow")) return PDF_ANNOT_LE_CLOSED_ARROW;
+ else if (!strcmp(end, "Butt")) return PDF_ANNOT_LE_BUTT;
+ else if (!strcmp(end, "ROpenArrow")) return PDF_ANNOT_LE_R_OPEN_ARROW;
+ else if (!strcmp(end, "RClosedArrow")) return PDF_ANNOT_LE_R_CLOSED_ARROW;
+ else if (!strcmp(end, "Slash")) return PDF_ANNOT_LE_SLASH;
+ else return PDF_ANNOT_LE_NONE;
+}
+
+pdf_obj *pdf_name_from_line_ending(fz_context *ctx, enum pdf_line_ending end)
+{
+ switch (end)
+ {
+ default:
+ case PDF_ANNOT_LE_NONE: return PDF_NAME(None);
+ case PDF_ANNOT_LE_SQUARE: return PDF_NAME(Square);
+ case PDF_ANNOT_LE_CIRCLE: return PDF_NAME(Circle);
+ case PDF_ANNOT_LE_DIAMOND: return PDF_NAME(Diamond);
+ case PDF_ANNOT_LE_OPEN_ARROW: return PDF_NAME(OpenArrow);
+ case PDF_ANNOT_LE_CLOSED_ARROW: return PDF_NAME(ClosedArrow);
+ case PDF_ANNOT_LE_BUTT: return PDF_NAME(Butt);
+ case PDF_ANNOT_LE_R_OPEN_ARROW: return PDF_NAME(ROpenArrow);
+ case PDF_ANNOT_LE_R_CLOSED_ARROW: return PDF_NAME(RClosedArrow);
+ case PDF_ANNOT_LE_SLASH: return PDF_NAME(Slash);
+ }
+}
+
+const char *pdf_string_from_line_ending(fz_context *ctx, enum pdf_line_ending end)
+{
+ switch (end)
+ {
+ default:
+ case PDF_ANNOT_LE_NONE: return "None";
+ case PDF_ANNOT_LE_SQUARE: return "Square";
+ case PDF_ANNOT_LE_CIRCLE: return "Circle";
+ case PDF_ANNOT_LE_DIAMOND: return "Diamond";
+ case PDF_ANNOT_LE_OPEN_ARROW: return "OpenArrow";
+ case PDF_ANNOT_LE_CLOSED_ARROW: return "ClosedArrow";
+ case PDF_ANNOT_LE_BUTT: return "Butt";
+ case PDF_ANNOT_LE_R_OPEN_ARROW: return "ROpenArrow";
+ case PDF_ANNOT_LE_R_CLOSED_ARROW: return "RClosedArrow";
+ case PDF_ANNOT_LE_SLASH: return "Slash";
+ }
+}
+
+static pdf_obj *line_ending_subtypes[] = {
+ PDF_NAME(FreeText),
+ PDF_NAME(Line),
+ PDF_NAME(PolyLine),
+ PDF_NAME(Polygon),
+ NULL,
+};
+
+int
+pdf_annot_has_line_ending_styles(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
+}
+
+void
+pdf_annot_line_ending_styles(fz_context *ctx, pdf_annot *annot,
+ enum pdf_line_ending *start_style,
+ enum pdf_line_ending *end_style)
+{
+ pdf_obj *style;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
+ style = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
+ *start_style = pdf_line_ending_from_name(ctx, pdf_array_get(ctx, style, 0));
+ *end_style = pdf_line_ending_from_name(ctx, pdf_array_get(ctx, style, 1));
+}
+
+enum pdf_line_ending
+pdf_annot_line_start_style(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *le = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
+ return pdf_line_ending_from_name(ctx, pdf_array_get(ctx, le, 0));
+}
+
+enum pdf_line_ending
+pdf_annot_line_end_style(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *le = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
+ return pdf_line_ending_from_name(ctx, pdf_array_get(ctx, le, 1));
+}
+
+void
+pdf_set_annot_line_ending_styles(fz_context *ctx, pdf_annot *annot,
+ enum pdf_line_ending start_style,
+ enum pdf_line_ending end_style)
+{
+ pdf_document *doc = annot->page->doc;
+ pdf_obj *style;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
+ style = pdf_new_array(ctx, doc, 2);
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(LE), style);
+ pdf_array_put_drop(ctx, style, 0, pdf_name_from_line_ending(ctx, start_style));
+ pdf_array_put_drop(ctx, style, 1, pdf_name_from_line_ending(ctx, end_style));
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_set_annot_line_start_style(fz_context *ctx, pdf_annot *annot, enum pdf_line_ending s)
+{
+ enum pdf_line_ending e = pdf_annot_line_end_style(ctx, annot);
+ pdf_set_annot_line_ending_styles(ctx, annot, s, e);
+}
+
+void
+pdf_set_annot_line_end_style(fz_context *ctx, pdf_annot *annot, enum pdf_line_ending e)
+{
+ enum pdf_line_ending s = pdf_annot_line_start_style(ctx, annot);
+ pdf_set_annot_line_ending_styles(ctx, annot, s, e);
+}
+
+float
+pdf_annot_border(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *bs, *bs_w;
+ bs = pdf_dict_get(ctx, annot->obj, PDF_NAME(BS));
+ bs_w = pdf_dict_get(ctx, bs, PDF_NAME(W));
+ if (pdf_is_number(ctx, bs_w))
+ return pdf_to_real(ctx, bs_w);
+ return 1;
+}
+
+void
+pdf_set_annot_border(fz_context *ctx, pdf_annot *annot, float w)
+{
+ pdf_obj *bs = pdf_dict_get(ctx, annot->obj, PDF_NAME(BS));
+ if (!pdf_is_dict(ctx, bs))
+ bs = pdf_dict_put_dict(ctx, annot->obj, PDF_NAME(BS), 1);
+ pdf_dict_put_real(ctx, bs, PDF_NAME(W), w);
+
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(Border)); /* deprecated */
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(BE)); /* not supported */
+
+ pdf_dirty_annot(ctx, annot);
+}
+
+int
+pdf_annot_quadding(fz_context *ctx, pdf_annot *annot)
+{
+ int q = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(Q));
+ return (q < 0 || q > 2) ? 0 : q;
+}
+
+void
+pdf_set_annot_quadding(fz_context *ctx, pdf_annot *annot, int q)
+{
+ q = (q < 0 || q > 2) ? 0 : q;
+ pdf_dict_put_int(ctx, annot->obj, PDF_NAME(Q), q);
+ pdf_dirty_annot(ctx, annot);
+}
+
+float pdf_annot_opacity(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *ca = pdf_dict_get(ctx, annot->obj, PDF_NAME(CA));
+ if (pdf_is_number(ctx, ca))
+ return pdf_to_real(ctx, ca);
+ return 1;
+}
+
+void pdf_set_annot_opacity(fz_context *ctx, pdf_annot *annot, float opacity)
+{
+ if (opacity != 1)
+ pdf_dict_put_real(ctx, annot->obj, PDF_NAME(CA), opacity);
+ else
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(CA));
+ pdf_dirty_annot(ctx, annot);
+}
+
+static void pdf_annot_color_imp(fz_context *ctx, pdf_annot *annot, pdf_obj *key, int *n, float color[4], pdf_obj **allowed)
+{
+ pdf_obj *arr;
+ int len;
+
+ if (allowed)
+ check_allowed_subtypes(ctx, annot, key, allowed);
+
+ arr = pdf_dict_get(ctx, annot->obj, key);
+ len = pdf_array_len(ctx, arr);
+
+ switch (len)
+ {
+ case 0:
+ if (n)
+ *n = 0;
+ break;
+ case 1:
+ case 2:
+ if (n)
+ *n = 1;
+ if (color)
+ color[0] = pdf_array_get_real(ctx, arr, 0);
+ break;
+ case 3:
+ if (n)
+ *n = 3;
+ if (color)
+ {
+ color[0] = pdf_array_get_real(ctx, arr, 0);
+ color[1] = pdf_array_get_real(ctx, arr, 1);
+ color[2] = pdf_array_get_real(ctx, arr, 2);
+ }
+ break;
+ case 4:
+ default:
+ if (n)
+ *n = 4;
+ if (color)
+ {
+ color[0] = pdf_array_get_real(ctx, arr, 0);
+ color[1] = pdf_array_get_real(ctx, arr, 1);
+ color[2] = pdf_array_get_real(ctx, arr, 2);
+ color[3] = pdf_array_get_real(ctx, arr, 3);
+ }
+ break;
+ }
+}
+
+static void pdf_set_annot_color_imp(fz_context *ctx, pdf_annot *annot, pdf_obj *key, int n, const float color[4], pdf_obj **allowed)
+{
+ pdf_document *doc = annot->page->doc;
+ pdf_obj *arr;
+
+ if (allowed)
+ check_allowed_subtypes(ctx, annot, key, allowed);
+ if (n != 0 && n != 1 && n != 3 && n != 4)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "color must be 0, 1, 3 or 4 components");
+ if (!color)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "no color given");
+
+ arr = pdf_new_array(ctx, doc, n);
+ fz_try(ctx)
+ {
+ switch (n)
+ {
+ case 1:
+ pdf_array_push_real(ctx, arr, color[0]);
+ break;
+ case 3:
+ pdf_array_push_real(ctx, arr, color[0]);
+ pdf_array_push_real(ctx, arr, color[1]);
+ pdf_array_push_real(ctx, arr, color[2]);
+ break;
+ case 4:
+ pdf_array_push_real(ctx, arr, color[0]);
+ pdf_array_push_real(ctx, arr, color[1]);
+ pdf_array_push_real(ctx, arr, color[2]);
+ pdf_array_push_real(ctx, arr, color[3]);
+ break;
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(ctx, arr);
+ fz_rethrow(ctx);
+ }
+
+ pdf_dict_put_drop(ctx, annot->obj, key, arr);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_annot_color(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
+{
+ pdf_annot_color_imp(ctx, annot, PDF_NAME(C), n, color, NULL);
+}
+
+void
+pdf_set_annot_color(fz_context *ctx, pdf_annot *annot, int n, const float color[4])
+{
+ pdf_set_annot_color_imp(ctx, annot, PDF_NAME(C), n, color, NULL);
+}
+
+static pdf_obj *interior_color_subtypes[] = {
+ PDF_NAME(Circle),
+ PDF_NAME(Line),
+ PDF_NAME(PolyLine),
+ PDF_NAME(Polygon),
+ PDF_NAME(Square),
+ NULL,
+};
+
+int
+pdf_annot_has_interior_color(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(IC), interior_color_subtypes);
+}
+
+void
+pdf_annot_interior_color(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
+{
+ pdf_annot_color_imp(ctx, annot, PDF_NAME(IC), n, color, interior_color_subtypes);
+}
+
+void
+pdf_set_annot_interior_color(fz_context *ctx, pdf_annot *annot, int n, const float color[4])
+{
+ pdf_set_annot_color_imp(ctx, annot, PDF_NAME(IC), n, color, interior_color_subtypes);
+}
+
+static pdf_obj *line_subtypes[] = {
+ PDF_NAME(Line),
+ NULL,
+};
+
+int
+pdf_annot_has_line(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(L), line_subtypes);
+}
+
+void
+pdf_annot_line(fz_context *ctx, pdf_annot *annot, fz_point *a, fz_point *b)
+{
+ fz_matrix page_ctm;
+ pdf_obj *line;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(L), line_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+
+ line = pdf_dict_get(ctx, annot->obj, PDF_NAME(L));
+ a->x = pdf_array_get_real(ctx, line, 0);
+ a->y = pdf_array_get_real(ctx, line, 1);
+ b->x = pdf_array_get_real(ctx, line, 2);
+ b->y = pdf_array_get_real(ctx, line, 3);
+ fz_transform_point(a, &page_ctm);
+ fz_transform_point(b, &page_ctm);
+}
+
+void
+pdf_set_annot_line(fz_context *ctx, pdf_annot *annot, fz_point a, fz_point b)
+{
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *line;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(L), line_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ fz_transform_point(&a, &inv_page_ctm);
+ fz_transform_point(&b, &inv_page_ctm);
+
+ line = pdf_new_array(ctx, annot->page->doc, 4);
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(L), line);
+ pdf_array_push_real(ctx, line, a.x);
+ pdf_array_push_real(ctx, line, a.y);
+ pdf_array_push_real(ctx, line, b.x);
+ pdf_array_push_real(ctx, line, b.y);
+
+ pdf_dirty_annot(ctx, annot);
+}
+
+static pdf_obj *vertices_subtypes[] = {
+ PDF_NAME(PolyLine),
+ PDF_NAME(Polygon),
+ NULL,
+};
+
+int
+pdf_annot_has_vertices(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+}
+
+int
+pdf_annot_vertex_count(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *vertices;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+ vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
+ return pdf_array_len(ctx, vertices) / 2;
+}
+
+fz_point
+pdf_annot_vertex(fz_context *ctx, pdf_annot *annot, int i)
+{
+ pdf_obj *vertices;
+ fz_matrix page_ctm;
+ fz_point point;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+
+ vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+
+ point.x = pdf_array_get_real(ctx, vertices, i * 2);
+ point.y = pdf_array_get_real(ctx, vertices, i * 2 + 1);
+ fz_transform_point(&point, &page_ctm);
+
+ return point;
+}
+
+void
+pdf_set_annot_vertices(fz_context *ctx, pdf_annot *annot, int n, const fz_point *v)
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *vertices;
+ fz_point point;
+ int i;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+ if (n <= 0 || !v)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of vertices");
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ vertices = pdf_new_array(ctx, doc, n * 2);
+ for (i = 0; i < n; ++i)
+ {
+ point = v[i];
+ fz_transform_point(&point, &inv_page_ctm);
+ pdf_array_push_real(ctx, vertices, point.x);
+ pdf_array_push_real(ctx, vertices, point.y);
+ }
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(Vertices), vertices);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void pdf_clear_annot_vertices(fz_context *ctx, pdf_annot *annot)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(Vertices));
+ pdf_dirty_annot(ctx, annot);
+}
+
+void pdf_add_annot_vertex(fz_context *ctx, pdf_annot *annot, fz_point p)
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *vertices;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
+ if (!pdf_is_array(ctx, vertices))
+ {
+ vertices = pdf_new_array(ctx, doc, 32);
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(Vertices), vertices);
+ }
+
+ fz_transform_point(&p, &inv_page_ctm);
+ pdf_array_push_real(ctx, vertices, p.x);
+ pdf_array_push_real(ctx, vertices, p.y);
+
+ pdf_dirty_annot(ctx, annot);
+}
+
+void pdf_set_annot_vertex(fz_context *ctx, pdf_annot *annot, int i, fz_point p)
+{
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *vertices;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ fz_transform_point(&p, &inv_page_ctm);
+
+ vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
+ pdf_array_put_drop(ctx, vertices, i * 2 + 0, pdf_new_real(ctx, p.x));
+ pdf_array_put_drop(ctx, vertices, i * 2 + 1, pdf_new_real(ctx, p.y));
+}
+
+static pdf_obj *quad_point_subtypes[] = {
+ PDF_NAME(Highlight),
+ PDF_NAME(Link),
+ PDF_NAME(Squiggly),
+ PDF_NAME(StrikeOut),
+ PDF_NAME(Underline),
+ NULL,
+};
+
+int
+pdf_annot_has_quad_points(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+}
+
+int
+pdf_annot_quad_point_count(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *quad_points;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+ quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
+ return pdf_array_len(ctx, quad_points) / 8;
+}
+
+void
+pdf_annot_quad_point(fz_context *ctx, pdf_annot *annot, int idx, float v[8])
+{
+ pdf_obj *quad_points;
+ fz_matrix page_ctm;
+ int i;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+ quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+
+ for (i = 0; i < 8; i += 2)
+ {
+ fz_point point;
+ point.x = pdf_array_get_real(ctx, quad_points, idx * 8 + i + 0);
+ point.y = pdf_array_get_real(ctx, quad_points, idx * 8 + i + 1);
+ fz_transform_point(&point, &page_ctm);
+ v[i+0] = point.x;
+ v[i+1] = point.y;
+ }
+}
+
+void
+pdf_set_annot_quad_points(fz_context *ctx, pdf_annot *annot, int n, const float *v)
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *quad_points;
+ fz_point point;
+ int i, k;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+ if (n <= 0 || !v)
+ fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of quadrilaterals");
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ quad_points = pdf_new_array(ctx, doc, n * 8);
+ for (i = 0; i < n; ++i)
+ {
+ for (k = 0; k < 4; ++k)
+ {
+ point.x = v[i * 8 + k * 2 + 0];
+ point.y = v[i * 8 + k * 2 + 1];
+ fz_transform_point(&point, &inv_page_ctm);
+ pdf_array_push_real(ctx, quad_points, point.x);
+ pdf_array_push_real(ctx, quad_points, point.y);
+ }
+ }
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(QuadPoints), quad_points);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_clear_annot_quad_points(fz_context *ctx, pdf_annot *annot)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(QuadPoints));
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_add_annot_quad_point(fz_context *ctx, pdf_annot *annot, fz_rect bbox)
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *quad_points;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
+ if (!pdf_is_array(ctx, quad_points))
+ {
+ quad_points = pdf_new_array(ctx, doc, 8);
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(QuadPoints), quad_points);
+ }
+
+ /* Contrary to the specification, the points within a QuadPoint are NOT ordered
+ * in a counterclockwise fashion. Experiments with Adobe's implementation
+ * indicates a cross-wise ordering is intended: ul, ur, ll, lr.
+ */
+ fz_transform_rect(&bbox, &inv_page_ctm);
+ pdf_array_push_real(ctx, quad_points, bbox.x0); /* ul */
+ pdf_array_push_real(ctx, quad_points, bbox.y1);
+ pdf_array_push_real(ctx, quad_points, bbox.x1); /* ur */
+ pdf_array_push_real(ctx, quad_points, bbox.y1);
+ pdf_array_push_real(ctx, quad_points, bbox.x0); /* ll */
+ pdf_array_push_real(ctx, quad_points, bbox.y0);
+ pdf_array_push_real(ctx, quad_points, bbox.x1); /* lr */
+ pdf_array_push_real(ctx, quad_points, bbox.y0);
+
+ pdf_dirty_annot(ctx, annot);
+}
+
+static pdf_obj *ink_list_subtypes[] = {
+ PDF_NAME(Ink),
+ NULL,
+};
+
+int
+pdf_annot_has_ink_list(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+}
+
+int
+pdf_annot_ink_list_count(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *ink_list;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+ ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
+ return pdf_array_len(ctx, ink_list);
+}
+
+int
+pdf_annot_ink_list_stroke_count(fz_context *ctx, pdf_annot *annot, int i)
+{
+ pdf_obj *ink_list;
+ pdf_obj *stroke;
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+ ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
+ stroke = pdf_array_get(ctx, ink_list, i);
+ return pdf_array_len(ctx, stroke) / 2;
+}
+
+fz_point
+pdf_annot_ink_list_stroke_vertex(fz_context *ctx, pdf_annot *annot, int i, int k)
+{
+ pdf_obj *ink_list;
+ pdf_obj *stroke;
+ fz_matrix page_ctm;
+ fz_point point;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+
+ ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
+ stroke = pdf_array_get(ctx, ink_list, i);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+
+ point.x = pdf_array_get_real(ctx, stroke, k * 2 + 0);
+ point.y = pdf_array_get_real(ctx, stroke, k * 2 + 1);
+ fz_transform_point(&point, &page_ctm);
+
+ return point;
+}
+
+void
+pdf_set_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, const int *count, const fz_point *v)
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *ink_list, *stroke;
+ fz_point point;
+ int i, k;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ // TODO: update Rect (in update appearance perhaps?)
+
+ ink_list = pdf_new_array(ctx, doc, n);
+ for (i = 0; i < n; ++i)
+ {
+ stroke = pdf_new_array(ctx, doc, count[i] * 2);
+ for (k = 0; k < count[i]; ++k)
+ {
+ point = *v++;
+ fz_transform_point(&point, &inv_page_ctm);
+ pdf_array_push_real(ctx, stroke, point.x);
+ pdf_array_push_real(ctx, stroke, point.y);
+ }
+ pdf_array_push_drop(ctx, ink_list, stroke);
+ }
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(InkList), ink_list);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_clear_annot_ink_list(fz_context *ctx, pdf_annot *annot)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(InkList));
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_add_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, fz_point p[])
+{
+ pdf_document *doc = annot->page->doc;
+ fz_matrix page_ctm, inv_page_ctm;
+ pdf_obj *ink_list, *stroke;
+ int i;
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
+ if (!pdf_is_array(ctx, ink_list))
+ {
+ ink_list = pdf_new_array(ctx, doc, 10);
+ pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(InkList), ink_list);
+ }
+
+ stroke = pdf_new_array(ctx, doc, n * 2);
+ fz_try(ctx)
+ {
+ for (i = 0; i < n; ++i)
+ {
+ fz_point tp = p[i];
+ fz_transform_point(&tp, &inv_page_ctm);
+ pdf_array_push_real(ctx, stroke, tp.x);
+ pdf_array_push_real(ctx, stroke, tp.y);
+ }
+ }
+ fz_catch(ctx)
+ {
+ pdf_drop_obj(ctx, stroke);
+ fz_rethrow(ctx);
+ }
+
+ pdf_array_push_drop(ctx, ink_list, stroke);
+
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_set_text_annot_position(fz_context *ctx, pdf_annot *annot, fz_point pt)
+{
+ fz_matrix page_ctm, inv_page_ctm;
+ fz_rect rect;
+ int flags;
+
+ pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
+ fz_invert_matrix(&inv_page_ctm, &page_ctm);
+
+ rect.x0 = pt.x;
+ rect.x1 = pt.x + TEXT_ANNOT_SIZE;
+ rect.y0 = pt.y;
+ rect.y1 = pt.y + TEXT_ANNOT_SIZE;
+ fz_transform_rect(&rect, &inv_page_ctm);
+
+ pdf_dict_put_rect(ctx, annot->obj, PDF_NAME(Rect), &rect);
+
+ flags = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
+ flags |= (PDF_ANNOT_IS_NO_ZOOM|PDF_ANNOT_IS_NO_ROTATE);
+ pdf_dict_put_int(ctx, annot->obj, PDF_NAME(F), flags);
+}
+
+static void
+pdf_format_date(fz_context *ctx, char *s, int n, time_t secs)
+{
+#ifdef _POSIX_SOURCE
+ struct tm tmbuf, *tm = gmtime_r(&secs, &tmbuf);
+#else
+ struct tm *tm = gmtime(&secs);
+#endif
+ if (!tm)
+ fz_strlcpy(s, "D:19700101000000Z", n);
+ else
+ strftime(s, n, "D:%Y%m%d%H%M%SZ", tm);
+}
+
+static int64_t
+pdf_parse_date(fz_context *ctx, const char *s)
+{
+ int tz_sign, tz_hour, tz_min, tz_adj;
+ struct tm tm;
+ time_t utc;
+
+ if (!s)
+ return 0;
+
+ memset(&tm, 0, sizeof tm);
+ tm.tm_mday = 1;
+
+ tz_sign = 1;
+ tz_hour = 0;
+ tz_min = 0;
+
+ if (s[0] == 'D' && s[1] == ':')
+ s += 2;
+
+ if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2]) || !isdigit(s[3]))
+ {
+ fz_warn(ctx, "invalid date format (missing year)");
+ return 0;
+ }
+ tm.tm_year = (s[0]-'0')*1000 + (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0') - 1900;
+ s += 4;
+
+ if (isdigit(s[0]) && isdigit(s[1]))
+ {
+ tm.tm_mon = (s[0]-'0')*10 + (s[1]-'0') - 1; /* month is 0-11 in struct tm */
+ s += 2;
+ if (isdigit(s[0]) && isdigit(s[1]))
+ {
+ tm.tm_mday = (s[0]-'0')*10 + (s[1]-'0');
+ s += 2;
+ if (isdigit(s[0]) && isdigit(s[1]))
+ {
+ tm.tm_hour = (s[0]-'0')*10 + (s[1]-'0');
+ s += 2;
+ if (isdigit(s[0]) && isdigit(s[1]))
+ {
+ tm.tm_min = (s[0]-'0')*10 + (s[1]-'0');
+ s += 2;
+ if (isdigit(s[0]) && isdigit(s[1]))
+ {
+ tm.tm_sec = (s[0]-'0')*10 + (s[1]-'0');
+ s += 2;
+ }
+ }
+ }
+ }
+ }
+
+ if (s[0] == 'Z')
+ {
+ s += 1;
+ }
+ else if ((s[0] == '-' || s[0] == '+') && isdigit(s[1]) && isdigit(s[2]))
+ {
+ tz_sign = (s[0] == '-') ? -1 : 1;
+ tz_hour = (s[1]-'0')*10 + (s[2]-'0');
+ s += 3;
+ if (s[0] == '\'' && isdigit(s[1]) && isdigit(s[2]))
+ {
+ tz_min = (s[1]-'0')*10 + (s[2]-'0');
+ s += 3;
+ if (s[0] == '\'')
+ s += 1;
+ }
+ }
+
+ if (s[0] != 0)
+ fz_warn(ctx, "invalid date format (garbage at end)");
+
+ utc = timegm(&tm);
+ if (utc == (time_t)-1)
+ {
+ fz_warn(ctx, "date overflow error");
+ return 0;
+ }
+
+ tz_adj = tz_sign * (tz_hour * 3600 + tz_min * 60);
+ return utc - tz_adj;
+}
+
+static pdf_obj *markup_subtypes[] = {
+ PDF_NAME(Text),
+ PDF_NAME(FreeText),
+ PDF_NAME(Line),
+ PDF_NAME(Square),
+ PDF_NAME(Circle),
+ PDF_NAME(Polygon),
+ PDF_NAME(PolyLine),
+ PDF_NAME(Highlight),
+ PDF_NAME(Underline),
+ PDF_NAME(Squiggly),
+ PDF_NAME(StrikeOut),
+ PDF_NAME(Stamp),
+ PDF_NAME(Caret),
+ PDF_NAME(Ink),
+ PDF_NAME(FileAttachment),
+ PDF_NAME(Sound),
+ NULL,
+};
+
+int64_t
+pdf_annot_modification_date(fz_context *ctx, pdf_annot *annot)
+{
+ pdf_obj *date = pdf_dict_get(ctx, annot->obj, PDF_NAME(M));
+ return date ? pdf_parse_date(ctx, pdf_to_str_buf(ctx, date)) : 0;
+}
+
+void
+pdf_set_annot_modification_date(fz_context *ctx, pdf_annot *annot, int64_t secs)
+{
+ char s[40];
+
+ check_allowed_subtypes(ctx, annot, PDF_NAME(M), markup_subtypes);
+
+ pdf_format_date(ctx, s, sizeof s, secs);
+ pdf_dict_put_string(ctx, annot->obj, PDF_NAME(M), s, strlen(s));
+ pdf_dirty_annot(ctx, annot);
+}
+
+int
+pdf_annot_has_author(fz_context *ctx, pdf_annot *annot)
+{
+ return is_allowed_subtype(ctx, annot, PDF_NAME(T), markup_subtypes);
+}
+
+char *
+pdf_copy_annot_author(fz_context *ctx, pdf_annot *annot)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(T), markup_subtypes);
+ return pdf_to_utf8(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(T)));
+}
+
+void
+pdf_set_annot_author(fz_context *ctx, pdf_annot *annot, const char *author)
+{
+ check_allowed_subtypes(ctx, annot, PDF_NAME(T), markup_subtypes);
+ pdf_dict_put_text_string(ctx, annot->obj, PDF_NAME(T), author);
+ pdf_dirty_annot(ctx, annot);
+}
+
+void
+pdf_parse_default_appearance(fz_context *ctx, const char *da, const char **font, float *size, float color[3])
+{
+ char buf[100], *p = buf, *tok, *end;
+ float stack[3] = { 0, 0, 0 };
+ int top = 0;
+
+ *font = "Helv";
+ *size = 12;
+ color[0] = color[1] = color[2] = 0;
+
+ fz_strlcpy(buf, da, sizeof buf);
+ while ((tok = fz_strsep(&p, " ")) != NULL)
+ {
+ if (tok[0] == 0)
+ ;
+ else if (tok[0] == '/')
+ {
+ if (!strcmp(tok+1, "Cour")) *font = "Cour";
+ if (!strcmp(tok+1, "Helv")) *font = "Helv";
+ if (!strcmp(tok+1, "TiRo")) *font = "TiRo";
+ if (!strcmp(tok+1, "Symb")) *font = "Symb";
+ if (!strcmp(tok+1, "ZaDb")) *font = "ZaDb";
+ }
+ else if (!strcmp(tok, "Tf"))
+ {
+ *size = stack[0];
+ top = 0;
+ }
+ else if (!strcmp(tok, "g"))
+ {
+ color[0] = color[1] = color[2] = stack[0];
+ top = 0;
+ }
+ else if (!strcmp(tok, "rg"))
+ {
+ color[0] = stack[0];
+ color[1] = stack[1];
+ color[2] = stack[2];
+ top=0;
+ }
+ else
+ {
+ if (top < 3)
+ stack[top] = fz_strtof(tok, &end);
+ if (*end == 0)
+ ++top;
+ else
+ top = 0;
+ }
+ }
+}
+
+void
+pdf_print_default_appearance(fz_context *ctx, char *buf, int nbuf, const char *font, float size, const float color[3])
+{
+ if (color[0] > 0 || color[1] > 0 || color[2] > 0)
+ fz_snprintf(buf, nbuf, "/%s %g Tf %g %g %g rg", font, size, color[0], color[1], color[2]);
+ else
+ fz_snprintf(buf, nbuf, "/%s %g Tf", font, size);
+}
+
+void
+pdf_annot_default_appearance(fz_context *ctx, pdf_annot *annot, const char **font, float *size, float color[3])
+{
+ pdf_obj *da = pdf_dict_get(ctx, annot->obj, PDF_NAME(DA));
+ pdf_parse_default_appearance(ctx, pdf_to_str_buf(ctx, da), font, size, color);
+}
+
+void
+pdf_set_annot_default_appearance(fz_context *ctx, pdf_annot *annot, const char *font, float size, const float color[3])
+{
+ char buf[100];
+
+ pdf_print_default_appearance(ctx, buf, sizeof buf, font, size, color);
+
+ pdf_dict_put_string(ctx, annot->obj, PDF_NAME(DA), buf, strlen(buf));
+
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(DS)); /* not supported */
+ pdf_dict_del(ctx, annot->obj, PDF_NAME(RC)); /* not supported */
+
+ pdf_dirty_annot(ctx, annot);
+}