@@ -10064,6 +10064,193 @@ def _addWidget(self, field_type, field_name):
1006410064 JM_add_annot_id(annot, "W")
1006510065 return Annot(annot)
1006610066
10067+ def _ensure_field_hierarchy(self, field_name):
10068+ """Ensures that the complete Form field hierarchy exists.
10069+
10070+ If the field_name shows a hierarchy like "A.B.C" establish a chain of
10071+ bidirectional links between the fields A, B, and C. The last field
10072+ ("C") in the hierarchy will point to the actual Widget on the page
10073+ (via /Kids). "A" is a root field and thus lives in the AcroForm/Fields
10074+ array. All fields in the chain will be created when missing.
10075+
10076+ Args:
10077+ field_name: (raw) field name of the Widget() object.
10078+
10079+ Returns:
10080+ The xref of the last field_name segment ("C"). To be used for the
10081+ /Parent value of the widget.
10082+ None if field_name has no segments, and hence is a root field.
10083+ """
10084+ parts = field_name.split(".")
10085+ if any(x == "" for x in parts):
10086+ raise ValueError(f"Invalid field name '{field_name}'.")
10087+
10088+ # Helper: Find name in /Kids of parent_xref
10089+ def name_in_array(array, name):
10090+ """Check if a name exists in an array of XREFs.
10091+
10092+ Returns the xref of the item if found, else False.
10093+ """
10094+ for i in range(array.pdf_array_len()):
10095+ item = array.pdf_array_get(i)
10096+ T = item.pdf_dict_get(PDF_NAME("T"))
10097+ T_name = T.pdf_to_string()
10098+ if T_name == (name, len(name)):
10099+ return item.pdf_to_num(), item
10100+ return False, None
10101+
10102+ doc = self.parent
10103+
10104+ # Access or create /AcroForm/Fields array
10105+ pdfdoc = _as_pdf_document(doc)
10106+ root = mupdf.pdf_dict_get(mupdf.pdf_trailer(pdfdoc), PDF_NAME("Root"))
10107+ acro = root.pdf_dict_get(PDF_NAME("AcroForm"))
10108+ if not acro.pdf_is_dict():
10109+ acro = root.pdf_dict_put_dict(PDF_NAME("AcroForm"), 5)
10110+ root_array = acro.pdf_dict_get(PDF_NAME("Fields"))
10111+ if not root_array.pdf_is_array():
10112+ root_array = acro.pdf_dict_put_array(PDF_NAME("Fields"), 1)
10113+
10114+ # ------------------------------------------------------------
10115+ # 3) Create missing fields in hierarchy
10116+ # ------------------------------------------------------------
10117+ parent_xref = None
10118+
10119+ for i, part in enumerate(parts):
10120+ if i == 0:
10121+ kids_array = acro.pdf_dict_get(PDF_NAME("Fields"))
10122+ parent = None
10123+ else:
10124+ parent = mupdf.pdf_load_object(pdfdoc, parent_xref)
10125+ kids_array = parent.pdf_dict_get(PDF_NAME("Kids"))
10126+ if kids_array.pdf_array_len() == 0:
10127+ kids_array = parent.pdf_dict_put_array(PDF_NAME("Kids"), 1)
10128+
10129+ item_xref, item_obj = name_in_array(kids_array, part)
10130+ if parent is None and item_obj and not item_obj.pdf_dict_get(PDF_NAME("Kids")).pdf_is_array():
10131+ new_xref = doc.get_new_xref()
10132+ doc.update_object(new_xref, f"<</T ({part})>>")
10133+ new_obj = mupdf.pdf_new_indirect(pdfdoc, new_xref, 0)
10134+ kids_array.pdf_array_push(new_obj)
10135+ item_xref = new_xref
10136+ if item_xref:
10137+ parent_xref = item_xref
10138+ continue # Field in parent Kids, move to next 'part'
10139+
10140+ # need to create a new field for this part
10141+ part_xref = doc.get_new_xref()
10142+ if parent_xref is not None:
10143+ doc.update_object(part_xref, f"<</Parent {parent_xref} 0 R /T ({part})>>")
10144+ else:
10145+ doc.update_object(part_xref, f"<</T ({part})>>")
10146+
10147+ part_obj = mupdf.pdf_new_indirect(pdfdoc, part_xref, 0)
10148+ kids_array.pdf_array_push(part_obj)
10149+ parent_xref = part_xref
10150+
10151+ return parent_xref
10152+
10153+ def _adjust_widget(self, widget):
10154+ """Adjust a widget whose field_name exhibits a field hierarchy."""
10155+ doc = self.parent
10156+ parent_xref = self._ensure_field_hierarchy(widget.field_name)
10157+ annot = widget._annot
10158+ if parent_xref is None: # just a safeguard
10159+ return annot
10160+
10161+ annot_xref = annot.xref
10162+ pdfdoc = _as_pdf_document(doc)
10163+
10164+ # remove the widget from the root Fields array
10165+ fields = (
10166+ mupdf.pdf_dict_get(mupdf.pdf_trailer(pdfdoc), PDF_NAME("Root"))
10167+ .pdf_dict_get(PDF_NAME("AcroForm"))
10168+ .pdf_dict_get(PDF_NAME("Fields"))
10169+ )
10170+ found = False
10171+ for i in range(fields.pdf_array_len()):
10172+ if fields.pdf_array_get(i).pdf_to_num() == annot_xref:
10173+ fields.pdf_array_delete(i)
10174+ found = True
10175+ break
10176+
10177+ if not found:
10178+ raise ValueError(
10179+ f"Unexpected: Widget {annot_xref} not found in root Fields array."
10180+ )
10181+
10182+ # ----------------------------------------------------------------
10183+ # Our widget will now get a /Parent. This requires moving
10184+ # multiple values of its object over to the parent object.
10185+ # Note: the name '/T' is in the parent already.
10186+ # ----------------------------------------------------------------
10187+ parent_obj = mupdf.pdf_new_indirect(pdfdoc, parent_xref, 0)
10188+ annot_obj = mupdf.pdf_load_object(pdfdoc, annot_xref)
10189+
10190+ kids_array = parent_obj.pdf_dict_get(PDF_NAME("Kids"))
10191+ if not kids_array.pdf_is_array():
10192+ kids_array = mupdf.pdf_dict_put_array(parent_obj, PDF_NAME("Kids"), 1)
10193+ kids_array.pdf_array_push(mupdf.pdf_new_indirect(pdfdoc, annot.xref, 0))
10194+
10195+ # store parent in /Parent key
10196+ annot_obj.pdf_dict_put(PDF_NAME("Parent"), parent_obj)
10197+ # store page reference
10198+ annot_obj.pdf_dict_put(PDF_NAME("P"), mupdf.pdf_new_indirect(pdfdoc, self.xref, 0))
10199+
10200+ annot_obj.pdf_dict_del(PDF_NAME("T")) # widget has no own name!
10201+ field_value = widget.field_value
10202+ if widget.field_type == PDF_WIDGET_TYPE_CHECKBOX: # noqa: F821
10203+ if field_value is True:
10204+ field_value = "Yes"
10205+ else:
10206+ field_value = "Off"
10207+ elif field_value is None:
10208+ field_value = ""
10209+ # move field value to parent
10210+ parent_obj.pdf_dict_put_string(PDF_NAME("V"), field_value, len(field_value))
10211+ annot_obj.pdf_dict_del(PDF_NAME("V"))
10212+
10213+ # move field type to parent
10214+ old = annot_obj.pdf_dict_get(PDF_NAME("FT"))
10215+ if not old.pdf_is_null():
10216+ parent_obj.pdf_dict_put(PDF_NAME("FT"), old)
10217+ annot_obj.pdf_dict_del(PDF_NAME("FT"))
10218+
10219+ # move field flags to parent
10220+ old = annot_obj.pdf_dict_get(PDF_NAME("Ff"))
10221+ if not old.pdf_is_null():
10222+ parent_obj.pdf_dict_put(PDF_NAME("Ff"), old)
10223+ annot_obj.pdf_dict_del(PDF_NAME("Ff"))
10224+
10225+ # move default appearance to parent
10226+ old = annot_obj.pdf_dict_get(PDF_NAME("DA"))
10227+ if not old.pdf_is_null():
10228+ parent_obj.pdf_dict_put(PDF_NAME("DA"), old)
10229+ annot_obj.pdf_dict_del(PDF_NAME("DA"))
10230+
10231+ # move choices list to parent
10232+ old = annot_obj.pdf_dict_get(PDF_NAME("Opt"))
10233+ if not old.pdf_is_null():
10234+ parent_obj.pdf_dict_put(PDF_NAME("Opt"), old)
10235+ annot_obj.pdf_dict_del(PDF_NAME("Opt"))
10236+
10237+ # move choices list index to parent
10238+ old = annot_obj.pdf_dict_get(PDF_NAME("I"))
10239+ if not old.pdf_is_null():
10240+ parent_obj.pdf_dict_put(PDF_NAME("I"), old)
10241+ annot_obj.pdf_dict_del(PDF_NAME("I"))
10242+
10243+ # Specials for RadioButtons
10244+ if widget.field_type == PDF_WIDGET_TYPE_RADIOBUTTON: # noqa: F821
10245+ doc.xref_set_key(annot_xref,"AS", f"/{field_value}")
10246+ apn=doc.xref_get_key(annot_xref,"AP/N")[1]
10247+ if apn and "/Yes" in apn:
10248+ apn = apn.replace("/Yes", f"/{field_value}")
10249+ doc.xref_set_key(annot_xref, "AP/N", apn)
10250+
10251+ self.parent.need_appearances(False)
10252+ return annot
10253+
1006710254 def _apply_redactions(self, text, images, graphics):
1006810255 page = self._pdf_page()
1006910256 opts = mupdf.PdfRedactOptions()
@@ -10799,8 +10986,11 @@ def add_widget(self, widget: Widget) -> Annot:
1079910986 self._annot_refs[id(annot)] = annot
1080010987 widget.parent = annot.parent
1080110988 widget._annot = annot
10989+ widget.xref = annot.xref
1080210990 widget.update()
10803- return annot
10991+ # if "." not in widget.field_name: # done if no field hierarchy
10992+ # return annot
10993+ return self._adjust_widget(widget)
1080410994
1080510995 def annot_names(self):
1080610996 '''
@@ -24909,7 +25099,7 @@ def pdfobj_string(o, prefix=''):
2490925099 ret += f'jpx_image:\n'
2491025100 elif mupdf.pdf_is_name(o):
2491125101 ret += f'name: {mupdf.pdf_to_name(o)}\n'
24912- elif o.pdf_is_null:
25102+ elif o.pdf_is_null() :
2491325103 ret += f'null\n'
2491425104 #elif o.pdf_is_number:
2491525105 # ret += f'number\n'
0 commit comments