@@ -67,18 +67,29 @@ def __init__(
67
67
* ,
68
68
x = 0 ,
69
69
y = 0 ,
70
- text = None ,
70
+ text = "" ,
71
71
max_glyphs = None ,
72
72
color = 0xFFFFFF ,
73
73
background_color = None ,
74
74
line_spacing = 1.25 ,
75
+ background_tight = False ,
76
+ padding_top = 0 ,
77
+ padding_bottom = 0 ,
78
+ padding_left = 0 ,
79
+ padding_right = 0 ,
75
80
** kwargs
76
81
):
82
+ if "scale" in kwargs .keys ():
83
+ self ._scale = kwargs ["scale" ]
84
+ else :
85
+ self ._scale = 1
77
86
if not max_glyphs and not text :
78
87
raise RuntimeError ("Please provide a max size, or initial text" )
79
88
if not max_glyphs :
80
89
max_glyphs = len (text )
81
- super ().__init__ (max_size = max_glyphs , ** kwargs )
90
+ # add one to max_size for the background bitmap tileGrid
91
+ super ().__init__ (max_size = max_glyphs + 1 , ** kwargs )
92
+
82
93
self .width = max_glyphs
83
94
self ._font = font
84
95
self ._text = None
@@ -87,28 +98,95 @@ def __init__(
87
98
self .y = y
88
99
89
100
self .palette = displayio .Palette (2 )
90
- if background_color is not None :
91
- self .palette [0 ] = background_color
92
- self .palette .make_opaque (0 )
93
- self ._transparent_background = False
94
- else :
95
- self .palette [0 ] = 0
96
- self .palette .make_transparent (0 )
97
- self ._transparent_background = True
101
+ self .palette [0 ] = 0
102
+ self .palette .make_transparent (0 )
98
103
self .palette [1 ] = color
99
104
100
- bounds = self ._font .get_bounding_box ()
101
- self .height = bounds [1 ]
105
+ self .height = self ._font .get_bounding_box ()[1 ]
102
106
self ._line_spacing = line_spacing
103
107
self ._boundingbox = None
104
108
109
+ self ._background_tight = (
110
+ background_tight # sets padding status for text background box
111
+ )
112
+
113
+ self ._background_color = background_color
114
+ self ._background_palette = displayio .Palette (1 )
115
+ self .append (
116
+ displayio .TileGrid (
117
+ displayio .Bitmap (0 , 0 , 1 ), pixel_shader = self ._background_palette
118
+ )
119
+ ) # initialize with a blank tilegrid placeholder for background
120
+
121
+ self ._padding_top = padding_top
122
+ self ._padding_bottom = padding_bottom
123
+ self ._padding_left = padding_left
124
+ self ._padding_right = padding_right
125
+
105
126
if text is not None :
106
127
self ._update_text (str (text ))
107
128
129
+ def _create_background_box (self , lines , y_offset ):
130
+
131
+ left = self ._boundingbox [0 ]
132
+
133
+ if self ._background_tight : # draw a tight bounding box
134
+ box_width = self ._boundingbox [2 ]
135
+ box_height = self ._boundingbox [3 ]
136
+ x_box_offset = 0
137
+ y_box_offset = self ._boundingbox [1 ]
138
+
139
+ else : # draw a "loose" bounding box to include any ascenders/descenders.
140
+
141
+ # check a few glyphs for maximum ascender and descender height
142
+ # Enhancement: it would be preferred to access the font "FONT_ASCENT" and
143
+ # "FONT_DESCENT" in the imported BDF file
144
+ glyphs = "M j'" # choose glyphs with highest ascender and lowest
145
+ # descender, will depend upon font used
146
+ ascender_max = descender_max = 0
147
+ for char in glyphs :
148
+ this_glyph = self ._font .get_glyph (ord (char ))
149
+ if this_glyph :
150
+ ascender_max = max (ascender_max , this_glyph .height + this_glyph .dy )
151
+ descender_max = max (descender_max , - this_glyph .dy )
152
+
153
+ box_width = self ._boundingbox [2 ] + self ._padding_left + self ._padding_right
154
+ x_box_offset = - self ._padding_left
155
+ box_height = (
156
+ (ascender_max + descender_max )
157
+ + int ((lines - 1 ) * self .height * self ._line_spacing )
158
+ + self ._padding_top
159
+ + self ._padding_bottom
160
+ )
161
+ y_box_offset = - ascender_max + y_offset - self ._padding_top
162
+
163
+ self ._update_background_color (self ._background_color )
164
+ box_width = max (0 , box_width ) # remove any negative values
165
+ box_height = max (0 , box_height ) # remove any negative values
166
+
167
+ background_bitmap = displayio .Bitmap (box_width , box_height , 1 )
168
+ tile_grid = displayio .TileGrid (
169
+ background_bitmap ,
170
+ pixel_shader = self ._background_palette ,
171
+ x = left + x_box_offset ,
172
+ y = y_box_offset ,
173
+ )
174
+
175
+ return tile_grid
176
+
177
+ def _update_background_color (self , new_color ):
178
+
179
+ if new_color is None :
180
+ self ._background_palette .make_transparent (0 )
181
+ else :
182
+ self ._background_palette .make_opaque (0 )
183
+ self ._background_palette [0 ] = new_color
184
+ self ._background_color = new_color
185
+
108
186
def _update_text (self , new_text ): # pylint: disable=too-many-locals
109
187
x = 0
110
188
y = 0
111
- i = 0
189
+ i = 1
112
190
old_c = 0
113
191
y_offset = int (
114
192
(
@@ -118,17 +196,19 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals
118
196
/ 2
119
197
)
120
198
left = right = top = bottom = 0
199
+ lines = 1
121
200
for character in new_text :
122
201
if character == "\n " :
123
202
y += int (self .height * self ._line_spacing )
124
203
x = 0
204
+ lines += 1
125
205
continue
126
206
glyph = self ._font .get_glyph (ord (character ))
127
207
if not glyph :
128
208
continue
129
- right = max (right , x + glyph .width )
209
+ right = max (right , x + glyph .shift_x )
130
210
if y == 0 : # first line, find the Ascender height
131
- top = min (top , - glyph .height + y_offset )
211
+ top = min (top , - glyph .height - glyph . dy + y_offset )
132
212
bottom = max (bottom , y - glyph .dy + y_offset )
133
213
position_y = y - glyph .height - glyph .dy + y_offset
134
214
position_x = x + glyph .dx
@@ -138,6 +218,7 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals
138
218
or character != self ._text [old_c ]
139
219
):
140
220
try :
221
+ # pylint: disable=unexpected-keyword-arg
141
222
face = displayio .TileGrid (
142
223
glyph .bitmap ,
143
224
pixel_shader = self .palette ,
@@ -161,14 +242,14 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals
161
242
else :
162
243
self .append (face )
163
244
elif self ._text and character == self ._text [old_c ]:
245
+
164
246
try :
165
247
self [i ].position = (position_x , position_y )
166
248
except AttributeError :
167
249
self [i ].x = position_x
168
250
self [i ].y = position_y
169
251
170
252
x += glyph .shift_x
171
-
172
253
# TODO skip this for control sequences or non-printables.
173
254
i += 1
174
255
old_c += 1
@@ -187,6 +268,7 @@ def _update_text(self, new_text): # pylint: disable=too-many-locals
187
268
self .pop ()
188
269
self ._text = new_text
189
270
self ._boundingbox = (left , top , left + right , bottom - top )
271
+ self [0 ] = self ._create_background_box (lines , y_offset )
190
272
191
273
@property
192
274
def bounding_box (self ):
@@ -216,20 +298,11 @@ def color(self, new_color):
216
298
@property
217
299
def background_color (self ):
218
300
"""Color of the background as an RGB hex number."""
219
- if not self ._transparent_background :
220
- return self .palette [0 ]
221
- return None
301
+ return self ._background_color
222
302
223
303
@background_color .setter
224
304
def background_color (self , new_color ):
225
- if new_color is not None :
226
- self .palette [0 ] = new_color
227
- self .palette .make_opaque (0 )
228
- self ._transparent_background = False
229
- else :
230
- self .palette [0 ] = 0
231
- self .palette .make_transparent (0 )
232
- self ._transparent_background = True
305
+ self ._update_background_color (new_color )
233
306
234
307
@property
235
308
def text (self ):
@@ -238,9 +311,12 @@ def text(self):
238
311
239
312
@text .setter
240
313
def text (self , new_text ):
241
- current_anchored_position = self .anchored_position
242
- self ._update_text (str (new_text ))
243
- self .anchored_position = current_anchored_position
314
+ try :
315
+ current_anchored_position = self .anchored_position
316
+ self ._update_text (str (new_text ))
317
+ self .anchored_position = current_anchored_position
318
+ except RuntimeError :
319
+ raise RuntimeError ("Text length exceeds max_glyphs" )
244
320
245
321
@property
246
322
def font (self ):
@@ -253,8 +329,7 @@ def font(self, new_font):
253
329
current_anchored_position = self .anchored_position
254
330
self ._text = ""
255
331
self ._font = new_font
256
- bounds = self ._font .get_bounding_box ()
257
- self .height = bounds [1 ]
332
+ self .height = self ._font .get_bounding_box ()[1 ]
258
333
self ._update_text (str (old_text ))
259
334
self .anchored_position = current_anchored_position
260
335
@@ -290,13 +365,15 @@ def anchored_position(self):
290
365
291
366
@anchored_position .setter
292
367
def anchored_position (self , new_position ):
293
- self . x = int (
368
+ new_x = int (
294
369
new_position [0 ]
295
- - self ._boundingbox [0 ]
296
- - self ._anchor_point [0 ] * self ._boundingbox [2 ]
370
+ - self ._anchor_point [0 ] * (self ._boundingbox [2 ] * self ._scale )
297
371
)
298
- self .y = int (
372
+ new_y = self .y = int (
299
373
new_position [1 ]
300
- - self ._boundingbox [1 ]
301
- - self ._anchor_point [ 1 ] * self ._boundingbox [ 3 ]
374
+ - self ._anchor_point [1 ] * ( self . _boundingbox [ 3 ] * self . _scale )
375
+ + ( self ._boundingbox [ 3 ] * self ._scale ) / 2
302
376
)
377
+ self ._boundingbox = (new_x , new_y , self ._boundingbox [2 ], self ._boundingbox [3 ])
378
+ self .x = new_x
379
+ self .y = new_y
0 commit comments