1#include "mupdf/fitz.h"
2#include "mupdf/pdf.h"
3#include "../fitz/fitz-imp.h" // ick!
4
5#include <string.h>
6#include <time.h>
7
8#ifdef _WIN32
9#define timegm _mkgmtime
10#endif
11
12#define isdigit(c) (c >= '0' && c <= '9')
13
14pdf_annot *
15pdf_keep_annot(fz_context *ctx, pdf_annot *annot)
16{
17 return fz_keep_imp(ctx, annot, &annot->refs);
18}
19
20void
21pdf_drop_annot(fz_context *ctx, pdf_annot *annot)
22{
23 if (fz_drop_imp(ctx, annot, &annot->refs))
24 {
25 pdf_drop_obj(ctx, annot->ap);
26 pdf_drop_obj(ctx, annot->obj);
27 fz_free(ctx, annot);
28 }
29}
30
31void
32pdf_drop_annots(fz_context *ctx, pdf_annot *annot)
33{
34 while (annot)
35 {
36 pdf_annot *next = annot->next;
37 pdf_drop_annot(ctx, annot);
38 annot = next;
39 }
40}
41
42/* Create transform to fit appearance stream to annotation Rect */
43fz_matrix
44pdf_annot_transform(fz_context *ctx, pdf_annot *annot)
45{
46 fz_rect bbox, rect;
47 fz_matrix matrix;
48 float w, h, x, y;
49
50 rect = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
51 bbox = pdf_xobject_bbox(ctx, annot->ap);
52 matrix = pdf_xobject_matrix(ctx, annot->ap);
53
54 bbox = fz_transform_rect(bbox, matrix);
55 if (bbox.x1 == bbox.x0)
56 w = 0;
57 else
58 w = (rect.x1 - rect.x0) / (bbox.x1 - bbox.x0);
59 if (bbox.y1 == bbox.y0)
60 h = 0;
61 else
62 h = (rect.y1 - rect.y0) / (bbox.y1 - bbox.y0);
63 x = rect.x0 - bbox.x0;
64 y = rect.y0 - bbox.y0;
65
66 return fz_pre_scale(fz_translate(x, y), w, h);
67}
68
69/*
70 Internal function for creating a new pdf annotation.
71*/
72static pdf_annot *
73pdf_new_annot(fz_context *ctx, pdf_page *page, pdf_obj *obj)
74{
75 pdf_annot *annot;
76
77 annot = fz_malloc_struct(ctx, pdf_annot);
78 annot->refs = 1;
79 annot->page = page; /* only borrowed, as the page owns the annot */
80 annot->obj = pdf_keep_obj(ctx, obj);
81
82 return annot;
83}
84
85void
86pdf_load_annots(fz_context *ctx, pdf_page *page, pdf_obj *annots)
87{
88 pdf_annot *annot;
89 pdf_obj *subtype;
90 int i, n;
91
92 n = pdf_array_len(ctx, annots);
93 for (i = 0; i < n; ++i)
94 {
95 pdf_obj *obj = pdf_array_get(ctx, annots, i);
96 if (pdf_is_dict(ctx, obj))
97 {
98 subtype = pdf_dict_get(ctx, obj, PDF_NAME(Subtype));
99 if (pdf_name_eq(ctx, subtype, PDF_NAME(Link)))
100 continue;
101 if (pdf_name_eq(ctx, subtype, PDF_NAME(Popup)))
102 continue;
103
104 annot = pdf_new_annot(ctx, page, obj);
105 fz_try(ctx)
106 {
107 pdf_update_annot(ctx, annot);
108 annot->has_new_ap = 0;
109 }
110 fz_catch(ctx)
111 fz_warn(ctx, "could not update appearance for annotation");
112
113 if (pdf_name_eq(ctx, subtype, PDF_NAME(Widget)))
114 {
115 *page->widget_tailp = annot;
116 page->widget_tailp = &annot->next;
117 }
118 else
119 {
120 *page->annot_tailp = annot;
121 page->annot_tailp = &annot->next;
122 }
123 }
124 }
125}
126
127pdf_annot *
128pdf_first_annot(fz_context *ctx, pdf_page *page)
129{
130 return page->annots;
131}
132
133pdf_annot *
134pdf_next_annot(fz_context *ctx, pdf_annot *annot)
135{
136 return annot->next;
137}
138
139/*
140 Return the rectangle for an annotation on a page.
141*/
142fz_rect
143pdf_bound_annot(fz_context *ctx, pdf_annot *annot)
144{
145 fz_matrix page_ctm;
146 fz_rect rect;
147 int flags;
148
149 rect = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
150 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
151
152 flags = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
153 if (flags & PDF_ANNOT_IS_NO_ROTATE)
154 {
155 int rotate = pdf_to_int(ctx, pdf_dict_get_inheritable(ctx, annot->page->obj, PDF_NAME(Rotate)));
156 fz_point tp = fz_transform_point_xy(rect.x0, rect.y1, page_ctm);
157 page_ctm = fz_concat(page_ctm, fz_translate(-tp.x, -tp.y));
158 page_ctm = fz_concat(page_ctm, fz_rotate(-rotate));
159 page_ctm = fz_concat(page_ctm, fz_translate(tp.x, tp.y));
160 }
161
162 return fz_transform_rect(rect, page_ctm);
163}
164
165void
166pdf_dirty_annot(fz_context *ctx, pdf_annot *annot)
167{
168 annot->needs_new_ap = 1;
169 if (annot->page && annot->page->doc)
170 annot->page->doc->dirty = 1;
171}
172
173const char *
174pdf_string_from_annot_type(fz_context *ctx, enum pdf_annot_type type)
175{
176 switch (type)
177 {
178 case PDF_ANNOT_TEXT: return "Text";
179 case PDF_ANNOT_LINK: return "Link";
180 case PDF_ANNOT_FREE_TEXT: return "FreeText";
181 case PDF_ANNOT_LINE: return "Line";
182 case PDF_ANNOT_SQUARE: return "Square";
183 case PDF_ANNOT_CIRCLE: return "Circle";
184 case PDF_ANNOT_POLYGON: return "Polygon";
185 case PDF_ANNOT_POLY_LINE: return "PolyLine";
186 case PDF_ANNOT_HIGHLIGHT: return "Highlight";
187 case PDF_ANNOT_UNDERLINE: return "Underline";
188 case PDF_ANNOT_SQUIGGLY: return "Squiggly";
189 case PDF_ANNOT_STRIKE_OUT: return "StrikeOut";
190 case PDF_ANNOT_REDACT: return "Redact";
191 case PDF_ANNOT_STAMP: return "Stamp";
192 case PDF_ANNOT_CARET: return "Caret";
193 case PDF_ANNOT_INK: return "Ink";
194 case PDF_ANNOT_POPUP: return "Popup";
195 case PDF_ANNOT_FILE_ATTACHMENT: return "FileAttachment";
196 case PDF_ANNOT_SOUND: return "Sound";
197 case PDF_ANNOT_MOVIE: return "Movie";
198 case PDF_ANNOT_WIDGET: return "Widget";
199 case PDF_ANNOT_SCREEN: return "Screen";
200 case PDF_ANNOT_PRINTER_MARK: return "PrinterMark";
201 case PDF_ANNOT_TRAP_NET: return "TrapNet";
202 case PDF_ANNOT_WATERMARK: return "Watermark";
203 case PDF_ANNOT_3D: return "3D";
204 default: return "UNKNOWN";
205 }
206}
207
208int
209pdf_annot_type_from_string(fz_context *ctx, const char *subtype)
210{
211 if (!strcmp("Text", subtype)) return PDF_ANNOT_TEXT;
212 if (!strcmp("Link", subtype)) return PDF_ANNOT_LINK;
213 if (!strcmp("FreeText", subtype)) return PDF_ANNOT_FREE_TEXT;
214 if (!strcmp("Line", subtype)) return PDF_ANNOT_LINE;
215 if (!strcmp("Square", subtype)) return PDF_ANNOT_SQUARE;
216 if (!strcmp("Circle", subtype)) return PDF_ANNOT_CIRCLE;
217 if (!strcmp("Polygon", subtype)) return PDF_ANNOT_POLYGON;
218 if (!strcmp("PolyLine", subtype)) return PDF_ANNOT_POLY_LINE;
219 if (!strcmp("Highlight", subtype)) return PDF_ANNOT_HIGHLIGHT;
220 if (!strcmp("Underline", subtype)) return PDF_ANNOT_UNDERLINE;
221 if (!strcmp("Squiggly", subtype)) return PDF_ANNOT_SQUIGGLY;
222 if (!strcmp("StrikeOut", subtype)) return PDF_ANNOT_STRIKE_OUT;
223 if (!strcmp("Redact", subtype)) return PDF_ANNOT_REDACT;
224 if (!strcmp("Stamp", subtype)) return PDF_ANNOT_STAMP;
225 if (!strcmp("Caret", subtype)) return PDF_ANNOT_CARET;
226 if (!strcmp("Ink", subtype)) return PDF_ANNOT_INK;
227 if (!strcmp("Popup", subtype)) return PDF_ANNOT_POPUP;
228 if (!strcmp("FileAttachment", subtype)) return PDF_ANNOT_FILE_ATTACHMENT;
229 if (!strcmp("Sound", subtype)) return PDF_ANNOT_SOUND;
230 if (!strcmp("Movie", subtype)) return PDF_ANNOT_MOVIE;
231 if (!strcmp("Widget", subtype)) return PDF_ANNOT_WIDGET;
232 if (!strcmp("Screen", subtype)) return PDF_ANNOT_SCREEN;
233 if (!strcmp("PrinterMark", subtype)) return PDF_ANNOT_PRINTER_MARK;
234 if (!strcmp("TrapNet", subtype)) return PDF_ANNOT_TRAP_NET;
235 if (!strcmp("Watermark", subtype)) return PDF_ANNOT_WATERMARK;
236 if (!strcmp("3D", subtype)) return PDF_ANNOT_3D;
237 return PDF_ANNOT_UNKNOWN;
238}
239
240static int is_allowed_subtype(fz_context *ctx, pdf_annot *annot, pdf_obj *property, pdf_obj **allowed)
241{
242 pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
243 while (*allowed) {
244 if (pdf_name_eq(ctx, subtype, *allowed))
245 return 1;
246 allowed++;
247 }
248
249 return 0;
250}
251
252static void check_allowed_subtypes(fz_context *ctx, pdf_annot *annot, pdf_obj *property, pdf_obj **allowed)
253{
254 pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
255 if (!is_allowed_subtype(ctx, annot, property, allowed))
256 fz_throw(ctx, FZ_ERROR_GENERIC, "%s annotations have no %s property", pdf_to_name(ctx, subtype), pdf_to_name(ctx, property));
257}
258
259/*
260 create a new annotation of the specified type on the
261 specified page. The returned pdf_annot structure is owned by the page
262 and does not need to be freed.
263*/
264pdf_annot *
265pdf_create_annot_raw(fz_context *ctx, pdf_page *page, enum pdf_annot_type type)
266{
267 pdf_annot *annot = NULL;
268 pdf_document *doc = page->doc;
269 pdf_obj *annot_obj = pdf_new_dict(ctx, doc, 0);
270 pdf_obj *ind_obj = NULL;
271
272 fz_var(annot);
273 fz_var(ind_obj);
274 fz_try(ctx)
275 {
276 int ind_obj_num;
277 const char *type_str;
278 pdf_obj *annot_arr;
279
280 type_str = pdf_string_from_annot_type(ctx, type);
281 if (type == PDF_ANNOT_UNKNOWN)
282 fz_throw(ctx, FZ_ERROR_GENERIC, "cannot create unknown annotation");
283
284 annot_arr = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
285 if (annot_arr == NULL)
286 {
287 annot_arr = pdf_new_array(ctx, doc, 0);
288 pdf_dict_put_drop(ctx, page->obj, PDF_NAME(Annots), annot_arr);
289 }
290
291 pdf_dict_put(ctx, annot_obj, PDF_NAME(Type), PDF_NAME(Annot));
292 pdf_dict_put_name(ctx, annot_obj, PDF_NAME(Subtype), type_str);
293
294 /*
295 Both annotation object and annotation structure are now created.
296 Insert the object in the hierarchy and the structure in the
297 page's array.
298 */
299 ind_obj_num = pdf_create_object(ctx, doc);
300 pdf_update_object(ctx, doc, ind_obj_num, annot_obj);
301 ind_obj = pdf_new_indirect(ctx, doc, ind_obj_num, 0);
302 pdf_array_push(ctx, annot_arr, ind_obj);
303
304 annot = pdf_new_annot(ctx, page, ind_obj);
305 annot->ap = NULL;
306
307 /*
308 Linking must be done after any call that might throw because
309 pdf_drop_annots below actually frees a list. Put the new annot
310 at the end of the list, so that it will be drawn last.
311 */
312 *page->annot_tailp = annot;
313 page->annot_tailp = &annot->next;
314
315 doc->dirty = 1;
316 }
317 fz_always(ctx)
318 {
319 pdf_drop_obj(ctx, annot_obj);
320 pdf_drop_obj(ctx, ind_obj);
321 }
322 fz_catch(ctx)
323 {
324 pdf_drop_annots(ctx, annot);
325 fz_rethrow(ctx);
326 }
327
328 return annot;
329}
330
331pdf_annot *
332pdf_create_annot(fz_context *ctx, pdf_page *page, enum pdf_annot_type type)
333{
334 static const float black[3] = { 0, 0, 0 };
335 static const float red[3] = { 1, 0, 0 };
336 static const float green[3] = { 0, 1, 0 };
337 static const float blue[3] = { 0, 0, 1 };
338 static const float yellow[3] = { 1, 1, 0 };
339 static const float magenta[3] = { 1, 0, 1 };
340
341 int flags = PDF_ANNOT_IS_PRINT; /* Make printable as default */
342
343 pdf_annot *annot = pdf_create_annot_raw(ctx, page, type);
344
345 switch (type)
346 {
347 default:
348 break;
349
350 case PDF_ANNOT_TEXT:
351 case PDF_ANNOT_FILE_ATTACHMENT:
352 case PDF_ANNOT_SOUND:
353 {
354 fz_rect icon_rect = { 12, 12, 12+20, 12+20 };
355 flags = PDF_ANNOT_IS_PRINT | PDF_ANNOT_IS_NO_ZOOM | PDF_ANNOT_IS_NO_ROTATE;
356 pdf_set_annot_rect(ctx, annot, icon_rect);
357 pdf_set_annot_color(ctx, annot, 3, yellow);
358 }
359 break;
360
361 case PDF_ANNOT_FREE_TEXT:
362 {
363 fz_rect text_rect = { 12, 12, 12+200, 12+100 };
364
365 /* Use undocumented Adobe property to match page rotation. */
366 int rot = pdf_to_int(ctx, pdf_dict_get_inheritable(ctx, page->obj, PDF_NAME(Rotate)));
367 if (rot != 0)
368 pdf_dict_put_int(ctx, annot->obj, PDF_NAME(Rotate), rot);
369
370 pdf_set_annot_rect(ctx, annot, text_rect);
371 pdf_set_annot_border(ctx, annot, 0);
372 pdf_set_annot_default_appearance(ctx, annot, "Helv", 12, black);
373 }
374 break;
375
376 case PDF_ANNOT_STAMP:
377 {
378 fz_rect stamp_rect = { 12, 12, 12+190, 12+50 };
379 pdf_set_annot_rect(ctx, annot, stamp_rect);
380 pdf_set_annot_color(ctx, annot, 3, red);
381 }
382 break;
383
384 case PDF_ANNOT_CARET:
385 {
386 fz_rect caret_rect = { 12, 12, 12+18, 12+15 };
387 pdf_set_annot_rect(ctx, annot, caret_rect);
388 pdf_set_annot_color(ctx, annot, 3, blue);
389 }
390 break;
391
392 case PDF_ANNOT_LINE:
393 {
394 fz_point a = { 12, 12 }, b = { 12 + 100, 12 + 50 };
395 pdf_set_annot_line(ctx, annot, a, b);
396 pdf_set_annot_border(ctx, annot, 1);
397 pdf_set_annot_color(ctx, annot, 3, red);
398 }
399 break;
400
401 case PDF_ANNOT_SQUARE:
402 case PDF_ANNOT_CIRCLE:
403 {
404 fz_rect shape_rect = { 12, 12, 12+100, 12+50 };
405 pdf_set_annot_rect(ctx, annot, shape_rect);
406 pdf_set_annot_border(ctx, annot, 1);
407 pdf_set_annot_color(ctx, annot, 3, red);
408 }
409 break;
410
411 case PDF_ANNOT_POLYGON:
412 case PDF_ANNOT_POLY_LINE:
413 case PDF_ANNOT_INK:
414 pdf_set_annot_border(ctx, annot, 1);
415 pdf_set_annot_color(ctx, annot, 3, red);
416 break;
417
418 case PDF_ANNOT_HIGHLIGHT:
419 pdf_set_annot_color(ctx, annot, 3, yellow);
420 break;
421 case PDF_ANNOT_UNDERLINE:
422 pdf_set_annot_color(ctx, annot, 3, green);
423 break;
424 case PDF_ANNOT_STRIKE_OUT:
425 pdf_set_annot_color(ctx, annot, 3, red);
426 break;
427 case PDF_ANNOT_SQUIGGLY:
428 pdf_set_annot_color(ctx, annot, 3, magenta);
429 break;
430 }
431
432 pdf_dict_put(ctx, annot->obj, PDF_NAME(P), page->obj);
433 pdf_dict_put_int(ctx, annot->obj, PDF_NAME(F), flags);
434
435 return pdf_keep_annot(ctx, annot);
436}
437
438void
439pdf_delete_annot(fz_context *ctx, pdf_page *page, pdf_annot *annot)
440{
441 pdf_document *doc = annot->page->doc;
442 pdf_annot **annotptr;
443 pdf_obj *annot_arr;
444 int i;
445
446 if (annot == NULL)
447 return;
448
449 /* Remove annot from page's list */
450 for (annotptr = &page->annots; *annotptr; annotptr = &(*annotptr)->next)
451 {
452 if (*annotptr == annot)
453 break;
454 }
455
456 /* Check the passed annotation was of this page */
457 if (*annotptr == NULL)
458 return;
459
460 *annotptr = annot->next;
461
462 /* If the removed annotation was the last in the list adjust the end pointer */
463 if (*annotptr == NULL)
464 page->annot_tailp = annotptr;
465
466 /* Remove the annot from the "Annots" array. */
467 annot_arr = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
468 i = pdf_array_find(ctx, annot_arr, annot->obj);
469 if (i >= 0)
470 pdf_array_delete(ctx, annot_arr, i);
471
472 /* The garbage collection pass when saving will remove the annot object,
473 * removing it here may break files if multiple pages use the same annot. */
474
475 /* And free it. */
476 pdf_drop_annot(ctx, annot);
477
478 doc->dirty = 1;
479}
480
481enum pdf_annot_type
482pdf_annot_type(fz_context *ctx, pdf_annot *annot)
483{
484 pdf_obj *obj = annot->obj;
485 pdf_obj *subtype = pdf_dict_get(ctx, obj, PDF_NAME(Subtype));
486 return pdf_annot_type_from_string(ctx, pdf_to_name(ctx, subtype));
487}
488
489int
490pdf_annot_flags(fz_context *ctx, pdf_annot *annot)
491{
492 return pdf_dict_get_int(ctx, annot->obj, PDF_NAME(F));
493}
494
495void
496pdf_set_annot_flags(fz_context *ctx, pdf_annot *annot, int flags)
497{
498 pdf_dict_put_int(ctx, annot->obj, PDF_NAME(F), flags);
499 pdf_dirty_annot(ctx, annot);
500}
501
502fz_rect
503pdf_annot_rect(fz_context *ctx, pdf_annot *annot)
504{
505 fz_matrix page_ctm;
506 fz_rect annot_rect;
507 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
508 annot_rect = pdf_dict_get_rect(ctx, annot->obj, PDF_NAME(Rect));
509 return fz_transform_rect(annot_rect, page_ctm);
510}
511
512void
513pdf_set_annot_rect(fz_context *ctx, pdf_annot *annot, fz_rect rect)
514{
515 fz_matrix page_ctm, inv_page_ctm;
516
517 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
518 inv_page_ctm = fz_invert_matrix(page_ctm);
519 rect = fz_transform_rect(rect, inv_page_ctm);
520
521 pdf_dict_put_rect(ctx, annot->obj, PDF_NAME(Rect), rect);
522 pdf_dirty_annot(ctx, annot);
523}
524
525const char *
526pdf_annot_contents(fz_context *ctx, pdf_annot *annot)
527{
528 return pdf_dict_get_text_string(ctx, annot->obj, PDF_NAME(Contents));
529}
530
531void
532pdf_set_annot_contents(fz_context *ctx, pdf_annot *annot, const char *text)
533{
534 pdf_dict_put_text_string(ctx, annot->obj, PDF_NAME(Contents), text);
535 pdf_dict_del(ctx, annot->obj, PDF_NAME(RC)); /* not supported */
536 pdf_dirty_annot(ctx, annot);
537}
538
539static pdf_obj *open_subtypes[] = {
540 PDF_NAME(Popup),
541 PDF_NAME(Text),
542 NULL,
543};
544
545int
546pdf_annot_has_open(fz_context *ctx, pdf_annot *annot)
547{
548 return is_allowed_subtype(ctx, annot, PDF_NAME(Open), open_subtypes);
549}
550
551int
552pdf_annot_is_open(fz_context *ctx, pdf_annot *annot)
553{
554 check_allowed_subtypes(ctx, annot, PDF_NAME(Open), open_subtypes);
555 return pdf_dict_get_bool(ctx, annot->obj, PDF_NAME(Open));
556}
557
558void
559pdf_set_annot_is_open(fz_context *ctx, pdf_annot *annot, int is_open)
560{
561 check_allowed_subtypes(ctx, annot, PDF_NAME(Open), open_subtypes);
562 pdf_dict_put_bool(ctx, annot->obj, PDF_NAME(Open), is_open);
563 pdf_dirty_annot(ctx, annot);
564}
565
566static pdf_obj *icon_name_subtypes[] = {
567 PDF_NAME(FileAttachment),
568 PDF_NAME(Sound),
569 PDF_NAME(Stamp),
570 PDF_NAME(Text),
571 NULL,
572};
573
574int
575pdf_annot_has_icon_name(fz_context *ctx, pdf_annot *annot)
576{
577 return is_allowed_subtype(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
578}
579
580const char *
581pdf_annot_icon_name(fz_context *ctx, pdf_annot *annot)
582{
583 pdf_obj *name;
584 check_allowed_subtypes(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
585 name = pdf_dict_get(ctx, annot->obj, PDF_NAME(Name));
586 if (!name)
587 {
588 pdf_obj *subtype = pdf_dict_get(ctx, annot->obj, PDF_NAME(Subtype));
589 if (pdf_name_eq(ctx, subtype, PDF_NAME(Text)))
590 return "Note";
591 if (pdf_name_eq(ctx, subtype, PDF_NAME(Stamp)))
592 return "Draft";
593 if (pdf_name_eq(ctx, subtype, PDF_NAME(FileAttachment)))
594 return "PushPin";
595 if (pdf_name_eq(ctx, subtype, PDF_NAME(Sound)))
596 return "Speaker";
597 }
598 return pdf_to_name(ctx, name);
599}
600
601void
602pdf_set_annot_icon_name(fz_context *ctx, pdf_annot *annot, const char *name)
603{
604 check_allowed_subtypes(ctx, annot, PDF_NAME(Name), icon_name_subtypes);
605 pdf_dict_put_name(ctx, annot->obj, PDF_NAME(Name), name);
606 pdf_dirty_annot(ctx, annot);
607}
608
609enum pdf_line_ending pdf_line_ending_from_name(fz_context *ctx, pdf_obj *end)
610{
611 if (pdf_name_eq(ctx, end, PDF_NAME(None))) return PDF_ANNOT_LE_NONE;
612 else if (pdf_name_eq(ctx, end, PDF_NAME(Square))) return PDF_ANNOT_LE_SQUARE;
613 else if (pdf_name_eq(ctx, end, PDF_NAME(Circle))) return PDF_ANNOT_LE_CIRCLE;
614 else if (pdf_name_eq(ctx, end, PDF_NAME(Diamond))) return PDF_ANNOT_LE_DIAMOND;
615 else if (pdf_name_eq(ctx, end, PDF_NAME(OpenArrow))) return PDF_ANNOT_LE_OPEN_ARROW;
616 else if (pdf_name_eq(ctx, end, PDF_NAME(ClosedArrow))) return PDF_ANNOT_LE_CLOSED_ARROW;
617 else if (pdf_name_eq(ctx, end, PDF_NAME(Butt))) return PDF_ANNOT_LE_BUTT;
618 else if (pdf_name_eq(ctx, end, PDF_NAME(ROpenArrow))) return PDF_ANNOT_LE_R_OPEN_ARROW;
619 else if (pdf_name_eq(ctx, end, PDF_NAME(RClosedArrow))) return PDF_ANNOT_LE_R_CLOSED_ARROW;
620 else if (pdf_name_eq(ctx, end, PDF_NAME(Slash))) return PDF_ANNOT_LE_SLASH;
621 else return PDF_ANNOT_LE_NONE;
622}
623
624enum pdf_line_ending pdf_line_ending_from_string(fz_context *ctx, const char *end)
625{
626 if (!strcmp(end, "None")) return PDF_ANNOT_LE_NONE;
627 else if (!strcmp(end, "Square")) return PDF_ANNOT_LE_SQUARE;
628 else if (!strcmp(end, "Circle")) return PDF_ANNOT_LE_CIRCLE;
629 else if (!strcmp(end, "Diamond")) return PDF_ANNOT_LE_DIAMOND;
630 else if (!strcmp(end, "OpenArrow")) return PDF_ANNOT_LE_OPEN_ARROW;
631 else if (!strcmp(end, "ClosedArrow")) return PDF_ANNOT_LE_CLOSED_ARROW;
632 else if (!strcmp(end, "Butt")) return PDF_ANNOT_LE_BUTT;
633 else if (!strcmp(end, "ROpenArrow")) return PDF_ANNOT_LE_R_OPEN_ARROW;
634 else if (!strcmp(end, "RClosedArrow")) return PDF_ANNOT_LE_R_CLOSED_ARROW;
635 else if (!strcmp(end, "Slash")) return PDF_ANNOT_LE_SLASH;
636 else return PDF_ANNOT_LE_NONE;
637}
638
639pdf_obj *pdf_name_from_line_ending(fz_context *ctx, enum pdf_line_ending end)
640{
641 switch (end)
642 {
643 default:
644 case PDF_ANNOT_LE_NONE: return PDF_NAME(None);
645 case PDF_ANNOT_LE_SQUARE: return PDF_NAME(Square);
646 case PDF_ANNOT_LE_CIRCLE: return PDF_NAME(Circle);
647 case PDF_ANNOT_LE_DIAMOND: return PDF_NAME(Diamond);
648 case PDF_ANNOT_LE_OPEN_ARROW: return PDF_NAME(OpenArrow);
649 case PDF_ANNOT_LE_CLOSED_ARROW: return PDF_NAME(ClosedArrow);
650 case PDF_ANNOT_LE_BUTT: return PDF_NAME(Butt);
651 case PDF_ANNOT_LE_R_OPEN_ARROW: return PDF_NAME(ROpenArrow);
652 case PDF_ANNOT_LE_R_CLOSED_ARROW: return PDF_NAME(RClosedArrow);
653 case PDF_ANNOT_LE_SLASH: return PDF_NAME(Slash);
654 }
655}
656
657const char *pdf_string_from_line_ending(fz_context *ctx, enum pdf_line_ending end)
658{
659 switch (end)
660 {
661 default:
662 case PDF_ANNOT_LE_NONE: return "None";
663 case PDF_ANNOT_LE_SQUARE: return "Square";
664 case PDF_ANNOT_LE_CIRCLE: return "Circle";
665 case PDF_ANNOT_LE_DIAMOND: return "Diamond";
666 case PDF_ANNOT_LE_OPEN_ARROW: return "OpenArrow";
667 case PDF_ANNOT_LE_CLOSED_ARROW: return "ClosedArrow";
668 case PDF_ANNOT_LE_BUTT: return "Butt";
669 case PDF_ANNOT_LE_R_OPEN_ARROW: return "ROpenArrow";
670 case PDF_ANNOT_LE_R_CLOSED_ARROW: return "RClosedArrow";
671 case PDF_ANNOT_LE_SLASH: return "Slash";
672 }
673}
674
675static pdf_obj *line_ending_subtypes[] = {
676 PDF_NAME(FreeText),
677 PDF_NAME(Line),
678 PDF_NAME(PolyLine),
679 PDF_NAME(Polygon),
680 NULL,
681};
682
683int
684pdf_annot_has_line_ending_styles(fz_context *ctx, pdf_annot *annot)
685{
686 return is_allowed_subtype(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
687}
688
689void
690pdf_annot_line_ending_styles(fz_context *ctx, pdf_annot *annot,
691 enum pdf_line_ending *start_style,
692 enum pdf_line_ending *end_style)
693{
694 pdf_obj *style;
695 check_allowed_subtypes(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
696 style = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
697 *start_style = pdf_line_ending_from_name(ctx, pdf_array_get(ctx, style, 0));
698 *end_style = pdf_line_ending_from_name(ctx, pdf_array_get(ctx, style, 1));
699}
700
701enum pdf_line_ending
702pdf_annot_line_start_style(fz_context *ctx, pdf_annot *annot)
703{
704 pdf_obj *le = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
705 return pdf_line_ending_from_name(ctx, pdf_array_get(ctx, le, 0));
706}
707
708enum pdf_line_ending
709pdf_annot_line_end_style(fz_context *ctx, pdf_annot *annot)
710{
711 pdf_obj *le = pdf_dict_get(ctx, annot->obj, PDF_NAME(LE));
712 return pdf_line_ending_from_name(ctx, pdf_array_get(ctx, le, 1));
713}
714
715void
716pdf_set_annot_line_ending_styles(fz_context *ctx, pdf_annot *annot,
717 enum pdf_line_ending start_style,
718 enum pdf_line_ending end_style)
719{
720 pdf_document *doc = annot->page->doc;
721 pdf_obj *style;
722 check_allowed_subtypes(ctx, annot, PDF_NAME(LE), line_ending_subtypes);
723 style = pdf_new_array(ctx, doc, 2);
724 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(LE), style);
725 pdf_array_put_drop(ctx, style, 0, pdf_name_from_line_ending(ctx, start_style));
726 pdf_array_put_drop(ctx, style, 1, pdf_name_from_line_ending(ctx, end_style));
727 pdf_dirty_annot(ctx, annot);
728}
729
730void
731pdf_set_annot_line_start_style(fz_context *ctx, pdf_annot *annot, enum pdf_line_ending s)
732{
733 enum pdf_line_ending e = pdf_annot_line_end_style(ctx, annot);
734 pdf_set_annot_line_ending_styles(ctx, annot, s, e);
735}
736
737void
738pdf_set_annot_line_end_style(fz_context *ctx, pdf_annot *annot, enum pdf_line_ending e)
739{
740 enum pdf_line_ending s = pdf_annot_line_start_style(ctx, annot);
741 pdf_set_annot_line_ending_styles(ctx, annot, s, e);
742}
743
744float
745pdf_annot_border(fz_context *ctx, pdf_annot *annot)
746{
747 pdf_obj *bs, *bs_w;
748 bs = pdf_dict_get(ctx, annot->obj, PDF_NAME(BS));
749 bs_w = pdf_dict_get(ctx, bs, PDF_NAME(W));
750 if (pdf_is_number(ctx, bs_w))
751 return pdf_to_real(ctx, bs_w);
752 return 1;
753}
754
755void
756pdf_set_annot_border(fz_context *ctx, pdf_annot *annot, float w)
757{
758 pdf_obj *bs = pdf_dict_get(ctx, annot->obj, PDF_NAME(BS));
759 if (!pdf_is_dict(ctx, bs))
760 bs = pdf_dict_put_dict(ctx, annot->obj, PDF_NAME(BS), 1);
761 pdf_dict_put_real(ctx, bs, PDF_NAME(W), w);
762
763 pdf_dict_del(ctx, annot->obj, PDF_NAME(Border)); /* deprecated */
764 pdf_dict_del(ctx, annot->obj, PDF_NAME(BE)); /* not supported */
765
766 pdf_dirty_annot(ctx, annot);
767}
768
769int
770pdf_annot_quadding(fz_context *ctx, pdf_annot *annot)
771{
772 int q = pdf_dict_get_int(ctx, annot->obj, PDF_NAME(Q));
773 return (q < 0 || q > 2) ? 0 : q;
774}
775
776void
777pdf_set_annot_quadding(fz_context *ctx, pdf_annot *annot, int q)
778{
779 q = (q < 0 || q > 2) ? 0 : q;
780 pdf_dict_put_int(ctx, annot->obj, PDF_NAME(Q), q);
781 pdf_dirty_annot(ctx, annot);
782}
783
784float pdf_annot_opacity(fz_context *ctx, pdf_annot *annot)
785{
786 pdf_obj *ca = pdf_dict_get(ctx, annot->obj, PDF_NAME(CA));
787 if (pdf_is_number(ctx, ca))
788 return pdf_to_real(ctx, ca);
789 return 1;
790}
791
792void pdf_set_annot_opacity(fz_context *ctx, pdf_annot *annot, float opacity)
793{
794 if (opacity != 1)
795 pdf_dict_put_real(ctx, annot->obj, PDF_NAME(CA), opacity);
796 else
797 pdf_dict_del(ctx, annot->obj, PDF_NAME(CA));
798 pdf_dirty_annot(ctx, annot);
799}
800
801static void pdf_annot_color_imp(fz_context *ctx, pdf_obj *arr, int *n, float color[4])
802{
803 switch (pdf_array_len(ctx, arr))
804 {
805 case 0:
806 if (n)
807 *n = 0;
808 break;
809 case 1:
810 case 2:
811 if (n)
812 *n = 1;
813 if (color)
814 color[0] = pdf_array_get_real(ctx, arr, 0);
815 break;
816 case 3:
817 if (n)
818 *n = 3;
819 if (color)
820 {
821 color[0] = pdf_array_get_real(ctx, arr, 0);
822 color[1] = pdf_array_get_real(ctx, arr, 1);
823 color[2] = pdf_array_get_real(ctx, arr, 2);
824 }
825 break;
826 case 4:
827 default:
828 if (n)
829 *n = 4;
830 if (color)
831 {
832 color[0] = pdf_array_get_real(ctx, arr, 0);
833 color[1] = pdf_array_get_real(ctx, arr, 1);
834 color[2] = pdf_array_get_real(ctx, arr, 2);
835 color[3] = pdf_array_get_real(ctx, arr, 3);
836 }
837 break;
838 }
839}
840
841static int pdf_annot_color_rgb(fz_context *ctx, pdf_obj *arr, float rgb[3])
842{
843 float color[4];
844 int n;
845 pdf_annot_color_imp(ctx, arr, &n, color);
846 if (n == 0)
847 {
848 return 0;
849 }
850 else if (n == 1)
851 {
852 rgb[0] = rgb[1] = rgb[2] = color[0];
853 }
854 else if (n == 3)
855 {
856 rgb[0] = color[0];
857 rgb[1] = color[1];
858 rgb[2] = color[2];
859 }
860 else if (n == 4)
861 {
862 rgb[0] = 1 - fz_min(1, color[0] + color[3]);
863 rgb[1] = 1 - fz_min(1, color[1] + color[3]);
864 rgb[2] = 1 - fz_min(1, color[2] + color[3]);
865 }
866 return 1;
867}
868
869static void pdf_set_annot_color_imp(fz_context *ctx, pdf_annot *annot, pdf_obj *key, int n, const float color[4], pdf_obj **allowed)
870{
871 pdf_document *doc = annot->page->doc;
872 pdf_obj *arr;
873
874 if (allowed)
875 check_allowed_subtypes(ctx, annot, key, allowed);
876 if (n != 0 && n != 1 && n != 3 && n != 4)
877 fz_throw(ctx, FZ_ERROR_GENERIC, "color must be 0, 1, 3 or 4 components");
878 if (!color)
879 fz_throw(ctx, FZ_ERROR_GENERIC, "no color given");
880
881 arr = pdf_new_array(ctx, doc, n);
882 fz_try(ctx)
883 {
884 switch (n)
885 {
886 case 1:
887 pdf_array_push_real(ctx, arr, color[0]);
888 break;
889 case 3:
890 pdf_array_push_real(ctx, arr, color[0]);
891 pdf_array_push_real(ctx, arr, color[1]);
892 pdf_array_push_real(ctx, arr, color[2]);
893 break;
894 case 4:
895 pdf_array_push_real(ctx, arr, color[0]);
896 pdf_array_push_real(ctx, arr, color[1]);
897 pdf_array_push_real(ctx, arr, color[2]);
898 pdf_array_push_real(ctx, arr, color[3]);
899 break;
900 }
901 }
902 fz_catch(ctx)
903 {
904 pdf_drop_obj(ctx, arr);
905 fz_rethrow(ctx);
906 }
907
908 pdf_dict_put_drop(ctx, annot->obj, key, arr);
909 pdf_dirty_annot(ctx, annot);
910}
911
912void
913pdf_annot_color(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
914{
915 pdf_obj *c = pdf_dict_get(ctx, annot->obj, PDF_NAME(C));
916 pdf_annot_color_imp(ctx, c, n, color);
917}
918
919void
920pdf_annot_MK_BG(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
921{
922 pdf_obj *mk_bg = pdf_dict_get(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(MK)), PDF_NAME(BG));
923 pdf_annot_color_imp(ctx, mk_bg, n, color);
924}
925
926int
927pdf_annot_MK_BG_rgb(fz_context *ctx, pdf_annot *annot, float rgb[3])
928{
929 pdf_obj *mk_bg = pdf_dict_get(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(MK)), PDF_NAME(BG));
930 return pdf_annot_color_rgb(ctx, mk_bg, rgb);
931}
932
933void
934pdf_annot_MK_BC(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
935{
936 pdf_obj *mk_bc = pdf_dict_get(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(MK)), PDF_NAME(BC));
937 pdf_annot_color_imp(ctx, mk_bc, n, color);
938}
939
940int
941pdf_annot_MK_BC_rgb(fz_context *ctx, pdf_annot *annot, float rgb[3])
942{
943 pdf_obj *mk_bc = pdf_dict_get(ctx, pdf_dict_get(ctx, annot->obj, PDF_NAME(MK)), PDF_NAME(BC));
944 return pdf_annot_color_rgb(ctx, mk_bc, rgb);
945}
946
947void
948pdf_set_annot_color(fz_context *ctx, pdf_annot *annot, int n, const float color[4])
949{
950 pdf_set_annot_color_imp(ctx, annot, PDF_NAME(C), n, color, NULL);
951}
952
953static pdf_obj *interior_color_subtypes[] = {
954 PDF_NAME(Circle),
955 PDF_NAME(Line),
956 PDF_NAME(PolyLine),
957 PDF_NAME(Polygon),
958 PDF_NAME(Square),
959 NULL,
960};
961
962int
963pdf_annot_has_interior_color(fz_context *ctx, pdf_annot *annot)
964{
965 return is_allowed_subtype(ctx, annot, PDF_NAME(IC), interior_color_subtypes);
966}
967
968void
969pdf_annot_interior_color(fz_context *ctx, pdf_annot *annot, int *n, float color[4])
970{
971 pdf_obj *ic = pdf_dict_get(ctx, annot->obj, PDF_NAME(IC));
972 pdf_annot_color_imp(ctx, ic, n, color);
973}
974
975void
976pdf_set_annot_interior_color(fz_context *ctx, pdf_annot *annot, int n, const float color[4])
977{
978 pdf_set_annot_color_imp(ctx, annot, PDF_NAME(IC), n, color, interior_color_subtypes);
979}
980
981static pdf_obj *line_subtypes[] = {
982 PDF_NAME(Line),
983 NULL,
984};
985
986int
987pdf_annot_has_line(fz_context *ctx, pdf_annot *annot)
988{
989 return is_allowed_subtype(ctx, annot, PDF_NAME(L), line_subtypes);
990}
991
992void
993pdf_annot_line(fz_context *ctx, pdf_annot *annot, fz_point *a, fz_point *b)
994{
995 fz_matrix page_ctm;
996 pdf_obj *line;
997
998 check_allowed_subtypes(ctx, annot, PDF_NAME(L), line_subtypes);
999
1000 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1001
1002 line = pdf_dict_get(ctx, annot->obj, PDF_NAME(L));
1003 a->x = pdf_array_get_real(ctx, line, 0);
1004 a->y = pdf_array_get_real(ctx, line, 1);
1005 b->x = pdf_array_get_real(ctx, line, 2);
1006 b->y = pdf_array_get_real(ctx, line, 3);
1007 *a = fz_transform_point(*a, page_ctm);
1008 *b = fz_transform_point(*b, page_ctm);
1009}
1010
1011void
1012pdf_set_annot_line(fz_context *ctx, pdf_annot *annot, fz_point a, fz_point b)
1013{
1014 fz_matrix page_ctm, inv_page_ctm;
1015 pdf_obj *line;
1016
1017 check_allowed_subtypes(ctx, annot, PDF_NAME(L), line_subtypes);
1018
1019 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1020 inv_page_ctm = fz_invert_matrix(page_ctm);
1021
1022 a = fz_transform_point(a, inv_page_ctm);
1023 b = fz_transform_point(b, inv_page_ctm);
1024
1025 line = pdf_new_array(ctx, annot->page->doc, 4);
1026 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(L), line);
1027 pdf_array_push_real(ctx, line, a.x);
1028 pdf_array_push_real(ctx, line, a.y);
1029 pdf_array_push_real(ctx, line, b.x);
1030 pdf_array_push_real(ctx, line, b.y);
1031
1032 pdf_dirty_annot(ctx, annot);
1033}
1034
1035static pdf_obj *vertices_subtypes[] = {
1036 PDF_NAME(PolyLine),
1037 PDF_NAME(Polygon),
1038 NULL,
1039};
1040
1041int
1042pdf_annot_has_vertices(fz_context *ctx, pdf_annot *annot)
1043{
1044 return is_allowed_subtype(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1045}
1046
1047int
1048pdf_annot_vertex_count(fz_context *ctx, pdf_annot *annot)
1049{
1050 pdf_obj *vertices;
1051 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1052 vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
1053 return pdf_array_len(ctx, vertices) / 2;
1054}
1055
1056fz_point
1057pdf_annot_vertex(fz_context *ctx, pdf_annot *annot, int i)
1058{
1059 pdf_obj *vertices;
1060 fz_matrix page_ctm;
1061 fz_point point;
1062
1063 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1064
1065 vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
1066
1067 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1068
1069 point.x = pdf_array_get_real(ctx, vertices, i * 2);
1070 point.y = pdf_array_get_real(ctx, vertices, i * 2 + 1);
1071 return fz_transform_point(point, page_ctm);
1072}
1073
1074void
1075pdf_set_annot_vertices(fz_context *ctx, pdf_annot *annot, int n, const fz_point *v)
1076{
1077 pdf_document *doc = annot->page->doc;
1078 fz_matrix page_ctm, inv_page_ctm;
1079 pdf_obj *vertices;
1080 fz_point point;
1081 int i;
1082
1083 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1084 if (n <= 0 || !v)
1085 fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of vertices");
1086
1087 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1088 inv_page_ctm = fz_invert_matrix(page_ctm);
1089
1090 vertices = pdf_new_array(ctx, doc, n * 2);
1091 for (i = 0; i < n; ++i)
1092 {
1093 point = fz_transform_point(v[i], inv_page_ctm);
1094 pdf_array_push_real(ctx, vertices, point.x);
1095 pdf_array_push_real(ctx, vertices, point.y);
1096 }
1097 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(Vertices), vertices);
1098 pdf_dirty_annot(ctx, annot);
1099}
1100
1101void pdf_clear_annot_vertices(fz_context *ctx, pdf_annot *annot)
1102{
1103 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1104 pdf_dict_del(ctx, annot->obj, PDF_NAME(Vertices));
1105 pdf_dirty_annot(ctx, annot);
1106}
1107
1108void pdf_add_annot_vertex(fz_context *ctx, pdf_annot *annot, fz_point p)
1109{
1110 pdf_document *doc = annot->page->doc;
1111 fz_matrix page_ctm, inv_page_ctm;
1112 pdf_obj *vertices;
1113
1114 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1115
1116 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1117 inv_page_ctm = fz_invert_matrix(page_ctm);
1118
1119 vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
1120 if (!pdf_is_array(ctx, vertices))
1121 {
1122 vertices = pdf_new_array(ctx, doc, 32);
1123 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(Vertices), vertices);
1124 }
1125
1126 p = fz_transform_point(p, inv_page_ctm);
1127 pdf_array_push_real(ctx, vertices, p.x);
1128 pdf_array_push_real(ctx, vertices, p.y);
1129
1130 pdf_dirty_annot(ctx, annot);
1131}
1132
1133void pdf_set_annot_vertex(fz_context *ctx, pdf_annot *annot, int i, fz_point p)
1134{
1135 fz_matrix page_ctm, inv_page_ctm;
1136 pdf_obj *vertices;
1137
1138 check_allowed_subtypes(ctx, annot, PDF_NAME(Vertices), vertices_subtypes);
1139
1140 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1141 inv_page_ctm = fz_invert_matrix(page_ctm);
1142
1143 p = fz_transform_point(p, inv_page_ctm);
1144
1145 vertices = pdf_dict_get(ctx, annot->obj, PDF_NAME(Vertices));
1146 pdf_array_put_drop(ctx, vertices, i * 2 + 0, pdf_new_real(ctx, p.x));
1147 pdf_array_put_drop(ctx, vertices, i * 2 + 1, pdf_new_real(ctx, p.y));
1148}
1149
1150static pdf_obj *quad_point_subtypes[] = {
1151 PDF_NAME(Highlight),
1152 PDF_NAME(Link),
1153 PDF_NAME(Squiggly),
1154 PDF_NAME(StrikeOut),
1155 PDF_NAME(Underline),
1156 PDF_NAME(Redact),
1157 NULL,
1158};
1159
1160int
1161pdf_annot_has_quad_points(fz_context *ctx, pdf_annot *annot)
1162{
1163 return is_allowed_subtype(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1164}
1165
1166int
1167pdf_annot_quad_point_count(fz_context *ctx, pdf_annot *annot)
1168{
1169 pdf_obj *quad_points;
1170 check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1171 quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
1172 return pdf_array_len(ctx, quad_points) / 8;
1173}
1174
1175void
1176pdf_annot_quad_point(fz_context *ctx, pdf_annot *annot, int idx, float v[8])
1177{
1178 pdf_obj *quad_points;
1179 fz_matrix page_ctm;
1180 int i;
1181
1182 check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1183 quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
1184 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1185
1186 for (i = 0; i < 8; i += 2)
1187 {
1188 fz_point point;
1189 point.x = pdf_array_get_real(ctx, quad_points, idx * 8 + i + 0);
1190 point.y = pdf_array_get_real(ctx, quad_points, idx * 8 + i + 1);
1191 point = fz_transform_point(point, page_ctm);
1192 v[i+0] = point.x;
1193 v[i+1] = point.y;
1194 }
1195}
1196
1197void
1198pdf_set_annot_quad_points(fz_context *ctx, pdf_annot *annot, int n, const float *v)
1199{
1200 pdf_document *doc = annot->page->doc;
1201 fz_matrix page_ctm, inv_page_ctm;
1202 pdf_obj *quad_points;
1203 fz_point point;
1204 int i, k;
1205
1206 check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1207 if (n <= 0 || !v)
1208 fz_throw(ctx, FZ_ERROR_GENERIC, "invalid number of quadrilaterals");
1209
1210 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1211 inv_page_ctm = fz_invert_matrix(page_ctm);
1212
1213 quad_points = pdf_new_array(ctx, doc, n * 8);
1214 for (i = 0; i < n; ++i)
1215 {
1216 for (k = 0; k < 4; ++k)
1217 {
1218 point.x = v[i * 8 + k * 2 + 0];
1219 point.y = v[i * 8 + k * 2 + 1];
1220 point = fz_transform_point(point, inv_page_ctm);
1221 pdf_array_push_real(ctx, quad_points, point.x);
1222 pdf_array_push_real(ctx, quad_points, point.y);
1223 }
1224 }
1225 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(QuadPoints), quad_points);
1226 pdf_dirty_annot(ctx, annot);
1227}
1228
1229void
1230pdf_clear_annot_quad_points(fz_context *ctx, pdf_annot *annot)
1231{
1232 check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1233 pdf_dict_del(ctx, annot->obj, PDF_NAME(QuadPoints));
1234 pdf_dirty_annot(ctx, annot);
1235}
1236
1237void
1238pdf_add_annot_quad_point(fz_context *ctx, pdf_annot *annot, fz_quad quad)
1239{
1240 pdf_document *doc = annot->page->doc;
1241 fz_matrix page_ctm, inv_page_ctm;
1242 pdf_obj *quad_points;
1243
1244 check_allowed_subtypes(ctx, annot, PDF_NAME(QuadPoints), quad_point_subtypes);
1245
1246 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1247 inv_page_ctm = fz_invert_matrix(page_ctm);
1248
1249 quad_points = pdf_dict_get(ctx, annot->obj, PDF_NAME(QuadPoints));
1250 if (!pdf_is_array(ctx, quad_points))
1251 {
1252 quad_points = pdf_new_array(ctx, doc, 8);
1253 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(QuadPoints), quad_points);
1254 }
1255
1256 /* Contrary to the specification, the points within a QuadPoint are NOT ordered
1257 * in a counterclockwise fashion. Experiments with Adobe's implementation
1258 * indicates a cross-wise ordering is intended: ul, ur, ll, lr.
1259 */
1260 quad = fz_transform_quad(quad, inv_page_ctm);
1261 pdf_array_push_real(ctx, quad_points, quad.ul.x);
1262 pdf_array_push_real(ctx, quad_points, quad.ul.y);
1263 pdf_array_push_real(ctx, quad_points, quad.ur.x);
1264 pdf_array_push_real(ctx, quad_points, quad.ur.y);
1265 pdf_array_push_real(ctx, quad_points, quad.ll.x);
1266 pdf_array_push_real(ctx, quad_points, quad.ll.y);
1267 pdf_array_push_real(ctx, quad_points, quad.lr.x);
1268 pdf_array_push_real(ctx, quad_points, quad.lr.y);
1269
1270 pdf_dirty_annot(ctx, annot);
1271}
1272
1273static pdf_obj *ink_list_subtypes[] = {
1274 PDF_NAME(Ink),
1275 NULL,
1276};
1277
1278int
1279pdf_annot_has_ink_list(fz_context *ctx, pdf_annot *annot)
1280{
1281 return is_allowed_subtype(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1282}
1283
1284int
1285pdf_annot_ink_list_count(fz_context *ctx, pdf_annot *annot)
1286{
1287 pdf_obj *ink_list;
1288 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1289 ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
1290 return pdf_array_len(ctx, ink_list);
1291}
1292
1293int
1294pdf_annot_ink_list_stroke_count(fz_context *ctx, pdf_annot *annot, int i)
1295{
1296 pdf_obj *ink_list;
1297 pdf_obj *stroke;
1298 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1299 ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
1300 stroke = pdf_array_get(ctx, ink_list, i);
1301 return pdf_array_len(ctx, stroke) / 2;
1302}
1303
1304fz_point
1305pdf_annot_ink_list_stroke_vertex(fz_context *ctx, pdf_annot *annot, int i, int k)
1306{
1307 pdf_obj *ink_list;
1308 pdf_obj *stroke;
1309 fz_matrix page_ctm;
1310 fz_point point;
1311
1312 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1313
1314 ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
1315 stroke = pdf_array_get(ctx, ink_list, i);
1316
1317 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1318
1319 point.x = pdf_array_get_real(ctx, stroke, k * 2 + 0);
1320 point.y = pdf_array_get_real(ctx, stroke, k * 2 + 1);
1321 return fz_transform_point(point, page_ctm);
1322}
1323
1324void
1325pdf_set_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, const int *count, const fz_point *v)
1326{
1327 pdf_document *doc = annot->page->doc;
1328 fz_matrix page_ctm, inv_page_ctm;
1329 pdf_obj *ink_list, *stroke;
1330 fz_point point;
1331 int i, k;
1332
1333 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1334
1335 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1336 inv_page_ctm = fz_invert_matrix(page_ctm);
1337
1338 // TODO: update Rect (in update appearance perhaps?)
1339
1340 ink_list = pdf_new_array(ctx, doc, n);
1341 for (i = 0; i < n; ++i)
1342 {
1343 stroke = pdf_new_array(ctx, doc, count[i] * 2);
1344 for (k = 0; k < count[i]; ++k)
1345 {
1346 point = fz_transform_point(*v++, inv_page_ctm);
1347 pdf_array_push_real(ctx, stroke, point.x);
1348 pdf_array_push_real(ctx, stroke, point.y);
1349 }
1350 pdf_array_push_drop(ctx, ink_list, stroke);
1351 }
1352 pdf_dict_put_drop(ctx, annot->obj, PDF_NAME(InkList), ink_list);
1353 pdf_dirty_annot(ctx, annot);
1354}
1355
1356void
1357pdf_clear_annot_ink_list(fz_context *ctx, pdf_annot *annot)
1358{
1359 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1360 pdf_dict_del(ctx, annot->obj, PDF_NAME(InkList));
1361 pdf_dirty_annot(ctx, annot);
1362}
1363
1364void
1365pdf_add_annot_ink_list(fz_context *ctx, pdf_annot *annot, int n, fz_point p[])
1366{
1367 fz_matrix page_ctm, inv_page_ctm;
1368 pdf_obj *ink_list, *stroke;
1369 int i;
1370
1371 check_allowed_subtypes(ctx, annot, PDF_NAME(InkList), ink_list_subtypes);
1372
1373 pdf_page_transform(ctx, annot->page, NULL, &page_ctm);
1374 inv_page_ctm = fz_invert_matrix(page_ctm);
1375
1376 ink_list = pdf_dict_get(ctx, annot->obj, PDF_NAME(InkList));
1377 if (!pdf_is_array(ctx, ink_list))
1378 ink_list = pdf_dict_put_array(ctx, annot->obj, PDF_NAME(InkList), 10);
1379
1380 stroke = pdf_array_push_array(ctx, ink_list, n * 2);
1381 for (i = 0; i < n; ++i)
1382 {
1383 fz_point tp = fz_transform_point(p[i], inv_page_ctm);
1384 pdf_array_push_real(ctx, stroke, tp.x);
1385 pdf_array_push_real(ctx, stroke, tp.y);
1386 }
1387
1388 pdf_dirty_annot(ctx, annot);
1389}
1390
1391static void
1392pdf_format_date(fz_context *ctx, char *s, int n, time_t secs)
1393{
1394#ifdef _POSIX_SOURCE
1395 struct tm tmbuf, *tm = gmtime_r(&secs, &tmbuf);
1396#else
1397 struct tm *tm = gmtime(&secs);
1398#endif
1399 if (!tm)
1400 fz_strlcpy(s, "D:19700101000000Z", n);
1401 else
1402 strftime(s, n, "D:%Y%m%d%H%M%SZ", tm);
1403}
1404
1405static int64_t
1406pdf_parse_date(fz_context *ctx, const char *s)
1407{
1408 int tz_sign, tz_hour, tz_min, tz_adj;
1409 struct tm tm;
1410 time_t utc;
1411
1412 if (!s)
1413 return 0;
1414
1415 memset(&tm, 0, sizeof tm);
1416 tm.tm_mday = 1;
1417
1418 tz_sign = 1;
1419 tz_hour = 0;
1420 tz_min = 0;
1421
1422 if (s[0] == 'D' && s[1] == ':')
1423 s += 2;
1424
1425 if (!isdigit(s[0]) || !isdigit(s[1]) || !isdigit(s[2]) || !isdigit(s[3]))
1426 {
1427 fz_warn(ctx, "invalid date format (missing year)");
1428 return 0;
1429 }
1430 tm.tm_year = (s[0]-'0')*1000 + (s[1]-'0')*100 + (s[2]-'0')*10 + (s[3]-'0') - 1900;
1431 s += 4;
1432
1433 if (isdigit(s[0]) && isdigit(s[1]))
1434 {
1435 tm.tm_mon = (s[0]-'0')*10 + (s[1]-'0') - 1; /* month is 0-11 in struct tm */
1436 s += 2;
1437 if (isdigit(s[0]) && isdigit(s[1]))
1438 {
1439 tm.tm_mday = (s[0]-'0')*10 + (s[1]-'0');
1440 s += 2;
1441 if (isdigit(s[0]) && isdigit(s[1]))
1442 {
1443 tm.tm_hour = (s[0]-'0')*10 + (s[1]-'0');
1444 s += 2;
1445 if (isdigit(s[0]) && isdigit(s[1]))
1446 {
1447 tm.tm_min = (s[0]-'0')*10 + (s[1]-'0');
1448 s += 2;
1449 if (isdigit(s[0]) && isdigit(s[1]))
1450 {
1451 tm.tm_sec = (s[0]-'0')*10 + (s[1]-'0');
1452 s += 2;
1453 }
1454 }
1455 }
1456 }
1457 }
1458
1459 if (s[0] == 'Z')
1460 {
1461 s += 1;
1462 }
1463 else if ((s[0] == '-' || s[0] == '+') && isdigit(s[1]) && isdigit(s[2]))
1464 {
1465 tz_sign = (s[0] == '-') ? -1 : 1;
1466 tz_hour = (s[1]-'0')*10 + (s[2]-'0');
1467 s += 3;
1468 if (s[0] == '\'' && isdigit(s[1]) && isdigit(s[2]))
1469 {
1470 tz_min = (s[1]-'0')*10 + (s[2]-'0');
1471 s += 3;
1472 if (s[0] == '\'')
1473 s += 1;
1474 }
1475 }
1476
1477 if (s[0] != 0)
1478 fz_warn(ctx, "invalid date format (garbage at end)");
1479
1480 utc = timegm(&tm);
1481 if (utc == (time_t)-1)
1482 {
1483 fz_warn(ctx, "date overflow error");
1484 return 0;
1485 }
1486
1487 tz_adj = tz_sign * (tz_hour * 3600 + tz_min * 60);
1488 return utc - tz_adj;
1489}
1490
1491static pdf_obj *markup_subtypes[] = {
1492 PDF_NAME(Text),
1493 PDF_NAME(FreeText),
1494 PDF_NAME(Line),
1495 PDF_NAME(Square),
1496 PDF_NAME(Circle),
1497 PDF_NAME(Polygon),
1498 PDF_NAME(PolyLine),
1499 PDF_NAME(Highlight),
1500 PDF_NAME(Underline),
1501 PDF_NAME(Squiggly),
1502 PDF_NAME(StrikeOut),
1503 PDF_NAME(Redact),
1504 PDF_NAME(Stamp),
1505 PDF_NAME(Caret),
1506 PDF_NAME(Ink),
1507 PDF_NAME(FileAttachment),
1508 PDF_NAME(Sound),
1509 NULL,
1510};
1511
1512/*
1513 Get annotation's modification date in seconds since the epoch.
1514*/
1515int64_t
1516pdf_annot_modification_date(fz_context *ctx, pdf_annot *annot)
1517{
1518 pdf_obj *date = pdf_dict_get(ctx, annot->obj, PDF_NAME(M));
1519 return date ? pdf_parse_date(ctx, pdf_to_str_buf(ctx, date)) : 0;
1520}
1521
1522/*
1523 Set annotation's modification date in seconds since the epoch.
1524*/
1525void
1526pdf_set_annot_modification_date(fz_context *ctx, pdf_annot *annot, int64_t secs)
1527{
1528 char s[40];
1529
1530 check_allowed_subtypes(ctx, annot, PDF_NAME(M), markup_subtypes);
1531
1532 pdf_format_date(ctx, s, sizeof s, secs);
1533 pdf_dict_put_string(ctx, annot->obj, PDF_NAME(M), s, strlen(s));
1534 pdf_dirty_annot(ctx, annot);
1535}
1536
1537int
1538pdf_annot_has_author(fz_context *ctx, pdf_annot *annot)
1539{
1540 return is_allowed_subtype(ctx, annot, PDF_NAME(T), markup_subtypes);
1541}
1542
1543const char *
1544pdf_annot_author(fz_context *ctx, pdf_annot *annot)
1545{
1546 check_allowed_subtypes(ctx, annot, PDF_NAME(T), markup_subtypes);
1547 return pdf_dict_get_text_string(ctx, annot->obj, PDF_NAME(T));
1548}
1549
1550void
1551pdf_set_annot_author(fz_context *ctx, pdf_annot *annot, const char *author)
1552{
1553 check_allowed_subtypes(ctx, annot, PDF_NAME(T), markup_subtypes);
1554 pdf_dict_put_text_string(ctx, annot->obj, PDF_NAME(T), author);
1555 pdf_dirty_annot(ctx, annot);
1556}
1557
1558void
1559pdf_parse_default_appearance(fz_context *ctx, const char *da, const char **font, float *size, float color[3])
1560{
1561 char buf[100], *p = buf, *tok, *end;
1562 float stack[3] = { 0, 0, 0 };
1563 int top = 0;
1564
1565 *font = "Helv";
1566 *size = 12;
1567 color[0] = color[1] = color[2] = 0;
1568
1569 fz_strlcpy(buf, da, sizeof buf);
1570 while ((tok = fz_strsep(&p, " \n\r\t")) != NULL)
1571 {
1572 if (tok[0] == 0)
1573 ;
1574 else if (tok[0] == '/')
1575 {
1576 if (!strcmp(tok+1, "Cour")) *font = "Cour";
1577 if (!strcmp(tok+1, "Helv")) *font = "Helv";
1578 if (!strcmp(tok+1, "TiRo")) *font = "TiRo";
1579 if (!strcmp(tok+1, "Symb")) *font = "Symb";
1580 if (!strcmp(tok+1, "ZaDb")) *font = "ZaDb";
1581 }
1582 else if (!strcmp(tok, "Tf"))
1583 {
1584 *size = stack[0];
1585 top = 0;
1586 }
1587 else if (!strcmp(tok, "g"))
1588 {
1589 color[0] = color[1] = color[2] = stack[0];
1590 top = 0;
1591 }
1592 else if (!strcmp(tok, "rg"))
1593 {
1594 color[0] = stack[0];
1595 color[1] = stack[1];
1596 color[2] = stack[2];
1597 top=0;
1598 }
1599 else
1600 {
1601 if (top < 3)
1602 stack[top] = fz_strtof(tok, &end);
1603 if (*end == 0)
1604 ++top;
1605 else
1606 top = 0;
1607 }
1608 }
1609}
1610
1611void
1612pdf_print_default_appearance(fz_context *ctx, char *buf, int nbuf, const char *font, float size, const float color[3])
1613{
1614 if (color[0] > 0 || color[1] > 0 || color[2] > 0)
1615 fz_snprintf(buf, nbuf, "/%s %g Tf %g %g %g rg", font, size, color[0], color[1], color[2]);
1616 else
1617 fz_snprintf(buf, nbuf, "/%s %g Tf", font, size);
1618}
1619
1620void
1621pdf_annot_default_appearance(fz_context *ctx, pdf_annot *annot, const char **font, float *size, float color[3])
1622{
1623 pdf_obj *da = pdf_dict_get_inheritable(ctx, annot->obj, PDF_NAME(DA));
1624 if (!da)
1625 {
1626 pdf_obj *trailer = pdf_trailer(ctx, annot->page->doc);
1627 da = pdf_dict_getl(ctx, trailer, PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(DA), NULL);
1628 }
1629 pdf_parse_default_appearance(ctx, pdf_to_str_buf(ctx, da), font, size, color);
1630}
1631
1632void
1633pdf_set_annot_default_appearance(fz_context *ctx, pdf_annot *annot, const char *font, float size, const float color[3])
1634{
1635 char buf[100];
1636
1637 pdf_print_default_appearance(ctx, buf, sizeof buf, font, size, color);
1638
1639 pdf_dict_put_string(ctx, annot->obj, PDF_NAME(DA), buf, strlen(buf));
1640
1641 pdf_dict_del(ctx, annot->obj, PDF_NAME(DS)); /* not supported */
1642 pdf_dict_del(ctx, annot->obj, PDF_NAME(RC)); /* not supported */
1643
1644 pdf_dirty_annot(ctx, annot);
1645}
1646