Skip to content

Commit 438cc37

Browse files
committed
ENH: _writer: Add right alignment and centering
This patch changes the generate_appearance_stream code so that it can deal with right alignment and centered text. Note that both require correct font metrics in order to work.
1 parent 3a6fdac commit 438cc37

File tree

1 file changed

+33
-6
lines changed

1 file changed

+33
-6
lines changed

pypdf/_writer.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ def _update_field_annotation(
943943
if font_size >= 0
944944
else float(font_properties[font_properties.index("Tf") - 1])
945945
)
946-
formatting = {
946+
formatting : dict[str, Union[bool, int]] = {
947947
"wrap" : False,
948948
"scale" : False,
949949
}
@@ -959,6 +959,7 @@ def _update_field_annotation(
959959
font_height = rct.height - 2
960960
formatting["scale"] = True
961961
font_properties[font_properties.index("Tf") - 1] = str(font_height)
962+
formatting["align"] = field.get("/Q", 0)
962963

963964
# Retrieve font information from local DR ...
964965
dr: Any = cast(
@@ -3554,7 +3555,7 @@ def generate_appearance_stream(
35543555
font_descriptor: FontDescriptor,
35553556
rct: RectangleObject,
35563557
font_height: float,
3557-
formatting: dict[str, bool],
3558+
formatting: dict[str, Union[bool, int]],
35583559
) -> bytes:
35593560
# Only wrap text for non-choice fields, otherwise we break matching sel and line later on.
35603561
if sel:
@@ -3567,7 +3568,7 @@ def generate_appearance_stream(
35673568
# takes one extra point (see below, under "line_number == 0:")
35683569
rct.height - 3, # One point margin for top and bottom, one point extra for the first line (see y_offset)
35693570
txt,
3570-
formatting["wrap"],
3571+
bool(formatting["wrap"]),
35713572
)
35723573
font_properties[1] = str(font_height)
35733574
else:
@@ -3576,7 +3577,11 @@ def generate_appearance_stream(
35763577
y_offset = rct.height - 1 - font_height
35773578
da = " ".join(font_properties)
35783579
ap_stream = f"q\n/Tx BMC \nq\n1 1 {rct.width - 1} {rct.height - 1} re\nW\nBT\n{da}\n".encode()
3580+
3581+
current_x_pos : float = 0 # Initial virtual position within the text object.
35793582
for line_number, line in enumerate(lines):
3583+
# Calculate line width
3584+
line_w = calculate_text_width(font_descriptor.character_widths, float(font_properties[1]), line)
35803585
# Escape parentheses (PDF 1.7 reference, table 3.2, Literal Strings)
35813586
line = line.replace("\\", "\\\\").replace("(", r"\(").replace(")", r"\)")
35823587
if line in sel:
@@ -3585,11 +3590,33 @@ def generate_appearance_stream(
35853590
f"1 {y_offset - (line_number * font_height * 1.4) - 1} {rct.width - 2} {font_height + 2} re\n"
35863591
f"0.5 0.5 0.5 rg s\n{da}\n"
35873592
).encode()
3593+
3594+
# Calculate the desired absolute starting X for the current line
3595+
desired_abs_x_start : float = 0
3596+
if formatting["align"] == 2: # Right aligned
3597+
desired_abs_x_start = rct.width - 2 - line_w
3598+
elif formatting["align"] == 1: # Centered
3599+
desired_abs_x_start = (rct.width - line_w) / 2
3600+
else: # Left aligned; default
3601+
desired_abs_x_start = 2
3602+
# Calculate x_rel_offset: how much to move from the current_x_pos
3603+
# to reach the desired_abs_x_start.
3604+
x_rel_offset = desired_abs_x_start - current_x_pos
3605+
3606+
# Y-offset:
3607+
y_rel_offset: float = 0
35883608
if line_number == 0:
3589-
ap_stream += f"2 {y_offset} Td\n".encode()
3609+
y_rel_offset = y_offset # Initial vertical position
35903610
else:
3591-
# Td is a relative translation
3592-
ap_stream += f"0 {- font_height * 1.4} Td\n".encode()
3611+
y_rel_offset = - font_height * 1.4 # Move down by line height
3612+
3613+
# Td is a relative translation (Tx and Ty).
3614+
# It updates the current text position.
3615+
ap_stream += f"{x_rel_offset} {y_rel_offset} Td\n".encode()
3616+
# Update current_x_pos based on the Td operation for the next iteration.
3617+
# This is the X position where the *current line* will start.
3618+
current_x_pos = desired_abs_x_start
3619+
35933620
enc_line: list[bytes] = [
35943621
font_full_rev.get(c, c.encode("utf-16-be")) for c in line
35953622
]

0 commit comments

Comments
 (0)