@@ -25,10 +25,11 @@ def _appearance_stream_data(
2525 self,
2626 text: str = "",
2727 selection: Optional[list[str]] = None,
28- default_appearance: str = "",
2928 font_glyph_byte_map: Optional[dict[str, bytes]] = None,
3029 rect: Union[RectangleObject, tuple[float, float, float, float]] = (0.0, 0.0, 0.0, 0.0),
31- font_size: float = 0,
30+ font_name: str = "/Helv",
31+ font_size: float = 0.0,
32+ font_color: str = "0 g",
3233 multiline: bool = False
3334 ) -> bytes:
3435 font_glyph_byte_map = font_glyph_byte_map or {}
@@ -41,11 +42,10 @@ def _appearance_stream_data(
4142 font_size = DEFAULT_FONT_SIZE_IN_MULTILINE
4243 else:
4344 font_size = rect.height - 2
44- default_appearance = re.sub(r"0.0 Tf", str(font_size) + r" Tf", default_appearance)
4545
4646 # Set the vertical offset
4747 y_offset = rect.height - 1 - font_size
48-
48+ default_appearance = f"{font_name} {font_size} Tf {font_color}"
4949 ap_stream = f"q\n/Tx BMC \nq\n1 1 {rect.width - 1} {rect.height - 1} re\nW\nBT\n{default_appearance}\n".encode()
5050 for line_number, line in enumerate(text.replace("\n", "\r").split("\r")):
5151 if selection and line in selection:
@@ -73,24 +73,23 @@ def __init__(
7373 self,
7474 text: str = "",
7575 selection: Optional[list[str]] = None,
76- default_appearance: str = "",
7776 font_glyph_byte_map: Optional[dict[str, bytes]] = None,
7877 rect: Union[RectangleObject, tuple[float, float, float, float]] = (0.0, 0.0, 0.0, 0.0),
79- font_size: float = 0,
78+ font_name: str = "/Helv",
79+ font_size: float = 0.0,
80+ font_color: str = "0 g",
8081 multiline: bool = False
8182 ) -> None:
8283 font_glyph_byte_map = font_glyph_byte_map or {}
83- if isinstance(rect, tuple):
84- rect = RectangleObject(rect)
8584
8685 ap_stream_data = self._appearance_stream_data(
87- text, selection, default_appearance, font_glyph_byte_map, rect, font_size, multiline
86+ text, selection, font_glyph_byte_map, rect, font_name, font_size, font_color , multiline
8887 )
8988
9089 super().__init__()
9190 self[NameObject("/Type")] = NameObject("/XObject")
9291 self[NameObject("/Subtype")] = NameObject("/Form")
93- self[NameObject("/BBox")] = rect
92+ self[NameObject("/BBox")] = RectangleObject( rect)
9493 self.set_data(ByteStringObject(ap_stream_data))
9594 self[NameObject("/Length")] = NumberObject(len(ap_stream_data))
9695
@@ -113,33 +112,30 @@ def from_text_annotation(
113112 AnnotationDictionaryAttributes.DA,
114113 acro_form.get(AnnotationDictionaryAttributes.DA, None),
115114 )
116- if default_appearance is None :
115+ if not default_appearance :
117116 # Create a default appearance if none was found in the annotation
118117 default_appearance = TextStringObject("/Helv 0 Tf 0 g")
119118 else:
120119 default_appearance = default_appearance.get_object()
121120
122- # Embed user-provided font name and font size in the default appearance, also
123- # taking into account whether the field flags indicate a multiline field.
124- # Uses the variable font_properties as an intermediate.
125- font_properties = default_appearance.replace("\n", " ").replace("\r", " ").split(" ")
126- font_properties = [x for x in font_properties if x != ""]
127- # Override default appearance font name with user provided font name, if given.
121+ # Derive font name, size and color from the default appearance. Also set
122+ # user-provided font name and font size in the default appearance, if given.
123+ # For a font name, this presumes that we can find an associated font resource
124+ # dictionary. Uses the variable font_properties as an intermediate.
125+ # As per the PDF spec:
126+ # "At a minimum, the string [that is, default_appearance] shall include a Tf (text
127+ # font) operator along with its two operands, font and size" (p. 519 of Version 2.0).
128+ font_properties = [prop for prop in re.split(r"\s", default_appearance) if prop]
129+ font_name = font_properties.pop(font_properties.index("Tf") - 2)
130+ font_size = float(font_properties.pop(font_properties.index("Tf") - 1))
131+ font_properties.remove("Tf")
132+ font_color = " ".join(font_properties)
133+ # Determine the font name to use, prioritizing the user's input
128134 if user_font_name:
129135 font_name = user_font_name
130- font_properties[font_properties.index("Tf") - 2] = user_font_name
131- else:
132- # Indirectly this just reads font_name from default appearance.
133- font_name = font_properties[font_properties.index("Tf") - 2]
134- # Override default appearance font size with user provided font size, if given.
135- font_size = (
136- user_font_size
137- if user_font_size >= 0
138- else float(font_properties[font_properties.index("Tf") - 1])
139- )
140- font_properties[font_properties.index("Tf") - 1] = str(font_size)
141- # Reconstruct default appearance with user info and flags information
142- default_appearance = " ".join(font_properties)
136+ # Determine the font size to use, prioritizing the user's input
137+ if user_font_size > 0:
138+ font_size = user_font_size
143139
144140 # Try to find a resource dictionary for the font
145141 document_resources: Any = cast(
@@ -207,7 +203,7 @@ def from_text_annotation(
207203
208204 # Create the TextStreamAppearance instance
209205 new_appearance_stream = cls(
210- text, selection, default_appearance, font_glyph_byte_map, rect, font_size, multiline
206+ text, selection, font_glyph_byte_map, rect, font_name, font_size, font_color , multiline
211207 )
212208
213209 if AnnotationDictionaryAttributes.AP in annotation:
0 commit comments