@@ -138,7 +138,8 @@ def _generate_appearance_stream_data(
138138 font_name : str = "/Helv" ,
139139 font_size : float = 0.0 ,
140140 font_color : str = "0 g" ,
141- multiline : bool = False
141+ multiline : bool = False ,
142+ alignment : int = 0
142143 ) -> bytes :
143144 """
144145 Generates the raw bytes of the PDF appearance stream for a text field.
@@ -160,6 +161,7 @@ def _generate_appearance_stream_data(
160161 font_color: The color to apply to the font, represented as a PDF
161162 graphics state string (e.g., "0 g" for black).
162163 multiline: A boolean indicating if the text field is multiline.
164+ alignment: Left-aligned (0), centered (1) or right-aligned (2) text.
163165
164166 Returns:
165167 A byte string containing the PDF content stream data.
@@ -181,7 +183,7 @@ def _generate_appearance_stream_data(
181183 font_descriptor ,
182184 font_size ,
183185 rectangle .width - 3 , # One point margin left and right, and an additional point because the first
184- # offset takes one extra point (see below, under "line_number == 0: ")
186+ # offset takes one extra point (see below, "desired_abs_x_start ")
185187 rectangle .height - 3 , # One point margin for top and bottom, one point extra for the first line
186188 # (see y_offset)
187189 text ,
@@ -201,19 +203,41 @@ def _generate_appearance_stream_data(
201203 f"q\n /Tx BMC \n q\n 1 1 { rectangle .width - 1 } { rectangle .height - 1 } "
202204 f"re\n W\n BT\n { default_appearance } \n " .encode ()
203205 )
204-
206+ current_x_pos : float = 0 # Initial virtual position within the text object.
205207 for line_number , (line_width , line ) in enumerate (lines ):
206208 if selection and line in selection :
207209 # Might be improved, but cannot find how to get fill working => replaced with lined box
208210 ap_stream += (
209211 f"1 { y_offset - (line_number * font_size * 1.4 ) - 1 } { rectangle .width - 2 } { font_size + 2 } re\n "
210212 f"0.5 0.5 0.5 rg s\n { default_appearance } \n "
211213 ).encode ()
214+
215+ # Calculate the desired absolute starting X for the current line
216+ desired_abs_x_start : float = 0
217+ if alignment == 2 : # Right aligned
218+ desired_abs_x_start = rectangle .width - 2 - line_width
219+ elif alignment == 1 : # Centered
220+ desired_abs_x_start = (rectangle .width - line_width ) / 2
221+ else : # Left aligned; default
222+ desired_abs_x_start = 2
223+ # Calculate x_rel_offset: how much to move from the current_x_pos
224+ # to reach the desired_abs_x_start.
225+ x_rel_offset = desired_abs_x_start - current_x_pos
226+
227+ # Y-offset:
228+ y_rel_offset : float = 0
212229 if line_number == 0 :
213- ap_stream += f"2 { y_offset } Td \n " . encode ()
230+ y_rel_offset = y_offset # Initial vertical position
214231 else :
215- # Td is a relative translation
216- ap_stream += f"0 { - font_size * 1.4 } Td\n " .encode ()
232+ y_rel_offset = - font_size * 1.4 # Move down by line height
233+
234+ # Td is a relative translation (Tx and Ty).
235+ # It updates the current text position.
236+ ap_stream += f"{ x_rel_offset } { y_rel_offset } Td\n " .encode ()
237+ # Update current_x_pos based on the Td operation for the next iteration.
238+ # This is the X position where the *current line* will start.
239+ current_x_pos = desired_abs_x_start
240+
217241 encoded_line : list [bytes ] = [
218242 font_glyph_byte_map .get (c , c .encode ("utf-16-be" )) for c in line
219243 ]
@@ -233,7 +257,8 @@ def __init__(
233257 font_name : str = "/Helv" ,
234258 font_size : float = 0.0 ,
235259 font_color : str = "0 g" ,
236- multiline : bool = False
260+ multiline : bool = False ,
261+ alignment : int = 0
237262 ) -> None :
238263 """
239264 Initializes a TextStreamAppearance object.
@@ -252,6 +277,7 @@ def __init__(
252277 font_size: The font size. If 0, it's auto-calculated.
253278 font_color: The font color string.
254279 multiline: A boolean indicating if the text field is multiline.
280+ alignment: Left-aligned (0), centered (1) or right-aligned (2) text.
255281
256282 """
257283 super ().__init__ ()
@@ -299,7 +325,8 @@ def __init__(
299325 font_name ,
300326 font_size ,
301327 font_color ,
302- multiline
328+ multiline ,
329+ alignment
303330 )
304331
305332 self [NameObject ("/Type" )] = NameObject ("/XObject" )
@@ -406,6 +433,7 @@ def from_text_annotation(
406433 # Retrieve field text, selected values and formatting information
407434 multiline = False
408435 field_flags = field .get (FieldDictionaryAttributes .Ff , 0 )
436+ alignment = field .get ("/Q" , 0 )
409437 if field_flags & FieldDictionaryAttributes .FfBits .Multiline :
410438 multiline = True
411439 if (
@@ -432,7 +460,8 @@ def from_text_annotation(
432460 font_name ,
433461 font_size ,
434462 font_color ,
435- multiline
463+ multiline ,
464+ alignment
436465 )
437466 if AnnotationDictionaryAttributes .AP in annotation :
438467 for key , value in (
0 commit comments