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