1010Text graphics handling for CircuitPython, including text boxes
1111
1212
13- * Author(s): Kevin Matocha
13+ * Author(s): Kevin Matocha, Tim Cocks
1414
1515Implementation Notes
1616--------------------
@@ -114,29 +114,57 @@ def __init__(
114114 font : FontProtocol ,
115115 save_text : bool = True ,
116116 color_palette : Optional [displayio .Palette ] = None ,
117+ outline_color : Optional [int ] = None ,
118+ outline_size : int = 1 ,
117119 ** kwargs ,
118120 ) -> None :
119121 self ._bitmap = None
120122 self ._tilegrid = None
121123 self ._prev_label_direction = None
122124
125+ if "padding_top" not in kwargs :
126+ kwargs ["padding_top" ] = outline_size + 0
127+ if "padding_bottom" not in kwargs :
128+ kwargs ["padding_bottom" ] = outline_size + 2
129+ if "padding_left" not in kwargs :
130+ kwargs ["padding_left" ] = outline_size + 0
131+ if "padding_right" not in kwargs :
132+ kwargs ["padding_right" ] = outline_size + 0
133+
123134 super ().__init__ (font , ** kwargs )
124135
125136 if color_palette is not None :
126- if len (color_palette ) <= 2 :
137+ if len (color_palette ) <= 3 :
127138 raise ValueError (
128- "color_palette should be at least 3 colors to "
129- "provide enough for normal and accented text. "
139+ "color_palette should be at least 4 colors to "
140+ "provide enough for normal, accented, and outlined text. "
130141 "color_palette argument can be omitted if not "
131142 "using accents."
132143 )
133144 self ._palette = color_palette
134145 self .color = self ._color
135146 self .background_color = self ._background_color
136-
147+ else :
148+ _background_color = self ._palette [0 ]
149+ _foreground_color = self ._palette [1 ]
150+ _background_is_transparent = self ._palette .is_transparent (0 )
151+ self ._palette = displayio .Palette (3 )
152+ self ._palette [0 ] = _background_color
153+ self ._palette [1 ] = _foreground_color
154+ self ._palette [2 ] = outline_color if outline_color is not None else 0x999999
155+ if _background_is_transparent :
156+ self ._palette .make_transparent (0 )
157+
158+ # accent handling vars
137159 self ._accent_ranges = []
138160 self ._tmp_glyph_bitmap = None
139161
162+ # outline handling vars
163+ self ._outline_size = outline_size
164+ self ._outline_color = outline_color
165+ if self ._outline_color is not None :
166+ self ._init_outline_stamp (outline_size )
167+
140168 self ._save_text = save_text
141169 self ._text = self ._replace_tabs (self ._text )
142170
@@ -148,6 +176,11 @@ def __init__(
148176 scale = self .scale ,
149177 )
150178
179+ def _init_outline_stamp (self , outline_size ):
180+ self ._outline_size = outline_size
181+ self ._stamp_source = displayio .Bitmap ((outline_size * 2 ) + 1 , (outline_size * 2 ) + 1 , 3 )
182+ self ._stamp_source .fill (2 )
183+
151184 def _reset_text (
152185 self ,
153186 font : Optional [FontProtocol ] = None ,
@@ -498,9 +531,36 @@ def _place_text(
498531
499532 xposition = xposition + my_glyph .shift_x
500533
534+ self ._add_outline ()
501535 # bounding_box
502536 return left , top , right - left , bottom - top
503537
538+ def _add_outline (self ):
539+ """
540+ Blit the outline into the labels Bitmap. Will blit self._stamp_source for each
541+ pixel of the foreground color but skip the foreground color when we blit,
542+ creating an outline.
543+ :return: None
544+ """
545+ if self ._outline_color is not None :
546+ for y in range (self .bitmap .height ):
547+ for x in range (self .bitmap .width ):
548+ if self .bitmap [x , y ] == 1 :
549+ try :
550+ bitmaptools .blit (
551+ self .bitmap ,
552+ self ._stamp_source ,
553+ x - self ._outline_size ,
554+ y - self ._outline_size ,
555+ skip_dest_index = 1 ,
556+ )
557+ except ValueError as value_error :
558+ raise ValueError (
559+ "Padding must be big enough to fit outline_size "
560+ "all the way around the text. "
561+ "Try using either larger padding sizes, or smaller outline_size."
562+ ) from value_error
563+
504564 def _blit (
505565 self ,
506566 bitmap : displayio .Bitmap , # target bitmap
@@ -627,6 +687,41 @@ def bitmap(self) -> displayio.Bitmap:
627687 """
628688 return self ._bitmap
629689
690+ @property
691+ def outline_color (self ):
692+ """Color of the outline to draw around the text. Or None for no outline."""
693+
694+ return self ._palette [2 ] if self ._outline_color is not None else None
695+
696+ @outline_color .setter
697+ def outline_color (self , new_outline_color ):
698+ if new_outline_color is not None :
699+ self ._palette [2 ] = new_outline_color
700+ else :
701+ self ._outline_color = None
702+
703+ @property
704+ def outline_size (self ):
705+ """Stroke size of the outline to draw around the text."""
706+ return self ._outline_size
707+
708+ @outline_size .setter
709+ def outline_size (self , new_outline_size ):
710+ self ._outline_size = new_outline_size
711+
712+ self ._padding_top = new_outline_size + 0
713+ self ._padding_bottom = new_outline_size + 2
714+ self ._padding_left = new_outline_size + 0
715+ self ._padding_right = new_outline_size + 0
716+
717+ self ._init_outline_stamp (new_outline_size )
718+ self ._reset_text (
719+ font = self ._font ,
720+ text = self ._text ,
721+ line_spacing = self ._line_spacing ,
722+ scale = self .scale ,
723+ )
724+
630725 def add_accent_range (self , start , end , foreground_color , background_color ):
631726 """
632727 Set a range of text to get accented with the specified colors.
0 commit comments