comparison src_classic/helper-annot.i @ 1:1d09e1dec1d9 upstream

ADD: PyMuPDF v1.26.4: the original sdist. It does not yet contain MuPDF. This normally will be downloaded when building PyMuPDF.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:37:51 +0200
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 1:1d09e1dec1d9
1 %{
2 /*
3 # ------------------------------------------------------------------------
4 # Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com
5 # License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html
6 #
7 # Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a
8 # lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is
9 # maintained and developed by Artifex Software, Inc. https://artifex.com.
10 # ------------------------------------------------------------------------
11 */
12 //------------------------------------------------------------------------
13 // return pdf_obj "border style" from Python str
14 //------------------------------------------------------------------------
15 pdf_obj *JM_get_border_style(fz_context *ctx, PyObject *style)
16 {
17 pdf_obj *val = PDF_NAME(S);
18 if (!style) return val;
19 char *s = JM_StrAsChar(style);
20 JM_PyErr_Clear;
21 if (!s) return val;
22 if (!strncmp(s, "b", 1) || !strncmp(s, "B", 1)) val = PDF_NAME(B);
23 else if (!strncmp(s, "d", 1) || !strncmp(s, "D", 1)) val = PDF_NAME(D);
24 else if (!strncmp(s, "i", 1) || !strncmp(s, "I", 1)) val = PDF_NAME(I);
25 else if (!strncmp(s, "u", 1) || !strncmp(s, "U", 1)) val = PDF_NAME(U);
26 else if (!strncmp(s, "s", 1) || !strncmp(s, "S", 1)) val = PDF_NAME(S);
27 return val;
28 }
29
30 //------------------------------------------------------------------------
31 // Make /DA string of annotation
32 //------------------------------------------------------------------------
33 const char *JM_expand_fname(const char **name)
34 {
35 if (!*name) return "Helv";
36 if (!strncmp(*name, "Co", 2)) return "Cour";
37 if (!strncmp(*name, "co", 2)) return "Cour";
38 if (!strncmp(*name, "Ti", 2)) return "TiRo";
39 if (!strncmp(*name, "ti", 2)) return "TiRo";
40 if (!strncmp(*name, "Sy", 2)) return "Symb";
41 if (!strncmp(*name, "sy", 2)) return "Symb";
42 if (!strncmp(*name, "Za", 2)) return "ZaDb";
43 if (!strncmp(*name, "za", 2)) return "ZaDb";
44 return "Helv";
45 }
46
47 void JM_make_annot_DA(fz_context *ctx, pdf_annot *annot, int ncol, float col[4], const char *fontname, float fontsize)
48 {
49 fz_buffer *buf = NULL;
50 fz_try(ctx)
51 {
52 buf = fz_new_buffer(ctx, 50);
53 if (ncol <= 1)
54 fz_append_printf(ctx, buf, "%g g ", col[0]);
55 else if (ncol < 4)
56 fz_append_printf(ctx, buf, "%g %g %g rg ", col[0], col[1], col[2]);
57 else
58 fz_append_printf(ctx, buf, "%g %g %g %g k ", col[0], col[1], col[2], col[3]);
59 fz_append_printf(ctx, buf, "/%s %g Tf", JM_expand_fname(&fontname), fontsize);
60 unsigned char *da = NULL;
61 size_t len = fz_buffer_storage(ctx, buf, &da);
62 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
63 pdf_dict_put_string(ctx, annot_obj, PDF_NAME(DA), (const char *) da, len);
64 }
65 fz_always(ctx) fz_drop_buffer(ctx, buf);
66 fz_catch(ctx) fz_rethrow(ctx);
67 return;
68 }
69
70 //------------------------------------------------------------------------
71 // refreshes the link and annotation tables of a page
72 //------------------------------------------------------------------------
73 void JM_refresh_links(fz_context *ctx, pdf_page *page)
74 {
75 if (!page) return;
76 fz_try(ctx) {
77 pdf_obj *obj = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
78 if (obj)
79 {
80 pdf_document *pdf = page->doc;
81 int number = pdf_lookup_page_number(ctx, pdf, page->obj);
82 fz_rect page_mediabox;
83 fz_matrix page_ctm;
84 pdf_page_transform(ctx, page, &page_mediabox, &page_ctm);
85 page->links = pdf_load_link_annots(ctx, pdf, page, obj, number, page_ctm);
86 }
87 }
88 fz_catch(ctx) {
89 fz_rethrow(ctx);
90 }
91 return;
92 }
93
94
95 PyObject *JM_annot_border(fz_context *ctx, pdf_obj *annot_obj)
96 {
97 PyObject *res = PyDict_New();
98 PyObject *dash_py = PyList_New(0);
99 PyObject *val;
100 int i;
101 const char *style = NULL;
102 float width = -1.0f;
103 int clouds = -1;
104 pdf_obj *obj = NULL;
105
106 obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(Border));
107 if (pdf_is_array(ctx, obj)) {
108 width = pdf_to_real(ctx, pdf_array_get(ctx, obj, 2));
109 if (pdf_array_len(ctx, obj) == 4) {
110 pdf_obj *dash = pdf_array_get(ctx, obj, 3);
111 for (i = 0; i < pdf_array_len(ctx, dash); i++) {
112 val = Py_BuildValue("i", pdf_to_int(ctx, pdf_array_get(ctx, dash, i)));
113 LIST_APPEND_DROP(dash_py, val);
114 }
115 }
116 }
117
118 pdf_obj *bs_o = pdf_dict_get(ctx, annot_obj, PDF_NAME(BS));
119 if (bs_o) {
120 width = pdf_to_real(ctx, pdf_dict_get(ctx, bs_o, PDF_NAME(W)));
121 style = pdf_to_name(ctx, pdf_dict_get(ctx, bs_o, PDF_NAME(S)));
122 if (style && strcmp(style, "") == 0) {
123 style = NULL;
124 }
125 obj = pdf_dict_get(ctx, bs_o, PDF_NAME(D));
126 if (obj) {
127 for (i = 0; i < pdf_array_len(ctx, obj); i++) {
128 val = Py_BuildValue("i", pdf_to_int(ctx, pdf_array_get(ctx, obj, i)));
129 LIST_APPEND_DROP(dash_py, val);
130 }
131 }
132 }
133
134 obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(BE));
135 if (obj) {
136 clouds = pdf_to_int(ctx, pdf_dict_get(ctx, obj, PDF_NAME(I)));
137 }
138 val = PySequence_Tuple(dash_py);
139 Py_CLEAR(dash_py);
140 DICT_SETITEM_DROP(res, dictkey_width, Py_BuildValue("f", width));
141 DICT_SETITEM_DROP(res, dictkey_dashes, val);
142 DICT_SETITEM_DROP(res, dictkey_style, Py_BuildValue("s", style));
143 DICT_SETITEMSTR_DROP(res, "clouds", Py_BuildValue("i", clouds));
144 return res;
145 }
146
147 PyObject *JM_annot_set_border(fz_context *ctx, PyObject *border, pdf_document *doc, pdf_obj *annot_obj)
148 {
149 if (!PyDict_Check(border)) {
150 JM_Warning("arg must be a dict");
151 Py_RETURN_NONE; // not a dict
152 }
153 pdf_obj *obj = NULL;
154 Py_ssize_t i = 0, dashlen = 0;
155 int d;
156 double nwidth = PyFloat_AsDouble(PyDict_GetItem(border, dictkey_width)); // new width
157 PyObject *ndashes = PyDict_GetItem(border, dictkey_dashes); // new dashes
158 PyObject *nstyle = PyDict_GetItem(border, dictkey_style); // new style
159 int nclouds = (int) PyLong_AsLong(PyDict_GetItemString(border, "clouds")); // new clouds value
160
161 // get old border properties
162 PyObject *oborder = JM_annot_border(ctx, annot_obj);
163
164 // delete border-related entries
165 pdf_dict_del(ctx, annot_obj, PDF_NAME(BS));
166 pdf_dict_del(ctx, annot_obj, PDF_NAME(BE));
167 pdf_dict_del(ctx, annot_obj, PDF_NAME(Border));
168
169 // populate border items: keep old values for any omitted new ones
170 if (nwidth < 0) nwidth = PyFloat_AsDouble(PyDict_GetItem(oborder, dictkey_width)); // no new width: keep current
171 if (ndashes == Py_None) ndashes = PyDict_GetItem(oborder, dictkey_dashes); // no new dashes: keep old
172 if (nstyle == Py_None) nstyle = PyDict_GetItem(oborder, dictkey_style); // no new style: keep old
173 if (nclouds < 0) nclouds = (int) PyLong_AsLong(PyDict_GetItemString(oborder, "clouds")); // no new clouds: keep old
174
175 if (ndashes && PyTuple_Check(ndashes) && PyTuple_Size(ndashes) > 0) {
176 dashlen = PyTuple_Size(ndashes);
177 pdf_obj *darr = pdf_new_array(ctx, doc, dashlen);
178 for (i = 0; i < dashlen; i++) {
179 d = (int) PyLong_AsLong(PyTuple_GetItem(ndashes, i));
180 pdf_array_push_int(ctx, darr, (int64_t) d);
181 }
182 pdf_dict_putl_drop(ctx, annot_obj, darr, PDF_NAME(BS), PDF_NAME(D), NULL);
183 }
184
185 pdf_dict_putl_drop(ctx, annot_obj, pdf_new_real(ctx, (float) nwidth),
186 PDF_NAME(BS), PDF_NAME(W), NULL);
187
188 if (dashlen == 0) {
189 obj = JM_get_border_style(ctx, nstyle);
190 } else {
191 obj = PDF_NAME(D);
192 }
193 pdf_dict_putl_drop(ctx, annot_obj, obj, PDF_NAME(BS), PDF_NAME(S), NULL);
194
195 if (nclouds > 0) {
196 pdf_dict_put_dict(ctx, annot_obj, PDF_NAME(BE), 2);
197 pdf_obj *obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(BE));
198 pdf_dict_put(ctx, obj, PDF_NAME(S), PDF_NAME(C));
199 pdf_dict_put_int(ctx, obj, PDF_NAME(I), (int64_t) nclouds);
200 }
201
202 PyErr_Clear();
203 Py_RETURN_NONE;
204 }
205
206 PyObject *JM_annot_colors(fz_context *ctx, pdf_obj *annot_obj)
207 {
208 PyObject *res = PyDict_New();
209 PyObject *color = NULL;
210 int i, n;
211 float col;
212 pdf_obj *o = NULL;
213
214 o = pdf_dict_get(ctx, annot_obj, PDF_NAME(C));
215 if (pdf_is_array(ctx, o)) {
216 n = pdf_array_len(ctx, o);
217 color = PyTuple_New((Py_ssize_t) n);
218 for (i = 0; i < n; i++) {
219 col = pdf_to_real(ctx, pdf_array_get(ctx, o, i));
220 PyTuple_SET_ITEM(color, i, Py_BuildValue("f", col));
221 }
222 DICT_SETITEM_DROP(res, dictkey_stroke, color);
223 } else {
224 DICT_SETITEM_DROP(res, dictkey_stroke, Py_BuildValue("s", NULL));
225 }
226
227 o = pdf_dict_get(ctx, annot_obj, PDF_NAME(IC));
228 if (pdf_is_array(ctx, o)) {
229 n = pdf_array_len(ctx, o);
230 color = PyTuple_New((Py_ssize_t) n);
231 for (i = 0; i < n; i++) {
232 col = pdf_to_real(ctx, pdf_array_get(ctx, o, i));
233 PyTuple_SET_ITEM(color, i, Py_BuildValue("f", col));
234 }
235 DICT_SETITEM_DROP(res, dictkey_fill, color);
236 } else {
237 DICT_SETITEM_DROP(res, dictkey_fill, Py_BuildValue("s", NULL));
238 }
239
240 return res;
241 }
242
243
244 //------------------------------------------------------------------------
245 // Return the first annotation whose /IRT key ("In Response To") points to
246 // annot. Used to remove the response chain of a given annotation.
247 //------------------------------------------------------------------------
248 pdf_annot *JM_find_annot_irt(fz_context *ctx, pdf_annot *annot)
249 {
250 pdf_annot *irt_annot = NULL; // returning this
251 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
252 pdf_obj *o = NULL;
253 int found = 0;
254 fz_try(ctx) { // loop thru MuPDF's internal annots array
255 pdf_page *page = pdf_annot_page(ctx, annot);
256 irt_annot = pdf_first_annot(ctx, page);
257 while (irt_annot) {
258 pdf_obj *irt_annot_obj = pdf_annot_obj(ctx, irt_annot);
259 o = pdf_dict_gets(ctx, irt_annot_obj, "IRT");
260 if (o) {
261 if (!pdf_objcmp(ctx, o, annot_obj)) {
262 found = 1;
263 break;
264 }
265 }
266 irt_annot = pdf_next_annot(ctx, irt_annot);
267 }
268 }
269 fz_catch(ctx) {;}
270 if (found) return pdf_keep_annot(ctx, irt_annot);
271 return NULL;
272 }
273
274 //------------------------------------------------------------------------
275 // return the annotation names (list of /NM entries)
276 //------------------------------------------------------------------------
277 PyObject *JM_get_annot_id_list(fz_context *ctx, pdf_page *page)
278 {
279 PyObject *names = PyList_New(0);
280 pdf_obj *annot_obj = NULL;
281 pdf_obj *annots = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots));
282 pdf_obj *name = NULL;
283 if (!annots) return names;
284 fz_try(ctx) {
285 int i, n = pdf_array_len(ctx, annots);
286 for (i = 0; i < n; i++) {
287 annot_obj = pdf_array_get(ctx, annots, i);
288 name = pdf_dict_gets(ctx, annot_obj, "NM");
289 if (name) {
290 LIST_APPEND_DROP(names, Py_BuildValue("s", pdf_to_text_string(ctx, name)));
291 }
292 }
293 }
294 fz_catch(ctx) {
295 return names;
296 }
297 return names;
298 }
299
300
301 //------------------------------------------------------------------------
302 // return the xrefs and /NM ids of a page's annots, links and fields
303 //------------------------------------------------------------------------
304 PyObject *JM_get_annot_xref_list(fz_context *ctx, pdf_obj *page_obj)
305 {
306 PyObject *names = PyList_New(0);
307 pdf_obj *id, *subtype, *annots, *annot_obj;
308 int xref, type, i, n;
309 fz_try(ctx) {
310 annots = pdf_dict_get(ctx, page_obj, PDF_NAME(Annots));
311 n = pdf_array_len(ctx, annots);
312 for (i = 0; i < n; i++) {
313 annot_obj = pdf_array_get(ctx, annots, i);
314 xref = pdf_to_num(ctx, annot_obj);
315 subtype = pdf_dict_get(ctx, annot_obj, PDF_NAME(Subtype));
316 if (!subtype) {
317 continue; // subtype is required
318 }
319 type = pdf_annot_type_from_string(ctx, pdf_to_name(ctx, subtype));
320 if (type == PDF_ANNOT_UNKNOWN) {
321 continue; // only accept valid annot types
322 }
323 id = pdf_dict_gets(ctx, annot_obj, "NM");
324 LIST_APPEND_DROP(names, Py_BuildValue("iis", xref, type, pdf_to_text_string(ctx, id)));
325 }
326 }
327 fz_catch(ctx) {
328 return names;
329 }
330 return names;
331 }
332
333
334 //------------------------------------------------------------------------
335 // Add a unique /NM key to an annotation or widget.
336 // Append a number to 'stem' such that the result is a unique name.
337 //------------------------------------------------------------------------
338 static char JM_annot_id_stem[50] = "fitz";
339 void JM_add_annot_id(fz_context *ctx, pdf_annot *annot, char *stem)
340 {
341 fz_try(ctx) {
342 PyObject *names = NULL;
343 pdf_page *page = pdf_annot_page(ctx, annot);
344 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
345 names = JM_get_annot_id_list(ctx, page);
346 int i = 0;
347 PyObject *stem_id = NULL;
348 while (1) {
349 stem_id = PyUnicode_FromFormat("%s-%s%d", JM_annot_id_stem, stem, i);
350 if (!PySequence_Contains(names, stem_id)) break;
351 i += 1;
352 Py_DECREF(stem_id);
353 }
354 char *response = JM_StrAsChar(stem_id);
355 pdf_obj *name = pdf_new_string(ctx, (const char *) response, strlen(response));
356 pdf_dict_puts_drop(ctx, annot_obj, "NM", name);
357 Py_CLEAR(stem_id);
358 Py_CLEAR(names);
359 page->doc->resynth_required = 0;
360 }
361 fz_catch(ctx) {
362 fz_rethrow(ctx);
363 }
364 }
365
366 //------------------------------------------------------------------------
367 // retrieve annot by name (/NM key)
368 //------------------------------------------------------------------------
369 pdf_annot *JM_get_annot_by_name(fz_context *ctx, pdf_page *page, char *name)
370 {
371 if (!name || strlen(name) == 0) {
372 return NULL;
373 }
374 pdf_annot *annot = NULL;
375 int found = 0;
376 size_t len = 0;
377
378 fz_try(ctx) { // loop thru MuPDF's internal annots and widget arrays
379 annot = pdf_first_annot(ctx, page);
380 while (annot) {
381 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
382 const char *response = pdf_to_string(ctx, pdf_dict_gets(ctx, annot_obj, "NM"), &len);
383 if (strcmp(name, response) == 0) {
384 found = 1;
385 break;
386 }
387 annot = pdf_next_annot(ctx, annot);
388 }
389 if (!found) {
390 fz_throw(ctx, FZ_ERROR_GENERIC, "'%s' is not an annot of this page", name);
391 }
392 }
393 fz_catch(ctx) {
394 fz_rethrow(ctx);
395 }
396 return pdf_keep_annot(ctx, annot);
397 }
398
399 //------------------------------------------------------------------------
400 // retrieve annot by its xref
401 //------------------------------------------------------------------------
402 pdf_annot *JM_get_annot_by_xref(fz_context *ctx, pdf_page *page, int xref)
403 {
404 pdf_annot *annot = NULL;
405 int found = 0;
406
407 fz_try(ctx) { // loop thru MuPDF's internal annots array
408 annot = pdf_first_annot(ctx, page);
409 while (annot) {
410 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
411 if (xref == pdf_to_num(ctx, annot_obj)) {
412 found = 1;
413 break;
414 }
415 annot = pdf_next_annot(ctx, annot);
416 }
417 if (!found) {
418 fz_throw(ctx, FZ_ERROR_GENERIC, "xref %d is not an annot of this page", xref);
419 }
420 }
421 fz_catch(ctx) {
422 fz_rethrow(ctx);
423 }
424 return pdf_keep_annot(ctx, annot);
425 }
426
427 //------------------------------------------------------------------------
428 // retrieve widget by its xref
429 //------------------------------------------------------------------------
430 pdf_annot *JM_get_widget_by_xref(fz_context *ctx, pdf_page *page, int xref)
431 {
432 pdf_annot *annot = NULL;
433 int found = 0;
434
435 fz_try(ctx) { // loop thru MuPDF's internal annots array
436 annot = pdf_first_widget(ctx, page);
437 while (annot) {
438 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot);
439 if (xref == pdf_to_num(ctx, annot_obj)) {
440 found = 1;
441 break;
442 }
443 annot = pdf_next_widget(ctx, annot);
444 }
445 if (!found) {
446 fz_throw(ctx, FZ_ERROR_GENERIC, "xref %d is not a widget of this page", xref);
447 }
448 }
449 fz_catch(ctx) {
450 fz_rethrow(ctx);
451 }
452 return pdf_keep_annot(ctx, annot);
453 }
454
455 %}