Skip to content

Commit ffa433d

Browse files
committed
ENH: TextAppearanceStream: Scale and wrap text
This patch scales and/or wrap text that does not fit into a text field unaltered, under the condition that font size was set to 0 in the default appearance stream. We only wrap text if the multiline bit was set in the corresponding annotation's field flags, otherwise we just scale the font until it fits. We move the escaping of parentheses below, so that it does not interfere with calculating the width of a text string.
1 parent 06c430e commit ffa433d

File tree

1 file changed

+23
-1
lines changed

1 file changed

+23
-1
lines changed

pypdf/generic/_appearance_stream.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any, Optional, Union, cast
33

44
from .._cmap import _default_fonts_space_width, build_char_map_from_dict
5+
from .._codecs.core_fontmetrics import CORE_FONT_METRICS
56
from .._font import FontDescriptor
67
from .._utils import logger_warning
78
from ..constants import AnnotationDictionaryAttributes, FieldDictionaryAttributes
@@ -132,6 +133,7 @@ def _generate_appearance_stream_data(
132133
text: str = "",
133134
selection: Optional[list[str]] = None,
134135
rectangle: Union[RectangleObject, tuple[float, float, float, float]] = (0.0, 0.0, 0.0, 0.0),
136+
font_descriptor: FontDescriptor = CORE_FONT_METRICS["Helvetica"],
135137
font_glyph_byte_map: Optional[dict[str, bytes]] = None,
136138
font_name: str = "/Helv",
137139
font_size: float = 0.0,
@@ -169,10 +171,27 @@ def _generate_appearance_stream_data(
169171

170172
# If font_size is 0, apply the logic for multiline or large-as-possible font
171173
if font_size == 0:
174+
if selection: # Don't wrap text when dealing with a /Ch field, in order to prevent problems
175+
multiline = False # with matching "selection" with "line" later on.
172176
if multiline:
173177
font_size = DEFAULT_FONT_SIZE_IN_MULTILINE
174178
else:
175179
font_size = rectangle.height - 2
180+
lines, font_size = self._scale_text(
181+
font_descriptor,
182+
font_size,
183+
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:")
185+
rectangle.height - 3, # One point margin for top and bottom, one point extra for the first line
186+
# (see y_offset)
187+
text,
188+
multiline,
189+
)
190+
else:
191+
lines = [(
192+
font_descriptor.text_width(line) * font_size / 1000,
193+
line
194+
) for line in text.replace("\n", "\r").split("\r")]
176195

177196
# Set the vertical offset
178197
y_offset = rectangle.height - 1 - font_size
@@ -183,7 +202,7 @@ def _generate_appearance_stream_data(
183202
f"re\nW\nBT\n{default_appearance}\n".encode()
184203
)
185204

186-
for line_number, line in enumerate(text.replace("\n", "\r").split("\r")):
205+
for line_number, (line_width, line) in enumerate(lines):
187206
if selection and line in selection:
188207
# Might be improved, but cannot find how to get fill working => replaced with lined box
189208
ap_stream += (
@@ -240,6 +259,7 @@ def __init__(
240259
# If a font resource was added, get the font character map
241260
if font_resource:
242261
font_resource = cast(DictionaryObject, font_resource.get_object())
262+
font_descriptor = FontDescriptor.from_font_resource(font_resource)
243263
_font_subtype, _, font_encoding, font_map = build_char_map_from_dict(
244264
200, font_resource
245265
)
@@ -260,11 +280,13 @@ def __init__(
260280
else:
261281
logger_warning(f"Font dictionary for {font_name} not found.", __name__)
262282
font_glyph_byte_map = {}
283+
font_descriptor = FontDescriptor()
263284

264285
ap_stream_data = self._generate_appearance_stream_data(
265286
text,
266287
selection,
267288
rectangle,
289+
font_descriptor,
268290
font_glyph_byte_map,
269291
font_name,
270292
font_size,

0 commit comments

Comments
 (0)