22from typing import Any , Optional , Union , cast
33
44from .._cmap import _default_fonts_space_width , build_char_map_from_dict
5+ from .._font import FontDescriptor
56from .._utils import logger_warning
67from ..constants import AnnotationDictionaryAttributes , FieldDictionaryAttributes
78from ..generic import (
@@ -24,6 +25,108 @@ class TextStreamAppearance(DecodedStreamObject):
2425 how text is rendered within a form field's bounding box. It handles properties
2526 like font, font size, color, multiline text, and text selection highlighting.
2627 """
28+ def _scale_text (
29+ self ,
30+ font_descriptor : FontDescriptor ,
31+ font_size : float ,
32+ field_width : float ,
33+ field_height : float ,
34+ txt : str ,
35+ multiline : bool ,
36+ min_font_size : float = 4.0 , # Minimum font size to attempt
37+ font_size_step : float = 0.2 # How much to decrease font size by each step
38+ ) -> tuple [list [tuple [float , str ]], float ]:
39+ """
40+ Takes a piece of text and scales it to field_width or field_height, given font_name
41+ and font_size. For multiline fields, adds newlines to wrap the text.
42+
43+ Args:
44+ font_descriptor: A FontDescriptor for the font to be used.
45+ font_size: The font size in points.
46+ field_width: The width of the field in which to fit the text.
47+ field_height: The height of the field in which to fit the text.
48+ txt: The text to fit with the field.
49+ multiline: Whether to scale and wrap the text, or only to scale.
50+ min_font_size: The minimum font size at which to scale the text.
51+ font_size_step: The amount by which to decrement font size per step while scaling.
52+
53+ Returns:
54+ The text in in the form of list of tuples, each tuple containing the length of a line
55+ and it contents, and the font_size for these lines and lengths.
56+ """
57+ # Single line:
58+ if not multiline :
59+ test_width = font_descriptor .text_width (txt ) * font_size / 1000
60+ if test_width > field_width or font_size > field_height :
61+ new_font_size = font_size - font_size_step
62+ if new_font_size >= min_font_size :
63+ # Text overflows height; Retry with smaller font size.
64+ return self ._scale_text (
65+ font_descriptor ,
66+ round (new_font_size , 1 ),
67+ field_width ,
68+ field_height ,
69+ txt ,
70+ multiline ,
71+ min_font_size ,
72+ font_size_step
73+ )
74+ # Font size lower than set minimum font size, give up.
75+ return [(test_width , txt )], font_size
76+ return [(test_width , txt )], font_size
77+ # Multiline:
78+ orig_txt = txt
79+ paragraphs = re .sub (r"\n" , "\r " , txt ).split ("\r " )
80+ wrapped_lines = []
81+ current_line_words : list [str ] = []
82+ current_line_width : float = 0
83+ space_width = font_descriptor .text_width (" " ) * font_size / 1000
84+ for paragraph in paragraphs :
85+ if not paragraph .strip ():
86+ wrapped_lines .append ((0.0 , "" ))
87+ continue
88+ words = paragraph .split (" " )
89+ for i , word in enumerate (words ):
90+ word_width = font_descriptor .text_width (word ) * font_size / 1000
91+ test_width = current_line_width + word_width + (space_width if i else 0 )
92+ if test_width > field_width and current_line_words :
93+ wrapped_lines .append ((current_line_width , " " .join (current_line_words )))
94+ current_line_words = [word ]
95+ current_line_width = word_width
96+ elif not current_line_words and word_width > field_width :
97+ wrapped_lines .append ((word_width , word ))
98+ current_line_words = []
99+ current_line_width = 0
100+ else :
101+ if current_line_words :
102+ current_line_width += space_width
103+ current_line_words .append (word )
104+ current_line_width += word_width
105+ if current_line_words :
106+ wrapped_lines .append ((current_line_width , " " .join (current_line_words )))
107+ current_line_words = []
108+ current_line_width = 0
109+ # Estimate total height.
110+ # Assumed line spacing of 1.4
111+ estimated_total_height = font_size + (len (wrapped_lines ) - 1 ) * 1.4 * font_size
112+ if estimated_total_height > field_height :
113+ new_font_size = font_size - font_size_step
114+ if new_font_size >= min_font_size :
115+ # Text overflows height; Retry with smaller font size.
116+ return self ._scale_text (
117+ font_descriptor ,
118+ round (new_font_size , 1 ),
119+ field_width ,
120+ field_height ,
121+ orig_txt ,
122+ multiline ,
123+ min_font_size ,
124+ font_size_step
125+ )
126+ # Font size lower than set minimum font size, give up.
127+ return (wrapped_lines , font_size )
128+ return (wrapped_lines , font_size )
129+
27130 def _generate_appearance_stream_data (
28131 self ,
29132 text : str = "" ,
0 commit comments