|
5 | 5 | """
|
6 | 6 | Display Text module helper functions
|
7 | 7 | """
|
| 8 | +from displayio import Group, Palette |
8 | 9 |
|
9 | 10 |
|
10 | 11 | def wrap_text_to_pixels(string, max_width, font=None, indent0="", indent1=""):
|
@@ -141,3 +142,224 @@ def chunks(lst, n):
|
141 | 142 | if the_lines[0][0] == " ":
|
142 | 143 | the_lines[0] = the_lines[0][1:]
|
143 | 144 | return the_lines
|
| 145 | + |
| 146 | + |
| 147 | +class LabelBase(Group): |
| 148 | + """Super class that all other types of labels will extend. This contains |
| 149 | + all of the properties and functions that work the same way in all labels. |
| 150 | +
|
| 151 | + subclasses should implement _set_text, _set_font, and _set_line_spacing to |
| 152 | + have the correct behavior fo rthat type of label. |
| 153 | +
|
| 154 | + :param Font font: A font class that has ``get_bounding_box`` and ``get_glyph``. |
| 155 | + Must include a capital M for measuring character size. |
| 156 | + :param str text: Text to display |
| 157 | + :param int max_glyphs: Unnecessary parameter (provided only for direct compability |
| 158 | + with label.py) |
| 159 | + :param int color: Color of all text in RGB hex |
| 160 | + :param int background_color: Color of the background, use `None` for transparent |
| 161 | + :param double line_spacing: Line spacing of text to display |
| 162 | + :param boolean background_tight: Set `True` only if you want background box to tightly |
| 163 | + surround text. When set to 'True' Padding parameters will be ignored. |
| 164 | + :param int padding_top: Additional pixels added to background bounding box at top |
| 165 | + :param int padding_bottom: Additional pixels added to background bounding box at bottom |
| 166 | + :param int padding_left: Additional pixels added to background bounding box at left |
| 167 | + :param int padding_right: Additional pixels added to background bounding box at right |
| 168 | + :param (float,float) anchor_point: Point that anchored_position moves relative to. |
| 169 | + Tuple with decimal percentage of width and height. |
| 170 | + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.) |
| 171 | + :param (int,int) anchored_position: Position relative to the anchor_point. Tuple |
| 172 | + containing x,y pixel coordinates. |
| 173 | + :param int scale: Integer value of the pixel scaling |
| 174 | + :param bool save_text: Set True to save the text string as a constant in the |
| 175 | + label structure. Set False to reduce memory use. |
| 176 | + :param: bool base_alignment: when True allows to align text label to the baseline. |
| 177 | + This is helpful when two or more labels need to be aligned to the same baseline""" |
| 178 | + |
| 179 | + # pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals, too-many-arguments |
| 180 | + def __init__( |
| 181 | + self, |
| 182 | + font, |
| 183 | + x=0, |
| 184 | + y=0, |
| 185 | + text="", |
| 186 | + max_glyphs=None, |
| 187 | + # with label.py |
| 188 | + color=0xFFFFFF, |
| 189 | + background_color=None, |
| 190 | + line_spacing=1.25, |
| 191 | + background_tight=False, |
| 192 | + padding_top=0, |
| 193 | + padding_bottom=0, |
| 194 | + padding_left=0, |
| 195 | + padding_right=0, |
| 196 | + anchor_point=None, |
| 197 | + anchored_position=None, |
| 198 | + save_text=True, # can reduce memory use if save_text = False |
| 199 | + scale=1, |
| 200 | + base_alignment=False, |
| 201 | + **kwargs, |
| 202 | + ): |
| 203 | + super().__init__(max_size=1, x=x, y=y, scale=1) |
| 204 | + |
| 205 | + self._font = font |
| 206 | + self.palette = Palette(2) |
| 207 | + self._color = color |
| 208 | + self._background_color = background_color |
| 209 | + |
| 210 | + self._bounding_box = None |
| 211 | + self._anchor_point = anchor_point |
| 212 | + self._anchored_position = anchored_position |
| 213 | + |
| 214 | + # local group will hold background and text |
| 215 | + # the self group scale should always remain at 1, the self.local_group will |
| 216 | + # be used to set the scale of the label |
| 217 | + self.local_group = None |
| 218 | + |
| 219 | + self._text = text |
| 220 | + |
| 221 | + def _get_ascent_descent(self): |
| 222 | + """ Private function to calculate ascent and descent font values """ |
| 223 | + if hasattr(self.font, "ascent"): |
| 224 | + return self.font.ascent, self.font.descent |
| 225 | + |
| 226 | + # check a few glyphs for maximum ascender and descender height |
| 227 | + glyphs = "M j'" # choose glyphs with highest ascender and lowest |
| 228 | + try: |
| 229 | + self._font.load_glyphs(glyphs) |
| 230 | + except AttributeError: |
| 231 | + # Builtin font doesn't have or need load_glyphs |
| 232 | + pass |
| 233 | + # descender, will depend upon font used |
| 234 | + ascender_max = descender_max = 0 |
| 235 | + for char in glyphs: |
| 236 | + this_glyph = self._font.get_glyph(ord(char)) |
| 237 | + if this_glyph: |
| 238 | + ascender_max = max(ascender_max, this_glyph.height + this_glyph.dy) |
| 239 | + descender_max = max(descender_max, -this_glyph.dy) |
| 240 | + return ascender_max, descender_max |
| 241 | + |
| 242 | + def _get_ascent(self): |
| 243 | + return self._get_ascent_descent()[0] |
| 244 | + |
| 245 | + @property |
| 246 | + def font(self): |
| 247 | + """Font to use for text display.""" |
| 248 | + return self._font |
| 249 | + |
| 250 | + def _set_font(self, new_font): |
| 251 | + # subclasses should override this |
| 252 | + pass |
| 253 | + |
| 254 | + @font.setter |
| 255 | + def font(self, new_font): |
| 256 | + self._set_font(new_font) |
| 257 | + |
| 258 | + @property |
| 259 | + def color(self): |
| 260 | + """Color of the text as an RGB hex number.""" |
| 261 | + return self._color |
| 262 | + |
| 263 | + @color.setter |
| 264 | + def color(self, new_color): |
| 265 | + self._color = new_color |
| 266 | + if new_color is not None: |
| 267 | + self.palette[1] = new_color |
| 268 | + self.palette.make_opaque(1) |
| 269 | + else: |
| 270 | + self.palette[1] = 0 |
| 271 | + self.palette.make_transparent(1) |
| 272 | + |
| 273 | + @property |
| 274 | + def background_color(self): |
| 275 | + """Color of the background as an RGB hex number.""" |
| 276 | + return self._background_color |
| 277 | + |
| 278 | + @background_color.setter |
| 279 | + def background_color(self, new_color): |
| 280 | + self._background_color = new_color |
| 281 | + if new_color is not None: |
| 282 | + self.palette[0] = new_color |
| 283 | + self.palette.make_opaque(0) |
| 284 | + else: |
| 285 | + self.palette[0] = 0 |
| 286 | + self.palette.make_transparent(0) |
| 287 | + |
| 288 | + @property |
| 289 | + def anchor_point(self): |
| 290 | + """Point that anchored_position moves relative to. |
| 291 | + Tuple with decimal percentage of width and height. |
| 292 | + (E.g. (0,0) is top left, (1.0, 0.5): is middle right.)""" |
| 293 | + return self._anchor_point |
| 294 | + |
| 295 | + @anchor_point.setter |
| 296 | + def anchor_point(self, new_anchor_point): |
| 297 | + self._anchor_point = new_anchor_point |
| 298 | + self.anchored_position = ( |
| 299 | + self._anchored_position |
| 300 | + ) # update the anchored_position using setter |
| 301 | + |
| 302 | + @property |
| 303 | + def anchored_position(self): |
| 304 | + """Position relative to the anchor_point. Tuple containing x,y |
| 305 | + pixel coordinates.""" |
| 306 | + return self._anchored_position |
| 307 | + |
| 308 | + @anchored_position.setter |
| 309 | + def anchored_position(self, new_position): |
| 310 | + self._anchored_position = new_position |
| 311 | + # Set anchored_position |
| 312 | + if (self._anchor_point is not None) and (self._anchored_position is not None): |
| 313 | + self.x = int( |
| 314 | + new_position[0] |
| 315 | + - (self._bounding_box[0] * self.scale) |
| 316 | + - round(self._anchor_point[0] * (self._bounding_box[2] * self.scale)) |
| 317 | + ) |
| 318 | + self.y = int( |
| 319 | + new_position[1] |
| 320 | + - (self._bounding_box[1] * self.scale) |
| 321 | + - round(self._anchor_point[1] * self._bounding_box[3] * self.scale) |
| 322 | + ) |
| 323 | + |
| 324 | + @property |
| 325 | + def scale(self): |
| 326 | + """Set the scaling of the label, in integer values""" |
| 327 | + return self.local_group.scale |
| 328 | + |
| 329 | + @scale.setter |
| 330 | + def scale(self, new_scale): |
| 331 | + self.local_group.scale = new_scale |
| 332 | + self.anchored_position = self._anchored_position # update the anchored_position |
| 333 | + |
| 334 | + def _set_text(self, new_text, scale): |
| 335 | + # subclasses should override this |
| 336 | + pass |
| 337 | + |
| 338 | + @property |
| 339 | + def text(self): |
| 340 | + """Text to be displayed.""" |
| 341 | + return self._text |
| 342 | + |
| 343 | + @text.setter # Cannot set color or background color with text setter, use separate setter |
| 344 | + def text(self, new_text): |
| 345 | + self._set_text(new_text, self.scale) |
| 346 | + |
| 347 | + @property |
| 348 | + def bounding_box(self): |
| 349 | + """An (x, y, w, h) tuple that completely covers all glyphs. The |
| 350 | + first two numbers are offset from the x, y origin of this group""" |
| 351 | + return tuple(self._bounding_box) |
| 352 | + |
| 353 | + @property |
| 354 | + def line_spacing(self): |
| 355 | + """The amount of space between lines of text, in multiples of the font's |
| 356 | + bounding-box height. (E.g. 1.0 is the bounding-box height)""" |
| 357 | + return self._line_spacing |
| 358 | + |
| 359 | + def _set_line_spacing(self, new_line_spacing): |
| 360 | + # subclass should override this. |
| 361 | + pass |
| 362 | + |
| 363 | + @line_spacing.setter |
| 364 | + def line_spacing(self, new_line_spacing): |
| 365 | + self._set_line_spacing(new_line_spacing) |
0 commit comments