Mercurial > hgrepos > Python2 > PyMuPDF
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src_classic/helper-annot.i Mon Sep 15 11:37:51 2025 +0200 @@ -0,0 +1,455 @@ +%{ +/* +# ------------------------------------------------------------------------ +# Copyright 2020-2022, Harald Lieder, mailto:harald.lieder@outlook.com +# License: GNU AFFERO GPL 3.0, https://www.gnu.org/licenses/agpl-3.0.html +# +# Part of "PyMuPDF", a Python binding for "MuPDF" (http://mupdf.com), a +# lightweight PDF, XPS, and E-book viewer, renderer and toolkit which is +# maintained and developed by Artifex Software, Inc. https://artifex.com. +# ------------------------------------------------------------------------ +*/ +//------------------------------------------------------------------------ +// return pdf_obj "border style" from Python str +//------------------------------------------------------------------------ +pdf_obj *JM_get_border_style(fz_context *ctx, PyObject *style) +{ + pdf_obj *val = PDF_NAME(S); + if (!style) return val; + char *s = JM_StrAsChar(style); + JM_PyErr_Clear; + if (!s) return val; + if (!strncmp(s, "b", 1) || !strncmp(s, "B", 1)) val = PDF_NAME(B); + else if (!strncmp(s, "d", 1) || !strncmp(s, "D", 1)) val = PDF_NAME(D); + else if (!strncmp(s, "i", 1) || !strncmp(s, "I", 1)) val = PDF_NAME(I); + else if (!strncmp(s, "u", 1) || !strncmp(s, "U", 1)) val = PDF_NAME(U); + else if (!strncmp(s, "s", 1) || !strncmp(s, "S", 1)) val = PDF_NAME(S); + return val; +} + +//------------------------------------------------------------------------ +// Make /DA string of annotation +//------------------------------------------------------------------------ +const char *JM_expand_fname(const char **name) +{ + if (!*name) return "Helv"; + if (!strncmp(*name, "Co", 2)) return "Cour"; + if (!strncmp(*name, "co", 2)) return "Cour"; + if (!strncmp(*name, "Ti", 2)) return "TiRo"; + if (!strncmp(*name, "ti", 2)) return "TiRo"; + if (!strncmp(*name, "Sy", 2)) return "Symb"; + if (!strncmp(*name, "sy", 2)) return "Symb"; + if (!strncmp(*name, "Za", 2)) return "ZaDb"; + if (!strncmp(*name, "za", 2)) return "ZaDb"; + return "Helv"; +} + +void JM_make_annot_DA(fz_context *ctx, pdf_annot *annot, int ncol, float col[4], const char *fontname, float fontsize) +{ + fz_buffer *buf = NULL; + fz_try(ctx) + { + buf = fz_new_buffer(ctx, 50); + if (ncol <= 1) + fz_append_printf(ctx, buf, "%g g ", col[0]); + else if (ncol < 4) + fz_append_printf(ctx, buf, "%g %g %g rg ", col[0], col[1], col[2]); + else + fz_append_printf(ctx, buf, "%g %g %g %g k ", col[0], col[1], col[2], col[3]); + fz_append_printf(ctx, buf, "/%s %g Tf", JM_expand_fname(&fontname), fontsize); + unsigned char *da = NULL; + size_t len = fz_buffer_storage(ctx, buf, &da); + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + pdf_dict_put_string(ctx, annot_obj, PDF_NAME(DA), (const char *) da, len); + } + fz_always(ctx) fz_drop_buffer(ctx, buf); + fz_catch(ctx) fz_rethrow(ctx); + return; +} + +//------------------------------------------------------------------------ +// refreshes the link and annotation tables of a page +//------------------------------------------------------------------------ +void JM_refresh_links(fz_context *ctx, pdf_page *page) +{ + if (!page) return; + fz_try(ctx) { + pdf_obj *obj = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots)); + if (obj) + { + pdf_document *pdf = page->doc; + int number = pdf_lookup_page_number(ctx, pdf, page->obj); + fz_rect page_mediabox; + fz_matrix page_ctm; + pdf_page_transform(ctx, page, &page_mediabox, &page_ctm); + page->links = pdf_load_link_annots(ctx, pdf, page, obj, number, page_ctm); + } + } + fz_catch(ctx) { + fz_rethrow(ctx); + } + return; +} + + +PyObject *JM_annot_border(fz_context *ctx, pdf_obj *annot_obj) +{ + PyObject *res = PyDict_New(); + PyObject *dash_py = PyList_New(0); + PyObject *val; + int i; + const char *style = NULL; + float width = -1.0f; + int clouds = -1; + pdf_obj *obj = NULL; + + obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(Border)); + if (pdf_is_array(ctx, obj)) { + width = pdf_to_real(ctx, pdf_array_get(ctx, obj, 2)); + if (pdf_array_len(ctx, obj) == 4) { + pdf_obj *dash = pdf_array_get(ctx, obj, 3); + for (i = 0; i < pdf_array_len(ctx, dash); i++) { + val = Py_BuildValue("i", pdf_to_int(ctx, pdf_array_get(ctx, dash, i))); + LIST_APPEND_DROP(dash_py, val); + } + } + } + + pdf_obj *bs_o = pdf_dict_get(ctx, annot_obj, PDF_NAME(BS)); + if (bs_o) { + width = pdf_to_real(ctx, pdf_dict_get(ctx, bs_o, PDF_NAME(W))); + style = pdf_to_name(ctx, pdf_dict_get(ctx, bs_o, PDF_NAME(S))); + if (style && strcmp(style, "") == 0) { + style = NULL; + } + obj = pdf_dict_get(ctx, bs_o, PDF_NAME(D)); + if (obj) { + for (i = 0; i < pdf_array_len(ctx, obj); i++) { + val = Py_BuildValue("i", pdf_to_int(ctx, pdf_array_get(ctx, obj, i))); + LIST_APPEND_DROP(dash_py, val); + } + } + } + + obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(BE)); + if (obj) { + clouds = pdf_to_int(ctx, pdf_dict_get(ctx, obj, PDF_NAME(I))); + } + val = PySequence_Tuple(dash_py); + Py_CLEAR(dash_py); + DICT_SETITEM_DROP(res, dictkey_width, Py_BuildValue("f", width)); + DICT_SETITEM_DROP(res, dictkey_dashes, val); + DICT_SETITEM_DROP(res, dictkey_style, Py_BuildValue("s", style)); + DICT_SETITEMSTR_DROP(res, "clouds", Py_BuildValue("i", clouds)); + return res; +} + +PyObject *JM_annot_set_border(fz_context *ctx, PyObject *border, pdf_document *doc, pdf_obj *annot_obj) +{ + if (!PyDict_Check(border)) { + JM_Warning("arg must be a dict"); + Py_RETURN_NONE; // not a dict + } + pdf_obj *obj = NULL; + Py_ssize_t i = 0, dashlen = 0; + int d; + double nwidth = PyFloat_AsDouble(PyDict_GetItem(border, dictkey_width)); // new width + PyObject *ndashes = PyDict_GetItem(border, dictkey_dashes); // new dashes + PyObject *nstyle = PyDict_GetItem(border, dictkey_style); // new style + int nclouds = (int) PyLong_AsLong(PyDict_GetItemString(border, "clouds")); // new clouds value + + // get old border properties + PyObject *oborder = JM_annot_border(ctx, annot_obj); + + // delete border-related entries + pdf_dict_del(ctx, annot_obj, PDF_NAME(BS)); + pdf_dict_del(ctx, annot_obj, PDF_NAME(BE)); + pdf_dict_del(ctx, annot_obj, PDF_NAME(Border)); + + // populate border items: keep old values for any omitted new ones + if (nwidth < 0) nwidth = PyFloat_AsDouble(PyDict_GetItem(oborder, dictkey_width)); // no new width: keep current + if (ndashes == Py_None) ndashes = PyDict_GetItem(oborder, dictkey_dashes); // no new dashes: keep old + if (nstyle == Py_None) nstyle = PyDict_GetItem(oborder, dictkey_style); // no new style: keep old + if (nclouds < 0) nclouds = (int) PyLong_AsLong(PyDict_GetItemString(oborder, "clouds")); // no new clouds: keep old + + if (ndashes && PyTuple_Check(ndashes) && PyTuple_Size(ndashes) > 0) { + dashlen = PyTuple_Size(ndashes); + pdf_obj *darr = pdf_new_array(ctx, doc, dashlen); + for (i = 0; i < dashlen; i++) { + d = (int) PyLong_AsLong(PyTuple_GetItem(ndashes, i)); + pdf_array_push_int(ctx, darr, (int64_t) d); + } + pdf_dict_putl_drop(ctx, annot_obj, darr, PDF_NAME(BS), PDF_NAME(D), NULL); + } + + pdf_dict_putl_drop(ctx, annot_obj, pdf_new_real(ctx, (float) nwidth), + PDF_NAME(BS), PDF_NAME(W), NULL); + + if (dashlen == 0) { + obj = JM_get_border_style(ctx, nstyle); + } else { + obj = PDF_NAME(D); + } + pdf_dict_putl_drop(ctx, annot_obj, obj, PDF_NAME(BS), PDF_NAME(S), NULL); + + if (nclouds > 0) { + pdf_dict_put_dict(ctx, annot_obj, PDF_NAME(BE), 2); + pdf_obj *obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(BE)); + pdf_dict_put(ctx, obj, PDF_NAME(S), PDF_NAME(C)); + pdf_dict_put_int(ctx, obj, PDF_NAME(I), (int64_t) nclouds); + } + + PyErr_Clear(); + Py_RETURN_NONE; +} + +PyObject *JM_annot_colors(fz_context *ctx, pdf_obj *annot_obj) +{ + PyObject *res = PyDict_New(); + PyObject *color = NULL; + int i, n; + float col; + pdf_obj *o = NULL; + + o = pdf_dict_get(ctx, annot_obj, PDF_NAME(C)); + if (pdf_is_array(ctx, o)) { + n = pdf_array_len(ctx, o); + color = PyTuple_New((Py_ssize_t) n); + for (i = 0; i < n; i++) { + col = pdf_to_real(ctx, pdf_array_get(ctx, o, i)); + PyTuple_SET_ITEM(color, i, Py_BuildValue("f", col)); + } + DICT_SETITEM_DROP(res, dictkey_stroke, color); + } else { + DICT_SETITEM_DROP(res, dictkey_stroke, Py_BuildValue("s", NULL)); + } + + o = pdf_dict_get(ctx, annot_obj, PDF_NAME(IC)); + if (pdf_is_array(ctx, o)) { + n = pdf_array_len(ctx, o); + color = PyTuple_New((Py_ssize_t) n); + for (i = 0; i < n; i++) { + col = pdf_to_real(ctx, pdf_array_get(ctx, o, i)); + PyTuple_SET_ITEM(color, i, Py_BuildValue("f", col)); + } + DICT_SETITEM_DROP(res, dictkey_fill, color); + } else { + DICT_SETITEM_DROP(res, dictkey_fill, Py_BuildValue("s", NULL)); + } + + return res; +} + + +//------------------------------------------------------------------------ +// Return the first annotation whose /IRT key ("In Response To") points to +// annot. Used to remove the response chain of a given annotation. +//------------------------------------------------------------------------ +pdf_annot *JM_find_annot_irt(fz_context *ctx, pdf_annot *annot) +{ + pdf_annot *irt_annot = NULL; // returning this + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + pdf_obj *o = NULL; + int found = 0; + fz_try(ctx) { // loop thru MuPDF's internal annots array + pdf_page *page = pdf_annot_page(ctx, annot); + irt_annot = pdf_first_annot(ctx, page); + while (irt_annot) { + pdf_obj *irt_annot_obj = pdf_annot_obj(ctx, irt_annot); + o = pdf_dict_gets(ctx, irt_annot_obj, "IRT"); + if (o) { + if (!pdf_objcmp(ctx, o, annot_obj)) { + found = 1; + break; + } + } + irt_annot = pdf_next_annot(ctx, irt_annot); + } + } + fz_catch(ctx) {;} + if (found) return pdf_keep_annot(ctx, irt_annot); + return NULL; +} + +//------------------------------------------------------------------------ +// return the annotation names (list of /NM entries) +//------------------------------------------------------------------------ +PyObject *JM_get_annot_id_list(fz_context *ctx, pdf_page *page) +{ + PyObject *names = PyList_New(0); + pdf_obj *annot_obj = NULL; + pdf_obj *annots = pdf_dict_get(ctx, page->obj, PDF_NAME(Annots)); + pdf_obj *name = NULL; + if (!annots) return names; + fz_try(ctx) { + int i, n = pdf_array_len(ctx, annots); + for (i = 0; i < n; i++) { + annot_obj = pdf_array_get(ctx, annots, i); + name = pdf_dict_gets(ctx, annot_obj, "NM"); + if (name) { + LIST_APPEND_DROP(names, Py_BuildValue("s", pdf_to_text_string(ctx, name))); + } + } + } + fz_catch(ctx) { + return names; + } + return names; +} + + +//------------------------------------------------------------------------ +// return the xrefs and /NM ids of a page's annots, links and fields +//------------------------------------------------------------------------ +PyObject *JM_get_annot_xref_list(fz_context *ctx, pdf_obj *page_obj) +{ + PyObject *names = PyList_New(0); + pdf_obj *id, *subtype, *annots, *annot_obj; + int xref, type, i, n; + fz_try(ctx) { + annots = pdf_dict_get(ctx, page_obj, PDF_NAME(Annots)); + n = pdf_array_len(ctx, annots); + for (i = 0; i < n; i++) { + annot_obj = pdf_array_get(ctx, annots, i); + xref = pdf_to_num(ctx, annot_obj); + subtype = pdf_dict_get(ctx, annot_obj, PDF_NAME(Subtype)); + if (!subtype) { + continue; // subtype is required + } + type = pdf_annot_type_from_string(ctx, pdf_to_name(ctx, subtype)); + if (type == PDF_ANNOT_UNKNOWN) { + continue; // only accept valid annot types + } + id = pdf_dict_gets(ctx, annot_obj, "NM"); + LIST_APPEND_DROP(names, Py_BuildValue("iis", xref, type, pdf_to_text_string(ctx, id))); + } + } + fz_catch(ctx) { + return names; + } + return names; +} + + +//------------------------------------------------------------------------ +// Add a unique /NM key to an annotation or widget. +// Append a number to 'stem' such that the result is a unique name. +//------------------------------------------------------------------------ +static char JM_annot_id_stem[50] = "fitz"; +void JM_add_annot_id(fz_context *ctx, pdf_annot *annot, char *stem) +{ + fz_try(ctx) { + PyObject *names = NULL; + pdf_page *page = pdf_annot_page(ctx, annot); + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + names = JM_get_annot_id_list(ctx, page); + int i = 0; + PyObject *stem_id = NULL; + while (1) { + stem_id = PyUnicode_FromFormat("%s-%s%d", JM_annot_id_stem, stem, i); + if (!PySequence_Contains(names, stem_id)) break; + i += 1; + Py_DECREF(stem_id); + } + char *response = JM_StrAsChar(stem_id); + pdf_obj *name = pdf_new_string(ctx, (const char *) response, strlen(response)); + pdf_dict_puts_drop(ctx, annot_obj, "NM", name); + Py_CLEAR(stem_id); + Py_CLEAR(names); + page->doc->resynth_required = 0; + } + fz_catch(ctx) { + fz_rethrow(ctx); + } +} + +//------------------------------------------------------------------------ +// retrieve annot by name (/NM key) +//------------------------------------------------------------------------ +pdf_annot *JM_get_annot_by_name(fz_context *ctx, pdf_page *page, char *name) +{ + if (!name || strlen(name) == 0) { + return NULL; + } + pdf_annot *annot = NULL; + int found = 0; + size_t len = 0; + + fz_try(ctx) { // loop thru MuPDF's internal annots and widget arrays + annot = pdf_first_annot(ctx, page); + while (annot) { + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + const char *response = pdf_to_string(ctx, pdf_dict_gets(ctx, annot_obj, "NM"), &len); + if (strcmp(name, response) == 0) { + found = 1; + break; + } + annot = pdf_next_annot(ctx, annot); + } + if (!found) { + fz_throw(ctx, FZ_ERROR_GENERIC, "'%s' is not an annot of this page", name); + } + } + fz_catch(ctx) { + fz_rethrow(ctx); + } + return pdf_keep_annot(ctx, annot); +} + +//------------------------------------------------------------------------ +// retrieve annot by its xref +//------------------------------------------------------------------------ +pdf_annot *JM_get_annot_by_xref(fz_context *ctx, pdf_page *page, int xref) +{ + pdf_annot *annot = NULL; + int found = 0; + + fz_try(ctx) { // loop thru MuPDF's internal annots array + annot = pdf_first_annot(ctx, page); + while (annot) { + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + if (xref == pdf_to_num(ctx, annot_obj)) { + found = 1; + break; + } + annot = pdf_next_annot(ctx, annot); + } + if (!found) { + fz_throw(ctx, FZ_ERROR_GENERIC, "xref %d is not an annot of this page", xref); + } + } + fz_catch(ctx) { + fz_rethrow(ctx); + } + return pdf_keep_annot(ctx, annot); +} + +//------------------------------------------------------------------------ +// retrieve widget by its xref +//------------------------------------------------------------------------ +pdf_annot *JM_get_widget_by_xref(fz_context *ctx, pdf_page *page, int xref) +{ + pdf_annot *annot = NULL; + int found = 0; + + fz_try(ctx) { // loop thru MuPDF's internal annots array + annot = pdf_first_widget(ctx, page); + while (annot) { + pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); + if (xref == pdf_to_num(ctx, annot_obj)) { + found = 1; + break; + } + annot = pdf_next_widget(ctx, annot); + } + if (!found) { + fz_throw(ctx, FZ_ERROR_GENERIC, "xref %d is not a widget of this page", xref); + } + } + fz_catch(ctx) { + fz_rethrow(ctx); + } + return pdf_keep_annot(ctx, annot); +} + +%}
