Skip to content

Commit ef65049

Browse files
committed
MAINT: _appearance_stream: Move font_resource parsing
Move the font resource parsing code to TextAppearanceStream, in the hope that, later, one might be able to generate a TextAppearanceStream directly. I wonder, though, where the necessary font resource would come from.
1 parent 4046ac2 commit ef65049

File tree

1 file changed

+44
-44
lines changed

1 file changed

+44
-44
lines changed

pypdf/generic/_appearance_stream.py

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def _appearance_stream_data(
2525
self,
2626
text: str = "",
2727
selection: Optional[list[str]] = None,
28-
font_glyph_byte_map: Optional[dict[str, bytes]] = None,
2928
rect: Union[RectangleObject, tuple[float, float, float, float]] = (0.0, 0.0, 0.0, 0.0),
29+
font_glyph_byte_map: Optional[dict[str, bytes]] = None,
3030
font_name: str = "/Helv",
3131
font_size: float = 0.0,
3232
font_color: str = "0 g",
@@ -73,17 +73,39 @@ def __init__(
7373
self,
7474
text: str = "",
7575
selection: Optional[list[str]] = None,
76-
font_glyph_byte_map: Optional[dict[str, bytes]] = None,
7776
rect: Union[RectangleObject, tuple[float, float, float, float]] = (0.0, 0.0, 0.0, 0.0),
77+
font_resource: Optional[DictionaryObject] = None,
7878
font_name: str = "/Helv",
7979
font_size: float = 0.0,
8080
font_color: str = "0 g",
8181
multiline: bool = False
8282
) -> None:
83-
font_glyph_byte_map = font_glyph_byte_map or {}
83+
# If a font resource was added, get the font character map
84+
if font_resource:
85+
font_resource = cast(DictionaryObject, font_resource.get_object())
86+
_font_subtype, _, font_encoding, font_map = build_char_map_from_dict(
87+
200, font_resource
88+
)
89+
try: # remove width stored in -1 key
90+
del font_map[-1]
91+
except KeyError:
92+
pass
93+
font_glyph_byte_map: dict[str, bytes]
94+
if isinstance(font_encoding, str):
95+
font_glyph_byte_map = {
96+
v: k.encode(font_encoding) for k, v in font_map.items()
97+
}
98+
else:
99+
font_glyph_byte_map = {v: bytes((k,)) for k, v in font_encoding.items()}
100+
font_encoding_rev = {v: bytes((k,)) for k, v in font_encoding.items()}
101+
for key, value in font_map.items():
102+
font_glyph_byte_map[value] = font_encoding_rev.get(key, key)
103+
else:
104+
logger_warning(f"Font dictionary for {font_name} not found.", __name__)
105+
font_glyph_byte_map = {}
84106

85107
ap_stream_data = self._appearance_stream_data(
86-
text, selection, font_glyph_byte_map, rect, font_name, font_size, font_color, multiline
108+
text, selection, rect, font_glyph_byte_map, font_name, font_size, font_color, multiline
87109
)
88110

89111
super().__init__()
@@ -92,6 +114,19 @@ def __init__(
92114
self[NameObject("/BBox")] = RectangleObject(rect)
93115
self.set_data(ByteStringObject(ap_stream_data))
94116
self[NameObject("/Length")] = NumberObject(len(ap_stream_data))
117+
# Update Resources with font information if necessary
118+
if font_resource is not None:
119+
self[NameObject("/Resources")] = DictionaryObject(
120+
{
121+
NameObject("/Font"): DictionaryObject(
122+
{
123+
NameObject(font_name): getattr(
124+
font_resource, "indirect_reference", font_resource
125+
)
126+
}
127+
)
128+
}
129+
)
95130

96131
@classmethod
97132
def from_text_annotation(
@@ -118,8 +153,10 @@ def from_text_annotation(
118153
else:
119154
default_appearance = default_appearance.get_object()
120155

121-
# Derive font size. Also embed user-provided font name and font size in the default
122-
# appearance, if given. Uses the variable font_properties as an intermediate.
156+
# Derive font name, size and color from the default appearance. Also set
157+
# user-provided font name and font size in the default appearance, if given.
158+
# For a font name, this presumes that we can find an associated font resource
159+
# dictionary. Uses the variable font_properties as an intermediate.
123160
# As per the PDF spec:
124161
# "At a minimum, the string [that is, default_appearance] shall include a Tf (text
125162
# font) operator along with its two operands, font and size" (p. 519 of Version 2.0).
@@ -156,30 +193,8 @@ def from_text_annotation(
156193
)
157194
document_font_resources = document_resources.get_object().get("/Font", DictionaryObject()).get_object()
158195
font_resource = document_font_resources.get(font_name, None)
159-
160-
# If this annotation has a font resources, get the font character map
161196
if not is_null_or_none(font_resource):
162197
font_resource = cast(DictionaryObject, font_resource.get_object())
163-
_font_subtype, _, font_encoding, font_map = build_char_map_from_dict(
164-
200, font_resource
165-
)
166-
try: # remove width stored in -1 key
167-
del font_map[-1]
168-
except KeyError:
169-
pass
170-
font_glyph_byte_map: dict[str, bytes]
171-
if isinstance(font_encoding, str):
172-
font_glyph_byte_map = {
173-
v: k.encode(font_encoding) for k, v in font_map.items()
174-
}
175-
else:
176-
font_glyph_byte_map = {v: bytes((k,)) for k, v in font_encoding.items()}
177-
font_encoding_rev = {v: bytes((k,)) for k, v in font_encoding.items()}
178-
for key, value in font_map.items():
179-
font_glyph_byte_map[value] = font_encoding_rev.get(key, key)
180-
else:
181-
logger_warning(f"Font dictionary for {font_name} not found.", __name__)
182-
font_glyph_byte_map = {}
183198

184199
# Retrieve field text, selected values and formatting information
185200
multiline = False
@@ -201,26 +216,11 @@ def from_text_annotation(
201216

202217
# Create the TextStreamAppearance instance
203218
new_appearance_stream = cls(
204-
text, selection, font_glyph_byte_map, rect, font_name, font_size, font_color, multiline
219+
text, selection, rect, font_resource, font_name, font_size, font_color, multiline
205220
)
206-
207221
if AnnotationDictionaryAttributes.AP in annotation:
208222
for k, v in cast(DictionaryObject, annotation[AnnotationDictionaryAttributes.AP]).get("/N", {}).items():
209223
if k not in {"/BBox", "/Length", "/Subtype", "/Type", "/Filter"}:
210224
new_appearance_stream[k] = v
211225

212-
# Update Resources with font information if necessary
213-
if font_resource is not None:
214-
new_appearance_stream[NameObject("/Resources")] = DictionaryObject(
215-
{
216-
NameObject("/Font"): DictionaryObject(
217-
{
218-
NameObject(font_name): getattr(
219-
font_resource, "indirect_reference", font_resource
220-
)
221-
}
222-
)
223-
}
224-
)
225-
226226
return new_appearance_stream

0 commit comments

Comments
 (0)