|
44 | 44 | __version__ = "0.0.0-auto.0"
|
45 | 45 | __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_framebuf.git"
|
46 | 46 |
|
| 47 | +import struct |
| 48 | + |
47 | 49 | # Framebuf format constants:
|
48 | 50 | MVLSB = 0 # Single bit displays (like SSD1306 OLED)
|
49 | 51 | RGB565 = 1 # 16-bit color displays
|
50 | 52 | GS4_HMSB = 2 # Unimplemented!
|
51 | 53 |
|
52 |
| - |
53 | 54 | class MVLSBFormat:
|
54 | 55 | """MVLSBFormat"""
|
55 | 56 | @staticmethod
|
@@ -134,6 +135,7 @@ def __init__(self, buf, width, height, buf_format=MVLSB, stride=None):
|
134 | 135 | self.width = width
|
135 | 136 | self.height = height
|
136 | 137 | self.stride = stride
|
| 138 | + self._font = None |
137 | 139 | if self.stride is None:
|
138 | 140 | self.stride = width
|
139 | 141 | if buf_format == MVLSB:
|
@@ -183,9 +185,9 @@ def rect(self, x, y, width, height, color):
|
183 | 185 | a 1 pixel outline."""
|
184 | 186 | # pylint: disable=too-many-arguments
|
185 | 187 | self.fill_rect(x, y, width, 1, color)
|
186 |
| - self.fill_rect(x, y + height, width, 1, color) |
| 188 | + self.fill_rect(x, y + height-1, width, 1, color) |
187 | 189 | self.fill_rect(x, y, 1, height, color)
|
188 |
| - self.fill_rect(x + width, y, 1, height, color) |
| 190 | + self.fill_rect(x + width - 1, y, 1, height, color) |
189 | 191 |
|
190 | 192 | def line(self, x_0, y_0, x_1, y_1, color):
|
191 | 193 | # pylint: disable=too-many-arguments
|
@@ -223,9 +225,85 @@ def scroll(self):
|
223 | 225 | """scroll is not yet implemented"""
|
224 | 226 | raise NotImplementedError()
|
225 | 227 |
|
226 |
| - def text(self): |
227 |
| - """text is not yet implemented""" |
228 |
| - raise NotImplementedError() |
| 228 | + def text(self, string, x, y, color, *, |
| 229 | + font_name="font5x8.bin"): |
| 230 | + """Write an ascii string to location (x, y) as the bottom left corner, |
| 231 | + in a given color, in left-to-right fashion. |
| 232 | + Does not handle text wrapping. You can also pass in a font_name, but |
| 233 | + this has to be generated as a special binary format and placed in the |
| 234 | + working directory of the main script (so, probably /)""" |
| 235 | + if not self._font or self._font.font_name != font_name: |
| 236 | + # load the font! |
| 237 | + self._font = BitmapFont() |
| 238 | + w = self._font.font_width |
| 239 | + for i, char in enumerate(string): |
| 240 | + self._font.draw_char(char, |
| 241 | + x + (i * (w + 1)), |
| 242 | + y, self, color) |
| 243 | + |
| 244 | +# MicroPython basic bitmap font renderer. |
| 245 | +# Author: Tony DiCola |
| 246 | +# License: MIT License (https://opensource.org/licenses/MIT) |
| 247 | +class BitmapFont: |
| 248 | + """A helper class to read binary font tiles and 'seek' through them as a |
| 249 | + file to display in a framebuffer. We use file access so we dont waste 1KB |
| 250 | + of RAM on a font!""" |
| 251 | + def __init__(self, font_name='font5x8.bin'): |
| 252 | + # Specify the drawing area width and height, and the pixel function to |
| 253 | + # call when drawing pixels (should take an x and y param at least). |
| 254 | + # Optionally specify font_name to override the font file to use (default |
| 255 | + # is font5x8.bin). The font format is a binary file with the following |
| 256 | + # format: |
| 257 | + # - 1 unsigned byte: font character width in pixels |
| 258 | + # - 1 unsigned byte: font character height in pixels |
| 259 | + # - x bytes: font data, in ASCII order covering all 255 characters. |
| 260 | + # Each character should have a byte for each pixel column of |
| 261 | + # data (i.e. a 5x8 font has 5 bytes per character). |
| 262 | + self.font_name = font_name |
| 263 | + |
| 264 | + # Open the font file and grab the character width and height values. |
| 265 | + # Note that only fonts up to 8 pixels tall are currently supported. |
| 266 | + try: |
| 267 | + self._font = open(self.font_name, 'rb') |
| 268 | + except OSError: |
| 269 | + print("Could not find font file", font_name) |
| 270 | + raise |
| 271 | + self.font_width, self.font_height = struct.unpack('BB', self._font.read(2)) |
| 272 | + |
| 273 | + def deinit(self): |
| 274 | + """Close the font file as cleanup.""" |
| 275 | + self._font.close() |
| 276 | + |
| 277 | + def __enter__(self): |
| 278 | + """Initialize/open the font file""" |
| 279 | + self.__init__() |
| 280 | + return self |
| 281 | + |
| 282 | + def __exit__(self, exception_type, exception_value, traceback): |
| 283 | + """cleanup on exit""" |
| 284 | + self.deinit() |
| 285 | + |
| 286 | + def draw_char(self, char, x, y, framebuffer, color): |
| 287 | + # pylint: disable=too-many-arguments |
| 288 | + """Draw one character at position (x,y) to a framebuffer in a given color""" |
| 289 | + # Don't draw the character if it will be clipped off the visible area. |
| 290 | + if x < -self.font_width or x >= framebuffer.width or \ |
| 291 | + y < -self.font_height or y >= framebuffer.height: |
| 292 | + return |
| 293 | + # Go through each column of the character. |
| 294 | + for char_x in range(self.font_width): |
| 295 | + # Grab the byte for the current column of font data. |
| 296 | + self._font.seek(2 + (ord(char) * self.font_width) + char_x) |
| 297 | + line = struct.unpack('B', self._font.read(1))[0] |
| 298 | + # Go through each row in the column byte. |
| 299 | + for char_y in range(self.font_height): |
| 300 | + # Draw a pixel for each bit that's flipped on. |
| 301 | + if (line >> char_y) & 0x1: |
| 302 | + framebuffer.pixel(x + char_x, y + char_y, color) |
| 303 | + |
| 304 | + def width(self, text): |
| 305 | + """Return the pixel width of the specified text message.""" |
| 306 | + return len(text) * (self.font_width + 1) |
229 | 307 |
|
230 | 308 |
|
231 | 309 | class FrameBuffer1(FrameBuffer): # pylint: disable=abstract-method
|
|
0 commit comments