Mercurial > hgrepos > Python2 > PyMuPDF
comparison src_classic/helper-fields.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 #define SETATTR(a, v) PyObject_SetAttrString(Widget, a, v) | |
| 13 #define GETATTR(a) PyObject_GetAttrString(Widget, a) | |
| 14 #define CALLATTR(m, p) PyObject_CallMethod(Widget, m, p) | |
| 15 | |
| 16 static void | |
| 17 SETATTR_DROP(PyObject *mod, const char *attr, PyObject *value) | |
| 18 { | |
| 19 if (!value) | |
| 20 PyObject_DelAttrString(mod, attr); | |
| 21 else | |
| 22 { | |
| 23 PyObject_SetAttrString(mod, attr, value); | |
| 24 Py_DECREF(value); | |
| 25 } | |
| 26 } | |
| 27 | |
| 28 //----------------------------------------------------------------------------- | |
| 29 // Functions dealing with PDF form fields (widgets) | |
| 30 //----------------------------------------------------------------------------- | |
| 31 enum | |
| 32 { | |
| 33 SigFlag_SignaturesExist = 1, | |
| 34 SigFlag_AppendOnly = 2 | |
| 35 }; | |
| 36 | |
| 37 | |
| 38 // make new PDF action object from JavaScript source | |
| 39 // Parameters are a PDF document and a Python string. | |
| 40 // Returns a PDF action object. | |
| 41 //----------------------------------------------------------------------------- | |
| 42 pdf_obj * | |
| 43 JM_new_javascript(fz_context *ctx, pdf_document *pdf, PyObject *value) | |
| 44 { | |
| 45 fz_buffer *res = NULL; | |
| 46 if (!PyObject_IsTrue(value)) // no argument given | |
| 47 return NULL; | |
| 48 | |
| 49 char *data = JM_StrAsChar(value); | |
| 50 if (!data) // not convertible to char* | |
| 51 return NULL; | |
| 52 | |
| 53 res = fz_new_buffer_from_copied_data(ctx, data, strlen(data)); | |
| 54 pdf_obj *source = pdf_add_stream(ctx, pdf, res, NULL, 0); | |
| 55 pdf_obj *newaction = pdf_add_new_dict(ctx, pdf, 4); | |
| 56 pdf_dict_put(ctx, newaction, PDF_NAME(S), pdf_new_name(ctx, "JavaScript")); | |
| 57 pdf_dict_put(ctx, newaction, PDF_NAME(JS), source); | |
| 58 fz_drop_buffer(ctx, res); | |
| 59 return pdf_keep_obj(ctx, newaction); | |
| 60 } | |
| 61 | |
| 62 | |
| 63 // JavaScript extractor | |
| 64 // Returns either the script source or None. Parameter is a PDF action | |
| 65 // dictionary, which must have keys /S and /JS. The value of /S must be | |
| 66 // '/JavaScript'. The value of /JS is returned. | |
| 67 //----------------------------------------------------------------------------- | |
| 68 PyObject * | |
| 69 JM_get_script(fz_context *ctx, pdf_obj *key) | |
| 70 { | |
| 71 pdf_obj *js = NULL; | |
| 72 fz_buffer *res = NULL; | |
| 73 PyObject *script = NULL; | |
| 74 if (!key) Py_RETURN_NONE; | |
| 75 | |
| 76 if (!strcmp(pdf_to_name(ctx, | |
| 77 pdf_dict_get(ctx, key, PDF_NAME(S))), "JavaScript")) { | |
| 78 js = pdf_dict_get(ctx, key, PDF_NAME(JS)); | |
| 79 } | |
| 80 if (!js) Py_RETURN_NONE; | |
| 81 | |
| 82 if (pdf_is_string(ctx, js)) { | |
| 83 script = JM_UnicodeFromStr(pdf_to_text_string(ctx, js)); | |
| 84 } else if (pdf_is_stream(ctx, js)) { | |
| 85 res = pdf_load_stream(ctx, js); | |
| 86 script = JM_EscapeStrFromBuffer(ctx, res); | |
| 87 fz_drop_buffer(ctx, res); | |
| 88 } else { | |
| 89 Py_RETURN_NONE; | |
| 90 } | |
| 91 if (PyObject_IsTrue(script)) { // do not return an empty script | |
| 92 return script; | |
| 93 } | |
| 94 Py_CLEAR(script); | |
| 95 Py_RETURN_NONE; | |
| 96 } | |
| 97 | |
| 98 | |
| 99 // Create a JavaScript PDF action. | |
| 100 // Usable for all object types which support PDF actions, even if the | |
| 101 // argument name suggests annotations. Up to 2 key values can be specified, so | |
| 102 // JavaScript actions can be stored for '/A' and '/AA/?' keys. | |
| 103 //----------------------------------------------------------------------------- | |
| 104 void JM_put_script(fz_context *ctx, pdf_obj *annot_obj, pdf_obj *key1, pdf_obj *key2, PyObject *value) | |
| 105 { | |
| 106 PyObject *script = NULL; | |
| 107 pdf_obj *key1_obj = pdf_dict_get(ctx, annot_obj, key1); | |
| 108 pdf_document *pdf = pdf_get_bound_document(ctx, annot_obj); // owning PDF | |
| 109 | |
| 110 // if no new script given, just delete corresponding key | |
| 111 if (!value || !PyObject_IsTrue(value)) { | |
| 112 if (!key2) { | |
| 113 pdf_dict_del(ctx, annot_obj, key1); | |
| 114 } else if (key1_obj) { | |
| 115 pdf_dict_del(ctx, key1_obj, key2); | |
| 116 } | |
| 117 return; | |
| 118 } | |
| 119 | |
| 120 // read any existing script as a PyUnicode string | |
| 121 if (!key2 || !key1_obj) { | |
| 122 script = JM_get_script(ctx, key1_obj); | |
| 123 } else { | |
| 124 script = JM_get_script(ctx, pdf_dict_get(ctx, key1_obj, key2)); | |
| 125 } | |
| 126 | |
| 127 // replace old script, if different from new one | |
| 128 if (!PyObject_RichCompareBool(value, script, Py_EQ)) { | |
| 129 pdf_obj *newaction = JM_new_javascript(ctx, pdf, value); | |
| 130 if (!key2) { | |
| 131 pdf_dict_put_drop(ctx, annot_obj, key1, newaction); | |
| 132 } else { | |
| 133 pdf_dict_putl_drop(ctx, annot_obj, newaction, key1, key2, NULL); | |
| 134 } | |
| 135 } | |
| 136 Py_XDECREF(script); | |
| 137 return; | |
| 138 } | |
| 139 | |
| 140 /* | |
| 141 // Execute a JavaScript action for annot or field. | |
| 142 //----------------------------------------------------------------------------- | |
| 143 PyObject * | |
| 144 JM_exec_script(fz_context *ctx, pdf_obj *annot_obj, pdf_obj *key1, pdf_obj *key2) | |
| 145 { | |
| 146 PyObject *script = NULL; | |
| 147 char *code = NULL; | |
| 148 fz_try(ctx) { | |
| 149 pdf_document *pdf = pdf_get_bound_document(ctx, annot_obj); | |
| 150 char buf[100]; | |
| 151 if (!key2) { | |
| 152 script = JM_get_script(ctx, key1_obj); | |
| 153 } else { | |
| 154 script = JM_get_script(ctx, pdf_dict_get(ctx, key1_obj, key2)); | |
| 155 } | |
| 156 code = JM_StrAsChar(script); | |
| 157 fz_snprintf(buf, sizeof buf, "%d/A", pdf_to_num(ctx, annot_obj)); | |
| 158 pdf_js_execute(pdf->js, buf, code); | |
| 159 } | |
| 160 fz_always(ctx) { | |
| 161 Py_XDECREF(string); | |
| 162 } | |
| 163 fz_catch(ctx) { | |
| 164 Py_RETURN_FALSE; | |
| 165 } | |
| 166 Py_RETURN_TRUE; | |
| 167 } | |
| 168 */ | |
| 169 | |
| 170 // String from widget type | |
| 171 //----------------------------------------------------------------------------- | |
| 172 char *JM_field_type_text(int wtype) | |
| 173 { | |
| 174 switch(wtype) { | |
| 175 case(PDF_WIDGET_TYPE_BUTTON): | |
| 176 return "Button"; | |
| 177 case(PDF_WIDGET_TYPE_CHECKBOX): | |
| 178 return "CheckBox"; | |
| 179 case(PDF_WIDGET_TYPE_RADIOBUTTON): | |
| 180 return "RadioButton"; | |
| 181 case(PDF_WIDGET_TYPE_TEXT): | |
| 182 return "Text"; | |
| 183 case(PDF_WIDGET_TYPE_LISTBOX): | |
| 184 return "ListBox"; | |
| 185 case(PDF_WIDGET_TYPE_COMBOBOX): | |
| 186 return "ComboBox"; | |
| 187 case(PDF_WIDGET_TYPE_SIGNATURE): | |
| 188 return "Signature"; | |
| 189 default: | |
| 190 return "unknown"; | |
| 191 } | |
| 192 } | |
| 193 | |
| 194 // Set the field type | |
| 195 //----------------------------------------------------------------------------- | |
| 196 void JM_set_field_type(fz_context *ctx, pdf_document *doc, pdf_obj *obj, int type) | |
| 197 { | |
| 198 int setbits = 0; | |
| 199 int clearbits = 0; | |
| 200 pdf_obj *typename = NULL; | |
| 201 | |
| 202 switch(type) { | |
| 203 case PDF_WIDGET_TYPE_BUTTON: | |
| 204 typename = PDF_NAME(Btn); | |
| 205 setbits = PDF_BTN_FIELD_IS_PUSHBUTTON; | |
| 206 break; | |
| 207 case PDF_WIDGET_TYPE_RADIOBUTTON: | |
| 208 typename = PDF_NAME(Btn); | |
| 209 clearbits = PDF_BTN_FIELD_IS_PUSHBUTTON; | |
| 210 setbits = PDF_BTN_FIELD_IS_RADIO; | |
| 211 break; | |
| 212 case PDF_WIDGET_TYPE_CHECKBOX: | |
| 213 typename = PDF_NAME(Btn); | |
| 214 clearbits = (PDF_BTN_FIELD_IS_PUSHBUTTON|PDF_BTN_FIELD_IS_RADIO); | |
| 215 break; | |
| 216 case PDF_WIDGET_TYPE_TEXT: | |
| 217 typename = PDF_NAME(Tx); | |
| 218 break; | |
| 219 case PDF_WIDGET_TYPE_LISTBOX: | |
| 220 typename = PDF_NAME(Ch); | |
| 221 clearbits = PDF_CH_FIELD_IS_COMBO; | |
| 222 break; | |
| 223 case PDF_WIDGET_TYPE_COMBOBOX: | |
| 224 typename = PDF_NAME(Ch); | |
| 225 setbits = PDF_CH_FIELD_IS_COMBO; | |
| 226 break; | |
| 227 case PDF_WIDGET_TYPE_SIGNATURE: | |
| 228 typename = PDF_NAME(Sig); | |
| 229 break; | |
| 230 } | |
| 231 | |
| 232 if (typename) | |
| 233 pdf_dict_put_drop(ctx, obj, PDF_NAME(FT), typename); | |
| 234 | |
| 235 if (setbits != 0 || clearbits != 0) { | |
| 236 int bits = pdf_dict_get_int(ctx, obj, PDF_NAME(Ff)); | |
| 237 bits &= ~clearbits; | |
| 238 bits |= setbits; | |
| 239 pdf_dict_put_int(ctx, obj, PDF_NAME(Ff), bits); | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 // Copied from MuPDF v1.14 | |
| 244 // Create widget. | |
| 245 // Returns a kept reference to a pdf_annot - caller must drop it. | |
| 246 //----------------------------------------------------------------------------- | |
| 247 pdf_annot *JM_create_widget(fz_context *ctx, pdf_document *doc, pdf_page *page, int type, char *fieldname) | |
| 248 { | |
| 249 pdf_obj *form = NULL; | |
| 250 int old_sigflags = pdf_to_int(ctx, pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/SigFlags")); | |
| 251 pdf_annot *annot = pdf_create_annot_raw(ctx, page, PDF_ANNOT_WIDGET); // returns a kept reference. | |
| 252 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 253 fz_try(ctx) { | |
| 254 JM_set_field_type(ctx, doc, annot_obj, type); | |
| 255 pdf_dict_put_text_string(ctx, annot_obj, PDF_NAME(T), fieldname); | |
| 256 | |
| 257 if (type == PDF_WIDGET_TYPE_SIGNATURE) { | |
| 258 int sigflags = (old_sigflags | (SigFlag_SignaturesExist|SigFlag_AppendOnly)); | |
| 259 pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), pdf_new_int(ctx, sigflags), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(SigFlags), NULL); | |
| 260 } | |
| 261 | |
| 262 /* | |
| 263 pdf_create_annot will have linked the new widget into the page's | |
| 264 annot array. We also need it linked into the document's form | |
| 265 */ | |
| 266 form = pdf_dict_getp(ctx, pdf_trailer(ctx, doc), "Root/AcroForm/Fields"); | |
| 267 if (!form) { | |
| 268 form = pdf_new_array(ctx, doc, 1); | |
| 269 pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), | |
| 270 form, | |
| 271 PDF_NAME(Root), | |
| 272 PDF_NAME(AcroForm), | |
| 273 PDF_NAME(Fields), | |
| 274 NULL); | |
| 275 } | |
| 276 | |
| 277 pdf_array_push(ctx, form, annot_obj); // Cleanup relies on this statement being last | |
| 278 } | |
| 279 fz_catch(ctx) { | |
| 280 pdf_delete_annot(ctx, page, annot); | |
| 281 | |
| 282 if (type == PDF_WIDGET_TYPE_SIGNATURE) { | |
| 283 pdf_dict_putl_drop(ctx, pdf_trailer(ctx, doc), pdf_new_int(ctx, old_sigflags), PDF_NAME(Root), PDF_NAME(AcroForm), PDF_NAME(SigFlags), NULL); | |
| 284 } | |
| 285 | |
| 286 fz_rethrow(ctx); | |
| 287 } | |
| 288 | |
| 289 return annot; | |
| 290 } | |
| 291 | |
| 292 | |
| 293 | |
| 294 // PushButton get state | |
| 295 //----------------------------------------------------------------------------- | |
| 296 PyObject *JM_pushbtn_state(fz_context *ctx, pdf_annot *annot) | |
| 297 { // pushed buttons do not reflect status changes in the PDF | |
| 298 // always reflect them as untouched | |
| 299 Py_RETURN_FALSE; | |
| 300 } | |
| 301 | |
| 302 | |
| 303 // Text field retrieve value | |
| 304 //----------------------------------------------------------------------------- | |
| 305 PyObject *JM_text_value(fz_context *ctx, pdf_annot *annot) | |
| 306 { | |
| 307 const char *text = NULL; | |
| 308 fz_var(text); | |
| 309 fz_try(ctx) { | |
| 310 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 311 text = pdf_field_value(ctx, annot_obj); | |
| 312 } | |
| 313 fz_catch(ctx) Py_RETURN_NONE; | |
| 314 return JM_UnicodeFromStr(text); | |
| 315 } | |
| 316 | |
| 317 // ListBox retrieve value | |
| 318 //----------------------------------------------------------------------------- | |
| 319 PyObject *JM_listbox_value(fz_context *ctx, pdf_annot *annot) | |
| 320 { | |
| 321 int i = 0, n = 0; | |
| 322 // may be single value or array | |
| 323 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 324 pdf_obj *optarr = pdf_dict_get(ctx, annot_obj, PDF_NAME(V)); | |
| 325 if (pdf_is_string(ctx, optarr)) // a single string | |
| 326 return PyString_FromString(pdf_to_text_string(ctx, optarr)); | |
| 327 | |
| 328 // value is an array (may have len 0) | |
| 329 n = pdf_array_len(ctx, optarr); | |
| 330 PyObject *liste = PyList_New(0); | |
| 331 | |
| 332 // extract a list of strings | |
| 333 // each entry may again be an array: take second entry then | |
| 334 for (i = 0; i < n; i++) { | |
| 335 pdf_obj *elem = pdf_array_get(ctx, optarr, i); | |
| 336 if (pdf_is_array(ctx, elem)) | |
| 337 elem = pdf_array_get(ctx, elem, 1); | |
| 338 LIST_APPEND_DROP(liste, JM_UnicodeFromStr(pdf_to_text_string(ctx, elem))); | |
| 339 } | |
| 340 return liste; | |
| 341 } | |
| 342 | |
| 343 // ComboBox retrieve value | |
| 344 //----------------------------------------------------------------------------- | |
| 345 PyObject *JM_combobox_value(fz_context *ctx, pdf_annot *annot) | |
| 346 { // combobox treated like listbox | |
| 347 return JM_listbox_value(ctx, annot); | |
| 348 } | |
| 349 | |
| 350 // Signature field retrieve value | |
| 351 PyObject *JM_signature_value(fz_context *ctx, pdf_annot *annot) | |
| 352 { // signatures are currently not supported | |
| 353 Py_RETURN_NONE; | |
| 354 } | |
| 355 | |
| 356 // retrieve ListBox / ComboBox choice values | |
| 357 //----------------------------------------------------------------------------- | |
| 358 PyObject *JM_choice_options(fz_context *ctx, pdf_annot *annot) | |
| 359 { // return list of choices for list or combo boxes | |
| 360 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 361 PyObject *val; | |
| 362 int n = pdf_choice_widget_options(ctx, annot, 0, NULL); | |
| 363 if (n == 0) Py_RETURN_NONE; // wrong widget type | |
| 364 | |
| 365 pdf_obj *optarr = pdf_dict_get(ctx, annot_obj, PDF_NAME(Opt)); | |
| 366 int i, m; | |
| 367 PyObject *liste = PyList_New(0); | |
| 368 | |
| 369 for (i = 0; i < n; i++) { | |
| 370 m = pdf_array_len(ctx, pdf_array_get(ctx, optarr, i)); | |
| 371 if (m == 2) { | |
| 372 val = Py_BuildValue("ss", | |
| 373 pdf_to_text_string(ctx, pdf_array_get(ctx, pdf_array_get(ctx, optarr, i), 0)), | |
| 374 pdf_to_text_string(ctx, pdf_array_get(ctx, pdf_array_get(ctx, optarr, i), 1))); | |
| 375 LIST_APPEND_DROP(liste, val); | |
| 376 } else { | |
| 377 val = JM_UnicodeFromStr(pdf_to_text_string(ctx, pdf_array_get(ctx, optarr, i))); | |
| 378 LIST_APPEND_DROP(liste, val); | |
| 379 } | |
| 380 } | |
| 381 return liste; | |
| 382 } | |
| 383 | |
| 384 | |
| 385 // set ListBox / ComboBox values | |
| 386 //----------------------------------------------------------------------------- | |
| 387 void JM_set_choice_options(fz_context *ctx, pdf_annot *annot, PyObject *liste) | |
| 388 { | |
| 389 if (!liste) return; | |
| 390 if (!PySequence_Check(liste)) return; | |
| 391 Py_ssize_t i, n = PySequence_Size(liste); | |
| 392 if (n < 1) return; | |
| 393 PyObject *tuple = PySequence_Tuple(liste); | |
| 394 PyObject *val = NULL, *val1 = NULL, *val2 = NULL; | |
| 395 pdf_obj *optarrsub = NULL, *optarr = NULL, *annot_obj = NULL; | |
| 396 pdf_document *pdf = NULL; | |
| 397 const char *opt = NULL, *opt1 = NULL, *opt2 = NULL; | |
| 398 fz_try(ctx) { | |
| 399 annot_obj = pdf_annot_obj(ctx, annot); | |
| 400 pdf = pdf_get_bound_document(ctx, annot_obj); | |
| 401 optarr = pdf_new_array(ctx, pdf, (int) n); | |
| 402 for (i = 0; i < n; i++) { | |
| 403 val = PyTuple_GET_ITEM(tuple, i); | |
| 404 opt = PyUnicode_AsUTF8(val); | |
| 405 if (opt) { | |
| 406 pdf_array_push_text_string(ctx, optarr, opt); | |
| 407 } else { | |
| 408 if (!PySequence_Check(val) || PySequence_Size(val) != 2) { | |
| 409 RAISEPY(ctx, "bad choice field list", PyExc_ValueError); | |
| 410 } | |
| 411 val1 = PySequence_GetItem(val, 0); | |
| 412 opt1 = PyUnicode_AsUTF8(val1); | |
| 413 if (!opt1) { | |
| 414 RAISEPY(ctx, "bad choice field list", PyExc_ValueError); | |
| 415 } | |
| 416 val2 = PySequence_GetItem(val, 1); | |
| 417 opt2 = PyUnicode_AsUTF8(val2); | |
| 418 if (!opt2) { | |
| 419 RAISEPY(ctx, "bad choice field list", PyExc_ValueError); | |
| 420 }; | |
| 421 Py_CLEAR(val1); | |
| 422 Py_CLEAR(val2); | |
| 423 optarrsub = pdf_array_push_array(ctx, optarr, 2); | |
| 424 pdf_array_push_text_string(ctx, optarrsub, opt1); | |
| 425 pdf_array_push_text_string(ctx, optarrsub, opt2); | |
| 426 } | |
| 427 } | |
| 428 pdf_dict_put_drop(ctx, annot_obj, PDF_NAME(Opt), optarr); | |
| 429 } | |
| 430 fz_always(ctx) { | |
| 431 Py_CLEAR(tuple); | |
| 432 Py_CLEAR(val1); | |
| 433 Py_CLEAR(val2); | |
| 434 PyErr_Clear(); | |
| 435 } | |
| 436 fz_catch(ctx) { | |
| 437 fz_rethrow(ctx); | |
| 438 } | |
| 439 return; | |
| 440 } | |
| 441 | |
| 442 | |
| 443 //----------------------------------------------------------------------------- | |
| 444 // Populate a Python Widget object with the values from a PDF form field. | |
| 445 // Called by "Page.firstWidget" and "Widget.next". | |
| 446 //----------------------------------------------------------------------------- | |
| 447 void JM_get_widget_properties(fz_context *ctx, pdf_annot *annot, PyObject *Widget) | |
| 448 { | |
| 449 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 450 pdf_page *page = pdf_annot_page(ctx, annot); | |
| 451 pdf_document *pdf = page->doc; | |
| 452 pdf_annot *tw = annot; | |
| 453 pdf_obj *obj = NULL; | |
| 454 Py_ssize_t i = 0, n = 0; | |
| 455 fz_try(ctx) { | |
| 456 int field_type = pdf_widget_type(ctx, tw); | |
| 457 SETATTR_DROP(Widget, "field_type", Py_BuildValue("i", field_type)); | |
| 458 if (field_type == PDF_WIDGET_TYPE_SIGNATURE) { | |
| 459 if (pdf_signature_is_signed(ctx, pdf, annot_obj)) { | |
| 460 SETATTR("is_signed", Py_True); | |
| 461 } else { | |
| 462 SETATTR("is_signed", Py_False); | |
| 463 } | |
| 464 } else { | |
| 465 SETATTR("is_signed", Py_None); | |
| 466 } | |
| 467 SETATTR_DROP(Widget, "border_style", | |
| 468 JM_UnicodeFromStr(pdf_field_border_style(ctx, annot_obj))); | |
| 469 SETATTR_DROP(Widget, "field_type_string", | |
| 470 JM_UnicodeFromStr(JM_field_type_text(field_type))); | |
| 471 | |
| 472 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR <= 22 | |
| 473 char *field_name = pdf_field_name(ctx, annot_obj); | |
| 474 #else | |
| 475 char *field_name = pdf_load_field_name(ctx, annot_obj); | |
| 476 #endif | |
| 477 SETATTR_DROP(Widget, "field_name", JM_UnicodeFromStr(field_name)); | |
| 478 JM_Free(field_name); | |
| 479 | |
| 480 const char *label = NULL; | |
| 481 obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(TU)); | |
| 482 if (obj) label = pdf_to_text_string(ctx, obj); | |
| 483 SETATTR_DROP(Widget, "field_label", JM_UnicodeFromStr(label)); | |
| 484 | |
| 485 const char *fvalue = NULL; | |
| 486 if (field_type == PDF_WIDGET_TYPE_RADIOBUTTON) { | |
| 487 obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(Parent)); // owning RB group | |
| 488 if (obj) { | |
| 489 SETATTR_DROP(Widget, "rb_parent", Py_BuildValue("i", pdf_to_num(ctx, obj))); | |
| 490 } | |
| 491 obj = pdf_dict_get(ctx, annot_obj, PDF_NAME(AS)); | |
| 492 if (obj) { | |
| 493 fvalue = pdf_to_name(ctx, obj); | |
| 494 } | |
| 495 } | |
| 496 if (!fvalue) { | |
| 497 fvalue = pdf_field_value(ctx, annot_obj); | |
| 498 } | |
| 499 SETATTR_DROP(Widget, "field_value", JM_UnicodeFromStr(fvalue)); | |
| 500 | |
| 501 SETATTR_DROP(Widget, "field_display", | |
| 502 Py_BuildValue("i", pdf_field_display(ctx, annot_obj))); | |
| 503 | |
| 504 float border_width = pdf_to_real(ctx, pdf_dict_getl(ctx, annot_obj, | |
| 505 PDF_NAME(BS), PDF_NAME(W), NULL)); | |
| 506 if (border_width == 0) border_width = 1; | |
| 507 SETATTR_DROP(Widget, "border_width", | |
| 508 Py_BuildValue("f", border_width)); | |
| 509 | |
| 510 obj = pdf_dict_getl(ctx, annot_obj, | |
| 511 PDF_NAME(BS), PDF_NAME(D), NULL); | |
| 512 if (pdf_is_array(ctx, obj)) { | |
| 513 n = (Py_ssize_t) pdf_array_len(ctx, obj); | |
| 514 PyObject *d = PyList_New(n); | |
| 515 for (i = 0; i < n; i++) { | |
| 516 PyList_SET_ITEM(d, i, Py_BuildValue("i", pdf_to_int(ctx, | |
| 517 pdf_array_get(ctx, obj, (int) i)))); | |
| 518 } | |
| 519 SETATTR_DROP(Widget, "border_dashes", d); | |
| 520 } | |
| 521 | |
| 522 SETATTR_DROP(Widget, "text_maxlen", | |
| 523 Py_BuildValue("i", pdf_text_widget_max_len(ctx, tw))); | |
| 524 | |
| 525 SETATTR_DROP(Widget, "text_format", | |
| 526 Py_BuildValue("i", pdf_text_widget_format(ctx, tw))); | |
| 527 | |
| 528 obj = pdf_dict_getl(ctx, annot_obj, PDF_NAME(MK), PDF_NAME(BG), NULL); | |
| 529 if (pdf_is_array(ctx, obj)) { | |
| 530 n = (Py_ssize_t) pdf_array_len(ctx, obj); | |
| 531 PyObject *col = PyList_New(n); | |
| 532 for (i = 0; i < n; i++) { | |
| 533 PyList_SET_ITEM(col, i, Py_BuildValue("f", | |
| 534 pdf_to_real(ctx, pdf_array_get(ctx, obj, (int) i)))); | |
| 535 } | |
| 536 SETATTR_DROP(Widget, "fill_color", col); | |
| 537 } | |
| 538 | |
| 539 obj = pdf_dict_getl(ctx, annot_obj, PDF_NAME(MK), PDF_NAME(BC), NULL); | |
| 540 if (pdf_is_array(ctx, obj)) { | |
| 541 n = (Py_ssize_t) pdf_array_len(ctx, obj); | |
| 542 PyObject *col = PyList_New(n); | |
| 543 for (i = 0; i < n; i++) { | |
| 544 PyList_SET_ITEM(col, i, Py_BuildValue("f", | |
| 545 pdf_to_real(ctx, pdf_array_get(ctx, obj, (int) i)))); | |
| 546 } | |
| 547 SETATTR_DROP(Widget, "border_color", col); | |
| 548 } | |
| 549 | |
| 550 SETATTR_DROP(Widget, "choice_values", JM_choice_options(ctx, annot)); | |
| 551 | |
| 552 const char *da = pdf_to_text_string(ctx, pdf_dict_get_inheritable(ctx, | |
| 553 annot_obj, PDF_NAME(DA))); | |
| 554 SETATTR_DROP(Widget, "_text_da", JM_UnicodeFromStr(da)); | |
| 555 | |
| 556 obj = pdf_dict_getl(ctx, annot_obj, PDF_NAME(MK), PDF_NAME(CA), NULL); | |
| 557 if (obj) { | |
| 558 SETATTR_DROP(Widget, "button_caption", | |
| 559 JM_UnicodeFromStr((char *)pdf_to_text_string(ctx, obj))); | |
| 560 } | |
| 561 | |
| 562 SETATTR_DROP(Widget, "field_flags", | |
| 563 Py_BuildValue("i", pdf_field_flags(ctx, annot_obj))); | |
| 564 | |
| 565 // call Py method to reconstruct text color, font name, size | |
| 566 PyObject *call = CALLATTR("_parse_da", NULL); | |
| 567 Py_XDECREF(call); | |
| 568 | |
| 569 // extract JavaScript action texts | |
| 570 SETATTR_DROP(Widget, "script", | |
| 571 JM_get_script(ctx, pdf_dict_get(ctx, annot_obj, PDF_NAME(A)))); | |
| 572 | |
| 573 SETATTR_DROP(Widget, "script_stroke", | |
| 574 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(K), NULL))); | |
| 575 | |
| 576 SETATTR_DROP(Widget, "script_format", | |
| 577 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(F), NULL))); | |
| 578 | |
| 579 SETATTR_DROP(Widget, "script_change", | |
| 580 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(V), NULL))); | |
| 581 | |
| 582 SETATTR_DROP(Widget, "script_calc", | |
| 583 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(C), NULL))); | |
| 584 | |
| 585 SETATTR_DROP(Widget, "script_blur", | |
| 586 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), pdf_new_name(ctx, "Bl"), NULL))); | |
| 587 | |
| 588 SETATTR_DROP(Widget, "script_focus", | |
| 589 JM_get_script(ctx, pdf_dict_getl(ctx, annot_obj, PDF_NAME(AA), pdf_new_name(ctx, "Fo"), NULL))); | |
| 590 } | |
| 591 fz_always(ctx) PyErr_Clear(); | |
| 592 fz_catch(ctx) fz_rethrow(ctx); | |
| 593 return; | |
| 594 } | |
| 595 | |
| 596 | |
| 597 //----------------------------------------------------------------------------- | |
| 598 // Update the PDF form field with the properties from a Python Widget object. | |
| 599 // Called by "Page.addWidget" and "Annot.updateWidget". | |
| 600 //----------------------------------------------------------------------------- | |
| 601 void JM_set_widget_properties(fz_context *ctx, pdf_annot *annot, PyObject *Widget) | |
| 602 { | |
| 603 pdf_page *page = pdf_annot_page(ctx, annot); | |
| 604 pdf_obj *annot_obj = pdf_annot_obj(ctx, annot); | |
| 605 pdf_document *pdf = page->doc; | |
| 606 fz_rect rect; | |
| 607 pdf_obj *fill_col = NULL, *border_col = NULL; | |
| 608 pdf_obj *dashes = NULL; | |
| 609 Py_ssize_t i, n = 0; | |
| 610 int d; | |
| 611 PyObject *value = GETATTR("field_type"); | |
| 612 int field_type = (int) PyInt_AsLong(value); | |
| 613 Py_DECREF(value); | |
| 614 | |
| 615 // rectangle -------------------------------------------------------------- | |
| 616 value = GETATTR("rect"); | |
| 617 rect = JM_rect_from_py(value); | |
| 618 Py_XDECREF(value); | |
| 619 fz_matrix rot_mat = JM_rotate_page_matrix(ctx, page); | |
| 620 rect = fz_transform_rect(rect, rot_mat); | |
| 621 pdf_set_annot_rect(ctx, annot, rect); | |
| 622 | |
| 623 // fill color ------------------------------------------------------------- | |
| 624 value = GETATTR("fill_color"); | |
| 625 if (value && PySequence_Check(value)) { | |
| 626 n = PySequence_Size(value); | |
| 627 fill_col = pdf_new_array(ctx, pdf, n); | |
| 628 double col = 0; | |
| 629 for (i = 0; i < n; i++) { | |
| 630 JM_FLOAT_ITEM(value, i, &col); | |
| 631 pdf_array_push_real(ctx, fill_col, col); | |
| 632 } | |
| 633 pdf_field_set_fill_color(ctx, annot_obj, fill_col); | |
| 634 pdf_drop_obj(ctx, fill_col); | |
| 635 } | |
| 636 Py_XDECREF(value); | |
| 637 | |
| 638 // dashes ----------------------------------------------------------------- | |
| 639 value = GETATTR("border_dashes"); | |
| 640 if (value && PySequence_Check(value)) { | |
| 641 n = PySequence_Size(value); | |
| 642 dashes = pdf_new_array(ctx, pdf, n); | |
| 643 for (i = 0; i < n; i++) { | |
| 644 pdf_array_push_int(ctx, dashes, | |
| 645 (int64_t) PyInt_AsLong(PySequence_ITEM(value, i))); | |
| 646 } | |
| 647 pdf_dict_putl_drop(ctx, annot_obj, dashes, | |
| 648 PDF_NAME(BS), | |
| 649 PDF_NAME(D), | |
| 650 NULL); | |
| 651 } | |
| 652 Py_XDECREF(value); | |
| 653 | |
| 654 // border color ----------------------------------------------------------- | |
| 655 value = GETATTR("border_color"); | |
| 656 if (value && PySequence_Check(value)) { | |
| 657 n = PySequence_Size(value); | |
| 658 border_col = pdf_new_array(ctx, pdf, n); | |
| 659 double col = 0; | |
| 660 for (i = 0; i < n; i++) { | |
| 661 JM_FLOAT_ITEM(value, i, &col); | |
| 662 pdf_array_push_real(ctx, border_col, col); | |
| 663 } | |
| 664 pdf_dict_putl_drop(ctx, annot_obj, border_col, | |
| 665 PDF_NAME(MK), | |
| 666 PDF_NAME(BC), | |
| 667 NULL); | |
| 668 } | |
| 669 Py_XDECREF(value); | |
| 670 | |
| 671 // entry ignored - may be used later | |
| 672 /* | |
| 673 int text_format = (int) PyInt_AsLong(GETATTR("text_format")); | |
| 674 */ | |
| 675 | |
| 676 // field label ----------------------------------------------------------- | |
| 677 value = GETATTR("field_label"); | |
| 678 if (value != Py_None) { | |
| 679 char *label = JM_StrAsChar(value); | |
| 680 pdf_dict_put_text_string(ctx, annot_obj, PDF_NAME(TU), label); | |
| 681 } | |
| 682 Py_XDECREF(value); | |
| 683 | |
| 684 // field name ------------------------------------------------------------- | |
| 685 value = GETATTR("field_name"); | |
| 686 if (value != Py_None) { | |
| 687 char *name = JM_StrAsChar(value); | |
| 688 #if FZ_VERSION_MAJOR == 1 && FZ_VERSION_MINOR <= 22 | |
| 689 char *old_name = pdf_field_name(ctx, annot_obj); | |
| 690 #else | |
| 691 char *old_name = pdf_load_field_name(ctx, annot_obj); | |
| 692 #endif | |
| 693 if (strcmp(name, old_name) != 0) { | |
| 694 pdf_dict_put_text_string(ctx, annot_obj, PDF_NAME(T), name); | |
| 695 } | |
| 696 JM_Free(old_name); | |
| 697 } | |
| 698 Py_XDECREF(value); | |
| 699 | |
| 700 // max text len ----------------------------------------------------------- | |
| 701 if (field_type == PDF_WIDGET_TYPE_TEXT) | |
| 702 { | |
| 703 value = GETATTR("text_maxlen"); | |
| 704 int text_maxlen = (int) PyInt_AsLong(value); | |
| 705 if (text_maxlen) { | |
| 706 pdf_dict_put_int(ctx, annot_obj, PDF_NAME(MaxLen), text_maxlen); | |
| 707 } | |
| 708 Py_XDECREF(value); | |
| 709 } | |
| 710 value = GETATTR("field_display"); | |
| 711 d = (int) PyInt_AsLong(value); | |
| 712 Py_XDECREF(value); | |
| 713 pdf_field_set_display(ctx, annot_obj, d); | |
| 714 | |
| 715 // choice values ---------------------------------------------------------- | |
| 716 if (field_type == PDF_WIDGET_TYPE_LISTBOX || | |
| 717 field_type == PDF_WIDGET_TYPE_COMBOBOX) { | |
| 718 value = GETATTR("choice_values"); | |
| 719 JM_set_choice_options(ctx, annot, value); | |
| 720 Py_XDECREF(value); | |
| 721 } | |
| 722 | |
| 723 // border style ----------------------------------------------------------- | |
| 724 value = GETATTR("border_style"); | |
| 725 pdf_obj *val = JM_get_border_style(ctx, value); | |
| 726 Py_XDECREF(value); | |
| 727 pdf_dict_putl_drop(ctx, annot_obj, val, | |
| 728 PDF_NAME(BS), | |
| 729 PDF_NAME(S), | |
| 730 NULL); | |
| 731 | |
| 732 // border width ----------------------------------------------------------- | |
| 733 value = GETATTR("border_width"); | |
| 734 float border_width = (float) PyFloat_AsDouble(value); | |
| 735 Py_XDECREF(value); | |
| 736 pdf_dict_putl_drop(ctx, annot_obj, pdf_new_real(ctx, border_width), | |
| 737 PDF_NAME(BS), | |
| 738 PDF_NAME(W), | |
| 739 NULL); | |
| 740 | |
| 741 // /DA string ------------------------------------------------------------- | |
| 742 value = GETATTR("_text_da"); | |
| 743 char *da = JM_StrAsChar(value); | |
| 744 Py_XDECREF(value); | |
| 745 pdf_dict_put_text_string(ctx, annot_obj, PDF_NAME(DA), da); | |
| 746 pdf_dict_del(ctx, annot_obj, PDF_NAME(DS)); /* not supported by MuPDF */ | |
| 747 pdf_dict_del(ctx, annot_obj, PDF_NAME(RC)); /* not supported by MuPDF */ | |
| 748 | |
| 749 // field flags ------------------------------------------------------------ | |
| 750 value = GETATTR("field_flags"); | |
| 751 int field_flags = (int) PyInt_AsLong(value); | |
| 752 Py_XDECREF(value); | |
| 753 if (!PyErr_Occurred()) { | |
| 754 if (field_type == PDF_WIDGET_TYPE_COMBOBOX) { | |
| 755 field_flags |= PDF_CH_FIELD_IS_COMBO; | |
| 756 } else if (field_type == PDF_WIDGET_TYPE_RADIOBUTTON) { | |
| 757 field_flags |= PDF_BTN_FIELD_IS_RADIO; | |
| 758 } else if (field_type == PDF_WIDGET_TYPE_BUTTON) { | |
| 759 field_flags |= PDF_BTN_FIELD_IS_PUSHBUTTON; | |
| 760 } | |
| 761 pdf_dict_put_int(ctx, annot_obj, PDF_NAME(Ff), field_flags); | |
| 762 } | |
| 763 | |
| 764 // button caption --------------------------------------------------------- | |
| 765 value = GETATTR("button_caption"); | |
| 766 char *ca = JM_StrAsChar(value); | |
| 767 if (ca) { | |
| 768 pdf_field_set_button_caption(ctx, annot_obj, ca); | |
| 769 } | |
| 770 Py_XDECREF(value); | |
| 771 | |
| 772 // script (/A) ------------------------------------------------------- | |
| 773 value = GETATTR("script"); | |
| 774 JM_put_script(ctx, annot_obj, PDF_NAME(A), NULL, value); | |
| 775 Py_CLEAR(value); | |
| 776 | |
| 777 // script (/AA/K) ------------------------------------------------------- | |
| 778 value = GETATTR("script_stroke"); | |
| 779 JM_put_script(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(K), value); | |
| 780 Py_CLEAR(value); | |
| 781 | |
| 782 // script (/AA/F) ------------------------------------------------------- | |
| 783 value = GETATTR("script_format"); | |
| 784 JM_put_script(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(F), value); | |
| 785 Py_CLEAR(value); | |
| 786 | |
| 787 // script (/AA/V) ------------------------------------------------------- | |
| 788 value = GETATTR("script_change"); | |
| 789 JM_put_script(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(V), value); | |
| 790 Py_CLEAR(value); | |
| 791 | |
| 792 // script (/AA/C) ------------------------------------------------------- | |
| 793 value = GETATTR("script_calc"); | |
| 794 JM_put_script(ctx, annot_obj, PDF_NAME(AA), PDF_NAME(C), value); | |
| 795 Py_CLEAR(value); | |
| 796 | |
| 797 // script (/AA/Bl) ------------------------------------------------------ | |
| 798 value = GETATTR("script_blur"); | |
| 799 JM_put_script(ctx, annot_obj, PDF_NAME(AA), pdf_new_name(ctx, "Bl"), value); | |
| 800 Py_CLEAR(value); | |
| 801 | |
| 802 // script (/AA/Fo) ------------------------------------------------------ | |
| 803 value = GETATTR("script_focus"); | |
| 804 JM_put_script(ctx, annot_obj, PDF_NAME(AA), pdf_new_name(ctx, "Fo"), value); | |
| 805 Py_CLEAR(value); | |
| 806 | |
| 807 // field value ------------------------------------------------------------ | |
| 808 value = GETATTR("field_value"); // field value | |
| 809 char *text = JM_StrAsChar(value); // convert to text (may fail!) | |
| 810 | |
| 811 switch(field_type) | |
| 812 { | |
| 813 case PDF_WIDGET_TYPE_RADIOBUTTON: | |
| 814 if (PyObject_RichCompareBool(value, Py_False, Py_EQ)) { | |
| 815 pdf_set_field_value(ctx, pdf, annot_obj, "Off", 1); | |
| 816 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(AS), "Off"); | |
| 817 } else { | |
| 818 pdf_obj *onstate = pdf_button_field_on_state(ctx, annot_obj); | |
| 819 if (onstate) { | |
| 820 const char *on = pdf_to_name(ctx, onstate); | |
| 821 pdf_set_field_value(ctx, pdf, annot_obj, on, 1); | |
| 822 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(AS), on); | |
| 823 } else if (text) { | |
| 824 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(AS), text); | |
| 825 } | |
| 826 } | |
| 827 break; | |
| 828 | |
| 829 case PDF_WIDGET_TYPE_CHECKBOX: // will always be "Yes" or "Off" | |
| 830 if (PyObject_RichCompareBool(value, Py_True, Py_EQ) || text && strcmp(text, "Yes")==0) { | |
| 831 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(AS), "Yes"); | |
| 832 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(V), "Yes"); | |
| 833 } else { | |
| 834 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(AS), "Off"); | |
| 835 pdf_dict_put_name(gctx, annot_obj, PDF_NAME(V), "Off"); | |
| 836 } | |
| 837 break; | |
| 838 | |
| 839 default: | |
| 840 if (text) { | |
| 841 pdf_set_field_value(ctx, pdf, annot_obj, (const char *)text, 1); | |
| 842 if (field_type == PDF_WIDGET_TYPE_COMBOBOX || field_type == PDF_WIDGET_TYPE_LISTBOX) { | |
| 843 pdf_dict_del(ctx, annot_obj, PDF_NAME(I)); | |
| 844 } | |
| 845 } | |
| 846 } | |
| 847 Py_CLEAR(value); | |
| 848 PyErr_Clear(); | |
| 849 pdf_dirty_annot(ctx, annot); | |
| 850 pdf_set_annot_hot(ctx, annot, 1); | |
| 851 pdf_set_annot_active(ctx, annot, 1); | |
| 852 pdf_update_annot(ctx, annot); | |
| 853 } | |
| 854 #undef SETATTR | |
| 855 #undef GETATTR | |
| 856 #undef CALLATTR | |
| 857 %} | |
| 858 | |
| 859 %pythoncode %{ | |
| 860 #------------------------------------------------------------------------------ | |
| 861 # Class describing a PDF form field ("widget") | |
| 862 #------------------------------------------------------------------------------ | |
| 863 class Widget(object): | |
| 864 def __init__(self): | |
| 865 self.thisown = True | |
| 866 self.border_color = None | |
| 867 self.border_style = "S" | |
| 868 self.border_width = 0 | |
| 869 self.border_dashes = None | |
| 870 self.choice_values = None # choice fields only | |
| 871 self.rb_parent = None # radio buttons only: xref of owning parent | |
| 872 | |
| 873 self.field_name = None # field name | |
| 874 self.field_label = None # field label | |
| 875 self.field_value = None | |
| 876 self.field_flags = 0 | |
| 877 self.field_display = 0 | |
| 878 self.field_type = 0 # valid range 1 through 7 | |
| 879 self.field_type_string = None # field type as string | |
| 880 | |
| 881 self.fill_color = None | |
| 882 self.button_caption = None # button caption | |
| 883 self.is_signed = None # True / False if signature | |
| 884 self.text_color = (0, 0, 0) | |
| 885 self.text_font = "Helv" | |
| 886 self.text_fontsize = 0 | |
| 887 self.text_maxlen = 0 # text fields only | |
| 888 self.text_format = 0 # text fields only | |
| 889 self._text_da = "" # /DA = default apparance | |
| 890 | |
| 891 self.script = None # JavaScript (/A) | |
| 892 self.script_stroke = None # JavaScript (/AA/K) | |
| 893 self.script_format = None # JavaScript (/AA/F) | |
| 894 self.script_change = None # JavaScript (/AA/V) | |
| 895 self.script_calc = None # JavaScript (/AA/C) | |
| 896 self.script_blur = None # JavaScript (/AA/Bl) | |
| 897 self.script_focus = None # JavaScript (/AA/Fo) | |
| 898 | |
| 899 self.rect = None # annot value | |
| 900 self.xref = 0 # annot value | |
| 901 | |
| 902 | |
| 903 def _validate(self): | |
| 904 """Validate the class entries. | |
| 905 """ | |
| 906 if (self.rect.is_infinite | |
| 907 or self.rect.is_empty | |
| 908 ): | |
| 909 raise ValueError("bad rect") | |
| 910 | |
| 911 if not self.field_name: | |
| 912 raise ValueError("field name missing") | |
| 913 | |
| 914 if self.field_label == "Unnamed": | |
| 915 self.field_label = None | |
| 916 CheckColor(self.border_color) | |
| 917 CheckColor(self.fill_color) | |
| 918 if not self.text_color: | |
| 919 self.text_color = (0, 0, 0) | |
| 920 CheckColor(self.text_color) | |
| 921 | |
| 922 if not self.border_width: | |
| 923 self.border_width = 0 | |
| 924 | |
| 925 if not self.text_fontsize: | |
| 926 self.text_fontsize = 0 | |
| 927 | |
| 928 self.border_style = self.border_style.upper()[0:1] | |
| 929 | |
| 930 # standardize content of JavaScript entries | |
| 931 btn_type = self.field_type in ( | |
| 932 PDF_WIDGET_TYPE_BUTTON, | |
| 933 PDF_WIDGET_TYPE_CHECKBOX, | |
| 934 PDF_WIDGET_TYPE_RADIOBUTTON | |
| 935 ) | |
| 936 if not self.script: | |
| 937 self.script = None | |
| 938 elif type(self.script) is not str: | |
| 939 raise ValueError("script content must be a string") | |
| 940 | |
| 941 # buttons cannot have the following script actions | |
| 942 if btn_type or not self.script_calc: | |
| 943 self.script_calc = None | |
| 944 elif type(self.script_calc) is not str: | |
| 945 raise ValueError("script_calc content must be a string") | |
| 946 | |
| 947 if btn_type or not self.script_change: | |
| 948 self.script_change = None | |
| 949 elif type(self.script_change) is not str: | |
| 950 raise ValueError("script_change content must be a string") | |
| 951 | |
| 952 if btn_type or not self.script_format: | |
| 953 self.script_format = None | |
| 954 elif type(self.script_format) is not str: | |
| 955 raise ValueError("script_format content must be a string") | |
| 956 | |
| 957 if btn_type or not self.script_stroke: | |
| 958 self.script_stroke = None | |
| 959 elif type(self.script_stroke) is not str: | |
| 960 raise ValueError("script_stroke content must be a string") | |
| 961 | |
| 962 if btn_type or not self.script_blur: | |
| 963 self.script_blur = None | |
| 964 elif type(self.script_blur) is not str: | |
| 965 raise ValueError("script_blur content must be a string") | |
| 966 | |
| 967 if btn_type or not self.script_focus: | |
| 968 self.script_focus = None | |
| 969 elif type(self.script_focus) is not str: | |
| 970 raise ValueError("script_focus content must be a string") | |
| 971 | |
| 972 self._checker() # any field_type specific checks | |
| 973 | |
| 974 | |
| 975 def _adjust_font(self): | |
| 976 """Ensure text_font is correctly spelled if empty or from our list. | |
| 977 | |
| 978 Otherwise assume the font is in an existing field. | |
| 979 """ | |
| 980 if not self.text_font: | |
| 981 self.text_font = "Helv" | |
| 982 return | |
| 983 doc = self.parent.parent | |
| 984 for f in doc.FormFonts + ["Cour", "TiRo", "Helv", "ZaDb"]: | |
| 985 if self.text_font.lower() == f.lower(): | |
| 986 self.text_font = f | |
| 987 return | |
| 988 self.text_font = "Helv" | |
| 989 return | |
| 990 | |
| 991 | |
| 992 def _parse_da(self): | |
| 993 """Extract font name, size and color from default appearance string (/DA object). | |
| 994 | |
| 995 Equivalent to 'pdf_parse_default_appearance' function in MuPDF's 'pdf-annot.c'. | |
| 996 """ | |
| 997 if not self._text_da: | |
| 998 return | |
| 999 font = "Helv" | |
| 1000 fsize = 0 | |
| 1001 col = (0, 0, 0) | |
| 1002 dat = self._text_da.split() # split on any whitespace | |
| 1003 for i, item in enumerate(dat): | |
| 1004 if item == "Tf": | |
| 1005 font = dat[i - 2][1:] | |
| 1006 fsize = float(dat[i - 1]) | |
| 1007 dat[i] = dat[i-1] = dat[i-2] = "" | |
| 1008 continue | |
| 1009 if item == "g": # unicolor text | |
| 1010 col = [(float(dat[i - 1]))] | |
| 1011 dat[i] = dat[i-1] = "" | |
| 1012 continue | |
| 1013 if item == "rg": # RGB colored text | |
| 1014 col = [float(f) for f in dat[i - 3:i]] | |
| 1015 dat[i] = dat[i-1] = dat[i-2] = dat[i-3] = "" | |
| 1016 continue | |
| 1017 self.text_font = font | |
| 1018 self.text_fontsize = fsize | |
| 1019 self.text_color = col | |
| 1020 self._text_da = "" | |
| 1021 return | |
| 1022 | |
| 1023 | |
| 1024 def _checker(self): | |
| 1025 """Any widget type checks. | |
| 1026 """ | |
| 1027 if self.field_type not in range(1, 8): | |
| 1028 raise ValueError("bad field type") | |
| 1029 | |
| 1030 | |
| 1031 # if setting a radio button to ON, first set Off all buttons | |
| 1032 # in the group - this is not done by MuPDF: | |
| 1033 if self.field_type == PDF_WIDGET_TYPE_RADIOBUTTON and self.field_value not in (False, "Off") and hasattr(self, "parent"): | |
| 1034 # so we are about setting this button to ON/True | |
| 1035 # check other buttons in same group and set them to 'Off' | |
| 1036 doc = self.parent.parent | |
| 1037 kids_type, kids_value = doc.xref_get_key(self.xref, "Parent/Kids") | |
| 1038 if kids_type == "array": | |
| 1039 xrefs = tuple(map(int, kids_value[1:-1].replace("0 R","").split())) | |
| 1040 for xref in xrefs: | |
| 1041 if xref != self.xref: | |
| 1042 doc.xref_set_key(xref, "AS", "/Off") | |
| 1043 # the calling method will now set the intended button to on and | |
| 1044 # will find everything prepared for correct functioning. | |
| 1045 | |
| 1046 | |
| 1047 def update(self): | |
| 1048 """Reflect Python object in the PDF. | |
| 1049 """ | |
| 1050 doc = self.parent.parent | |
| 1051 self._validate() | |
| 1052 | |
| 1053 self._adjust_font() # ensure valid text_font name | |
| 1054 | |
| 1055 # now create the /DA string | |
| 1056 self._text_da = "" | |
| 1057 if len(self.text_color) == 3: | |
| 1058 fmt = "{:g} {:g} {:g} rg /{f:s} {s:g} Tf" + self._text_da | |
| 1059 elif len(self.text_color) == 1: | |
| 1060 fmt = "{:g} g /{f:s} {s:g} Tf" + self._text_da | |
| 1061 elif len(self.text_color) == 4: | |
| 1062 fmt = "{:g} {:g} {:g} {:g} k /{f:s} {s:g} Tf" + self._text_da | |
| 1063 self._text_da = fmt.format(*self.text_color, f=self.text_font, | |
| 1064 s=self.text_fontsize) | |
| 1065 | |
| 1066 # if widget has a '/AA/C' script, make sure it is in the '/CO' | |
| 1067 # array of the '/AcroForm' dictionary. | |
| 1068 if self.script_calc: # there is a "calculation" script: | |
| 1069 # make sure we are in the /CO array | |
| 1070 util_ensure_widget_calc(self._annot) | |
| 1071 | |
| 1072 # finally update the widget | |
| 1073 TOOLS._save_widget(self._annot, self) | |
| 1074 self._text_da = "" | |
| 1075 | |
| 1076 | |
| 1077 def button_states(self): | |
| 1078 """Return the on/off state names for button widgets. | |
| 1079 | |
| 1080 A button may have 'normal' or 'pressed down' appearances. While the 'Off' | |
| 1081 state is usually called like this, the 'On' state is often given a name | |
| 1082 relating to the functional context. | |
| 1083 """ | |
| 1084 if self.field_type not in (2, 5): | |
| 1085 return None # no button type | |
| 1086 if hasattr(self, "parent"): # field already exists on page | |
| 1087 doc = self.parent.parent | |
| 1088 else: | |
| 1089 return None | |
| 1090 xref = self.xref | |
| 1091 states = {"normal": None, "down": None} | |
| 1092 APN = doc.xref_get_key(xref, "AP/N") | |
| 1093 if APN[0] == "dict": | |
| 1094 nstates = [] | |
| 1095 APN = APN[1][2:-2] | |
| 1096 apnt = APN.split("/")[1:] | |
| 1097 for x in apnt: | |
| 1098 nstates.append(x.split()[0]) | |
| 1099 states["normal"] = nstates | |
| 1100 if APN[0] == "xref": | |
| 1101 nstates = [] | |
| 1102 nxref = int(APN[1].split(" ")[0]) | |
| 1103 APN = doc.xref_object(nxref) | |
| 1104 apnt = APN.split("/")[1:] | |
| 1105 for x in apnt: | |
| 1106 nstates.append(x.split()[0]) | |
| 1107 states["normal"] = nstates | |
| 1108 APD = doc.xref_get_key(xref, "AP/D") | |
| 1109 if APD[0] == "dict": | |
| 1110 dstates = [] | |
| 1111 APD = APD[1][2:-2] | |
| 1112 apdt = APD.split("/")[1:] | |
| 1113 for x in apdt: | |
| 1114 dstates.append(x.split()[0]) | |
| 1115 states["down"] = dstates | |
| 1116 if APD[0] == "xref": | |
| 1117 dstates = [] | |
| 1118 dxref = int(APD[1].split(" ")[0]) | |
| 1119 APD = doc.xref_object(dxref) | |
| 1120 apdt = APD.split("/")[1:] | |
| 1121 for x in apdt: | |
| 1122 dstates.append(x.split()[0]) | |
| 1123 states["down"] = dstates | |
| 1124 return states | |
| 1125 | |
| 1126 def on_state(self): | |
| 1127 """Return the "On" value for button widgets. | |
| 1128 | |
| 1129 This is useful for radio buttons mainly. Checkboxes will always return | |
| 1130 "Yes". Radio buttons will return the string that is unequal to "Off" | |
| 1131 as returned by method button_states(). | |
| 1132 If the radio button is new / being created, it does not yet have an | |
| 1133 "On" value. In this case, a warning is shown and True is returned. | |
| 1134 """ | |
| 1135 if self.field_type not in (2, 5): | |
| 1136 return None # no checkbox or radio button | |
| 1137 if self.field_type == 2: | |
| 1138 return "Yes" | |
| 1139 bstate = self.button_states() | |
| 1140 if bstate==None: | |
| 1141 bstate = {} | |
| 1142 for k in bstate.keys(): | |
| 1143 for v in bstate[k]: | |
| 1144 if v != "Off": | |
| 1145 return v | |
| 1146 print("warning: radio button has no 'On' value.") | |
| 1147 return True | |
| 1148 | |
| 1149 def reset(self): | |
| 1150 """Reset the field value to its default. | |
| 1151 """ | |
| 1152 TOOLS._reset_widget(self._annot) | |
| 1153 | |
| 1154 def __repr__(self): | |
| 1155 return "'%s' widget on %s" % (self.field_type_string, str(self.parent)) | |
| 1156 | |
| 1157 def __del__(self): | |
| 1158 if hasattr(self, "_annot"): | |
| 1159 del self._annot | |
| 1160 | |
| 1161 @property | |
| 1162 def next(self): | |
| 1163 return self._annot.next | |
| 1164 %} |
