From ee4a24cd63ae194d900aaf7afdeec00eaa032997 Mon Sep 17 00:00:00 2001 From: Owen Carter Date: Sat, 7 Dec 2024 15:13:14 +0100 Subject: [PATCH 01/17] stub prototype --- ezFBsevenSeg.py | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ sevenSegDemo.py | 17 ++++++++++ 2 files changed, 99 insertions(+) create mode 100644 ezFBsevenSeg.py create mode 100644 sevenSegDemo.py diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py new file mode 100644 index 00000000..781f7df3 --- /dev/null +++ b/ezFBsevenSeg.py @@ -0,0 +1,82 @@ +''' + ezFBsevenSeg.py: part of the microPyEZfonts repository + https://github.com/easytarget/microPyEZfonts + + This font definition can be used with the "ezFBfont" class provided there. + It can also be used with the "writer" class from Peter Hinches micropython + font-to-py tool: https://github.com/peterhinch/micropython-font-to-py +''' +''' + Copyright: +''' +version = '0.33' +name = '0.0.1' +family = 'fixed' +weight = 'medium' +size = 32 + +h = 32 # height +w = 16 # width +s = 0 # slant, todo! +c = True # cache + +def height(): + return h + +def baseline(): + return h + +def max_width(): + return w + +def hmap(): + return True + +def reverse(): + return False + +def monospaced(): + return False + +def min_ch(): + return charmap.keys[0] + +def max_ch(): + return return charmap.keys()[-1] + +def set(high=None, wide=None, slanted=None, cached=None, pre=None) + _clean_cache() + # modify defaults as required + h = high if high is not None else h + wide = wide if wide is not None else w + slant = slanted if slant is not None else s + cahched = cached if cached is not None else c + # constrain to value and type + h = int(max(5,h)) # integer, min = 5 + w = int(max(5,w)) # integer, min = 5 + s = int((max(-h,min(h,s))) # integer, max = (+/-)height + c = bool(cached) # bool + # precache + if pre is not None: + for ch in pre[0:-1]: + _gen(ch) + +def get(): + # returns height, width, slant, cache(bool) + # and the currently chached chars as a list + return w, h, s, c, cache.keys().sort() + +# segment polygons +segs = {} + +# character polygon lists +chars = {} + +# dictionary to hold cached chars +_g = {} + +def get_ch(ch): + c = ord(ch) + if c not in _g.keys(): + return None, 0, 0 + return memoryview(_g[c]), 26, 16 diff --git a/sevenSegDemo.py b/sevenSegDemo.py new file mode 100644 index 00000000..09d2b7b3 --- /dev/null +++ b/sevenSegDemo.py @@ -0,0 +1,17 @@ +# Demo the 7seg font + +from sys import path +path.append('drivers') +from repl_1306 import REPL_1306 +from ezFBfont import ezFBfont +import ezFBsevenSeg as sevenSeg + +# Display +display = REPL_1306(128, 64, clear=True) +display.invert(False) # as needed +display.rotate(0) # as needed +display.contrast(128) # as needed + +bigtime = ezFBfont(display, sevenSeg, halign='center', valign='center') + +sevenSeg.get() From 65dfd7dc79e5643982a455da332c7793697aa3b6 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 7 Dec 2024 19:04:09 +0100 Subject: [PATCH 02/17] working but wip --- ezFBsevenSeg.py | 138 ++++++++++++++++++++++++++++++++++++------------ sevenSegDemo.py | 14 +++-- 2 files changed, 115 insertions(+), 37 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 781f7df3..aeb9343e 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -9,25 +9,29 @@ ''' Copyright: ''' +from framebuf import FrameBuffer, MONO_HLSB +from math import ceil +from array import array + version = '0.33' name = '0.0.1' family = 'fixed' weight = 'medium' size = 32 -h = 32 # height -w = 16 # width -s = 0 # slant, todo! -c = True # cache +_high = 32 # height +_wide = 16 # width +_slant = 0 # slant (TODO!) +_cache = True # cached def height(): - return h + return _high def baseline(): - return h + return _high def max_width(): - return w + return _wide def hmap(): return True @@ -39,44 +43,110 @@ def monospaced(): return False def min_ch(): - return charmap.keys[0] + return _chars.keys[0] def max_ch(): - return return charmap.keys()[-1] + return _chars.keys()[-1] + +# segment polygons +# - indexed by an abbreviated name +# - values are an integer array of x,y pairs for use in framebuf.poly() +_segs = { + 'ul' : array('i', [1, 0, 2, 1, 13, 1, 14, 0]), # base, + 'ml' : array('i', [1, 13, 2, 14, 13, 14, 14, 13]), # middle + 'bl' : array('i', [1, 31, 2, 30, 13, 30, 14, 31]), # base + 'lu' : array('i', [0, 1, 0, 12, 1, 12, 1, 2]), # left upper + 'll' : array('i', [0, 14, 0, 30, 1, 29, 1, 15]), # left lower + 'ru' : array('i', [15, 1, 15, 12, 14, 12, 14, 2]), # right upper + 'rl' : array('i', [15, 14, 15, 30, 14, 29, 14, 15]), # right lower +} + +# character polygon lists +# - index is the integer character ord(), +# - value is a tuple with: +# (active segments(list), full(bool:False=halfwidth) +# - keep this list ordered or max_char() will be wrong. +_chars = { + 32 : ([],True), # space + 48 : (['ul','bl','lu','ll','ru','rl'],True), # 0 + 49 : (['ru','rl'],True), # 1 + 50 : (['ul','ml','bl','ll','ru'],True), # 2 + 51 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 3 + 52 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 4 + 53 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 5 + 54 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 6 + 55 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 7 + 56 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 8 + 59 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 9 + 65 : (['ul','ml','bl','lu','ll','ru','rl'],True), # A + 66 : (['ul','ml','bl','lu','ll','ru','rl'],True), # B + 67 : (['ul','ml','bl','lu','ll','ru','rl'],True), # C + 68 : (['ul','ml','bl','lu','ll','ru','rl'],True), # D + 69 : (['ul','ml','bl','lu','ll','ru','rl'],True), # E + 70 : (['ul','ml','bl','lu','ll','ru','rl'],True), # F +} -def set(high=None, wide=None, slanted=None, cached=None, pre=None) +# dictionary to hold cached chars +_g = {} + +def _clean_cache(): + global _g + _g = {} + +def _gen(ch): + # Generate a char using segment map. + # - returns false if char not available + ch = ord(ch) if type(ch) is str else ch + if ch not in _chars.keys(): + return False + print('rendering: ',ch) + _g[ch] = _render(*_chars[ch]) + +def _render(segs,iswide): + # Render the char using a framebuf + # returns a bytearray + # cache if needed + # if wide is False; do a half-width char. TODO + wide = _wide if iswide else ceil(_wide/2) + bytewide = ((wide - 1) // 8) + 1 + _buf = bytearray(_high * bytewide) + _canvas = FrameBuffer(_buf, wide, _high, MONO_HLSB) + for poly in segs: + _canvas.poly(0,0,_segs[poly],1,True) + return _buf + + +def set(height=None, width=None, slant=None, cached=None, pre=None): + # Always clean cache, then set/override defaults + # - Pre-cache any chars passed by 'pre' + global _high, _wide, _slant, _cache _clean_cache() # modify defaults as required - h = high if high is not None else h - wide = wide if wide is not None else w - slant = slanted if slant is not None else s - cahched = cached if cached is not None else c + _high = height if height is not None else _high + _wide = width if width is not None else _wide + _slant = slant if slant is not None else _slant + _cache = cached if cached is not None else _cache # constrain to value and type - h = int(max(5,h)) # integer, min = 5 - w = int(max(5,w)) # integer, min = 5 - s = int((max(-h,min(h,s))) # integer, max = (+/-)height - c = bool(cached) # bool + _high = int(max(5, _high)) # integer, min = 5 + _wide = int(max(5, _wide)) # integer, min = 5 + _slant = int(max(-_high, min(_high, _slant))) # integer, max = (+/-)height + _cache = bool(_cache) # bool # precache if pre is not None: - for ch in pre[0:-1]: + for ch in pre: _gen(ch) -def get(): - # returns height, width, slant, cache(bool) - # and the currently chached chars as a list - return w, h, s, c, cache.keys().sort() - -# segment polygons -segs = {} - -# character polygon lists -chars = {} - -# dictionary to hold cached chars -_g = {} +def info(): + # useful for debug; returns height, width, slant, + # cache active(bool),and any current chached chars as a list + c = list(_g.keys()) + c.sort() + return _wide, _high, _slant, _cache, c def get_ch(ch): c = ord(ch) - if c not in _g.keys(): + if c not in _chars.keys(): return None, 0, 0 - return memoryview(_g[c]), 26, 16 + if c not in _g.keys(): + _gen(ch) + return memoryview(_g[c]), _high, _wide # todo, half-wide.. diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 09d2b7b3..9532b6db 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,11 +7,19 @@ import ezFBsevenSeg as sevenSeg # Display -display = REPL_1306(128, 64, clear=True) +display = REPL_1306(100, 32, clear=False, blocks=False) display.invert(False) # as needed display.rotate(0) # as needed display.contrast(128) # as needed -bigtime = ezFBfont(display, sevenSeg, halign='center', valign='center') +bigtime = ezFBfont(display, sevenSeg, hgap=2) -sevenSeg.get() +print(sevenSeg.info()) + +sevenSeg.set(pre='8') + +print(sevenSeg.info()) + +bigtime.write('0123456789',0,0) + +display.show() \ No newline at end of file From 83473d22f2a428739df75ba8a547b3bf0847c0a7 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 7 Dec 2024 20:24:51 +0100 Subject: [PATCH 03/17] moving on - wip --- ezFBsevenSeg.py | 60 +++++++++++++++++++++++++++++++------------------ sevenSegDemo.py | 11 +++++---- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index aeb9343e..ac5b991e 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -52,13 +52,23 @@ def max_ch(): # - indexed by an abbreviated name # - values are an integer array of x,y pairs for use in framebuf.poly() _segs = { - 'ul' : array('i', [1, 0, 2, 1, 13, 1, 14, 0]), # base, - 'ml' : array('i', [1, 13, 2, 14, 13, 14, 14, 13]), # middle - 'bl' : array('i', [1, 31, 2, 30, 13, 30, 14, 31]), # base - 'lu' : array('i', [0, 1, 0, 12, 1, 12, 1, 2]), # left upper - 'll' : array('i', [0, 14, 0, 30, 1, 29, 1, 15]), # left lower - 'ru' : array('i', [15, 1, 15, 12, 14, 12, 14, 2]), # right upper - 'rl' : array('i', [15, 14, 15, 30, 14, 29, 14, 15]), # right lower + # conventional 7 segment + 'ul' : array('i', [2, 0, 3, 1, 12, 1, 13, 0]), # base, + 'ml' : array('i', [2, 14, 3, 15, 12, 15, 13, 14]), # middle + 'bl' : array('i', [2, 31, 3, 30, 11, 30, 13, 31]), # base + 'lu' : array('i', [1, 1, 1, 13, 2, 13, 2, 2]), # left upper + 'll' : array('i', [1, 15, 1, 30, 2, 29, 2, 16]), # left lower + 'ru' : array('i', [14, 1, 14, 13, 13, 13, 13, 2]), # right upper + 'rl' : array('i', [14, 15, 14, 30, 13, 29, 13, 16]), # right lower + # Special Symbols + 'dec' : array('i', [3, 30, 3, 31, 4, 31, 4, 30]), # decimal point + 'cou' : array('i', [3, 10, 3, 11, 4, 11, 4, 10]), # colon upper + 'col' : array('i', [3, 21, 3, 22, 4, 22, 4, 21]), # colon lower + 'min' : array('i', [1, 14, 1, 15, 6, 15, 6, 14]), # minus + 'pls' : array('i', [3, 12, 3, 17, 4, 17, 4, 12]), # plus (bar, goes with minus) + 'sel' : array('i', [1, 0, 1, 3, 2, 3, 2, 0]), # seconds left + 'ser' : array('i', [5, 0, 5, 3, 6, 3, 6, 0]), # seconds right + 'mns' : array('i', [3, 0, 3, 3, 4, 3, 4, 0]), # minutes } # character polygon lists @@ -68,22 +78,28 @@ def max_ch(): # - keep this list ordered or max_char() will be wrong. _chars = { 32 : ([],True), # space + 34 : (['sel','ser'],False), # " + 39 : (['mns'],False), # ' + 43 : (['min','pls'],False), # + + 45 : (['min'],False), # - + 46 : (['dec'],False), # . 48 : (['ul','bl','lu','ll','ru','rl'],True), # 0 49 : (['ru','rl'],True), # 1 - 50 : (['ul','ml','bl','ll','ru'],True), # 2 - 51 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 3 - 52 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 4 - 53 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 5 - 54 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 6 - 55 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 7 + 50 : (['ul','ml','bl','ll','ru'],True), # 2 + 51 : (['ul','ml','bl','ru','rl'],True), # 3 + 52 : (['ml','lu','ru','rl'],True), # 4 + 53 : (['ul','ml','bl','lu','rl'],True), # 5 + 54 : (['ul','ml','bl','lu','ll','rl'],True), # 6 + 55 : (['ul','ru','rl'],True), # 7 56 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 8 - 59 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 9 - 65 : (['ul','ml','bl','lu','ll','ru','rl'],True), # A - 66 : (['ul','ml','bl','lu','ll','ru','rl'],True), # B - 67 : (['ul','ml','bl','lu','ll','ru','rl'],True), # C - 68 : (['ul','ml','bl','lu','ll','ru','rl'],True), # D - 69 : (['ul','ml','bl','lu','ll','ru','rl'],True), # E - 70 : (['ul','ml','bl','lu','ll','ru','rl'],True), # F + 57 : (['ul','ml','bl','lu','ru','rl'],True), # 9 + 58 : (['cou','col'],False), # : + 65 : (['ul','ml','lu','ll','ru','rl'],True), # A + 66 : (['ml','bl','lu','ll','rl'],True), # B + 67 : (['ul','bl','lu','ll'],True), # C + 68 : (['ml','bl','ll','rl','ru'],True), # D + 69 : (['ul','ml','bl','lu','ll'],True), # E + 70 : (['ul','ml','lu','ll'],True), # F } # dictionary to hold cached chars @@ -99,7 +115,6 @@ def _gen(ch): ch = ord(ch) if type(ch) is str else ch if ch not in _chars.keys(): return False - print('rendering: ',ch) _g[ch] = _render(*_chars[ch]) def _render(segs,iswide): @@ -113,6 +128,7 @@ def _render(segs,iswide): _canvas = FrameBuffer(_buf, wide, _high, MONO_HLSB) for poly in segs: _canvas.poly(0,0,_segs[poly],1,True) + _buf.append(wide) return _buf @@ -149,4 +165,4 @@ def get_ch(ch): return None, 0, 0 if c not in _g.keys(): _gen(ch) - return memoryview(_g[c]), _high, _wide # todo, half-wide.. + return memoryview(_g[c]), _high, int(_g[c][-1]) diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 9532b6db..dff0665b 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,19 +7,18 @@ import ezFBsevenSeg as sevenSeg # Display -display = REPL_1306(100, 32, clear=False, blocks=False) +display = REPL_1306(160, 32, clear=False, blocks=False) display.invert(False) # as needed display.rotate(0) # as needed display.contrast(128) # as needed -bigtime = ezFBfont(display, sevenSeg, hgap=2) +bigtime = ezFBfont(display, sevenSeg) -print(sevenSeg.info()) - -sevenSeg.set(pre='8') +sevenSeg.set(pre='0123456789 ') print(sevenSeg.info()) -bigtime.write('0123456789',0,0) +bigtime.write('8-+.:\'"',0,0) +#bigtime.write('89ABCDEF',0,34) display.show() \ No newline at end of file From 694ec1ad0c349fd1afb0b3f6257b23a5157869b8 Mon Sep 17 00:00:00 2001 From: Owen Date: Sat, 7 Dec 2024 21:18:23 +0100 Subject: [PATCH 04/17] working at 32x16/8 fixed --- ezFBsevenSeg.py | 21 +++++++++++++++------ sevenSegDemo.py | 9 +++++---- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index ac5b991e..2e41d1d8 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -62,13 +62,20 @@ def max_ch(): 'rl' : array('i', [14, 15, 14, 30, 13, 29, 13, 16]), # right lower # Special Symbols 'dec' : array('i', [3, 30, 3, 31, 4, 31, 4, 30]), # decimal point - 'cou' : array('i', [3, 10, 3, 11, 4, 11, 4, 10]), # colon upper - 'col' : array('i', [3, 21, 3, 22, 4, 22, 4, 21]), # colon lower + 'cou' : array('i', [3, 8, 3, 9, 4, 9, 4, 8]), # colon upper + 'col' : array('i', [3, 20, 3, 21, 4, 21, 4, 20]), # colon lower 'min' : array('i', [1, 14, 1, 15, 6, 15, 6, 14]), # minus - 'pls' : array('i', [3, 12, 3, 17, 4, 17, 4, 12]), # plus (bar, goes with minus) - 'sel' : array('i', [1, 0, 1, 3, 2, 3, 2, 0]), # seconds left - 'ser' : array('i', [5, 0, 5, 3, 6, 3, 6, 0]), # seconds right - 'mns' : array('i', [3, 0, 3, 3, 4, 3, 4, 0]), # minutes + 'pls' : array('i', [3, 11, 3, 18, 4, 18, 4, 11]), # plus (bar, goes with minus) + 'sel' : array('i', [1, 0, 1, 5, 2, 5, 2, 0]), # seconds left + 'ser' : array('i', [5, 0, 5, 5, 6, 5, 6, 0]), # seconds right + 'mns' : array('i', [3, 0, 3, 5, 4, 5, 4, 0]), # minutes + 'dgl' : array('i', [1, 1, 1, 4, 2, 4, 2, 1]), # degrees left + 'dgr' : array('i', [5, 1, 5, 4, 6, 4, 6, 1]), # degrees right + 'dgu' : array('i', [2, 0, 2, 1, 5, 1, 5, 0]), # degrees upper + 'dgb' : array('i', [2, 4, 2, 5, 5, 5, 5, 4]), # degrees lower + 'pul' : array('i', [1, 0, 1, 1, 2, 1, 2, 0]), # percent upper left + 'plr' : array('i', [5, 4, 5, 5, 6, 5, 6, 4]), # percent lower right + 'psl' : array('i', [1, 4, 1, 5, 5, 1, 5, 0]), # percent slant } # character polygon lists @@ -79,6 +86,7 @@ def max_ch(): _chars = { 32 : ([],True), # space 34 : (['sel','ser'],False), # " + 37 : (['pul','plr','psl'],False), # % 39 : (['mns'],False), # ' 43 : (['min','pls'],False), # + 45 : (['min'],False), # - @@ -100,6 +108,7 @@ def max_ch(): 68 : (['ml','bl','ll','rl','ru'],True), # D 69 : (['ul','ml','bl','lu','ll'],True), # E 70 : (['ul','ml','lu','ll'],True), # F + 176: (['dgl','dgr','dgu','dgb'],False), # ° } # dictionary to hold cached chars diff --git a/sevenSegDemo.py b/sevenSegDemo.py index dff0665b..2420b655 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,7 +7,7 @@ import ezFBsevenSeg as sevenSeg # Display -display = REPL_1306(160, 32, clear=False, blocks=False) +display = REPL_1306(160, 102, clear=False, blocks=True) display.invert(False) # as needed display.rotate(0) # as needed display.contrast(128) # as needed @@ -18,7 +18,8 @@ print(sevenSeg.info()) -bigtime.write('8-+.:\'"',0,0) -#bigtime.write('89ABCDEF',0,34) +bigtime.write('-+.:\'"°8%)',0,0) +bigtime.write('01234567',0,34) +bigtime.write('89ABCDEF',0,68) -display.show() \ No newline at end of file +display.show() From 1e08cde1d33b760f6b64b10534ca5b7ebf4d99cc Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 10 Mar 2026 15:35:41 +0100 Subject: [PATCH 05/17] working , but badly --- ezFBsevenSeg.py | 114 +++++++++++++++++++++++++++++------------------- sevenSegDemo.py | 2 + 2 files changed, 72 insertions(+), 44 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 2e41d1d8..7682ad37 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -10,7 +10,7 @@ Copyright: ''' from framebuf import FrameBuffer, MONO_HLSB -from math import ceil +from math import ceil, floor from array import array version = '0.33' @@ -21,7 +21,6 @@ _high = 32 # height _wide = 16 # width -_slant = 0 # slant (TODO!) _cache = True # cached def height(): @@ -50,32 +49,32 @@ def max_ch(): # segment polygons # - indexed by an abbreviated name -# - values are an integer array of x,y pairs for use in framebuf.poly() +# - values are a list of x,y pairs between 0->1 that are scaled for use in framebuf.poly() _segs = { # conventional 7 segment - 'ul' : array('i', [2, 0, 3, 1, 12, 1, 13, 0]), # base, - 'ml' : array('i', [2, 14, 3, 15, 12, 15, 13, 14]), # middle - 'bl' : array('i', [2, 31, 3, 30, 11, 30, 13, 31]), # base - 'lu' : array('i', [1, 1, 1, 13, 2, 13, 2, 2]), # left upper - 'll' : array('i', [1, 15, 1, 30, 2, 29, 2, 16]), # left lower - 'ru' : array('i', [14, 1, 14, 13, 13, 13, 13, 2]), # right upper - 'rl' : array('i', [14, 15, 14, 30, 13, 29, 13, 16]), # right lower - # Special Symbols - 'dec' : array('i', [3, 30, 3, 31, 4, 31, 4, 30]), # decimal point - 'cou' : array('i', [3, 8, 3, 9, 4, 9, 4, 8]), # colon upper - 'col' : array('i', [3, 20, 3, 21, 4, 21, 4, 20]), # colon lower - 'min' : array('i', [1, 14, 1, 15, 6, 15, 6, 14]), # minus - 'pls' : array('i', [3, 11, 3, 18, 4, 18, 4, 11]), # plus (bar, goes with minus) - 'sel' : array('i', [1, 0, 1, 5, 2, 5, 2, 0]), # seconds left - 'ser' : array('i', [5, 0, 5, 5, 6, 5, 6, 0]), # seconds right - 'mns' : array('i', [3, 0, 3, 5, 4, 5, 4, 0]), # minutes - 'dgl' : array('i', [1, 1, 1, 4, 2, 4, 2, 1]), # degrees left - 'dgr' : array('i', [5, 1, 5, 4, 6, 4, 6, 1]), # degrees right - 'dgu' : array('i', [2, 0, 2, 1, 5, 1, 5, 0]), # degrees upper - 'dgb' : array('i', [2, 4, 2, 5, 5, 5, 5, 4]), # degrees lower - 'pul' : array('i', [1, 0, 1, 1, 2, 1, 2, 0]), # percent upper left - 'plr' : array('i', [5, 4, 5, 5, 6, 5, 6, 4]), # percent lower right - 'psl' : array('i', [1, 4, 1, 5, 5, 1, 5, 0]), # percent slant + 'ul' : [0.1333, 0, 0.2, 0.0323, 0.8, 0.0323, 0.8667, 0], # base, + 'ml' : [0.1333, 0.4516, 0.2, 0.4839, 0.8, 0.4839, 0.8667, 0.4516], # middle + 'bl' : [0.1333, 1, 0.2, 0.9677, 0.7333, 0.9677, 0.8667, 1], # base + 'lu' : [0.0667, 0.0323, 0.0667, 0.4194, 0.1333, 0.4194, 0.1333, 0.0645], # left upper + 'll' : [0.0667, 0.4839, 0.0667, 0.9677, 0.1333, 0.9355, 0.1333, 0.5161], # left lower + 'ru' : [0.9333, 0.0323, 0.9333, 0.4194, 0.8667, 0.4194, 0.8667, 0.0645], # right upper + 'rl' : [0.9333, 0.4839, 0.9333, 0.9677, 0.8667, 0.9355, 0.8667, 0.5161], # right lower + # Special Symbols - TODO:REDUCE + 'dec' : [0.4286, 0.9677, 0.4286, 1, 0.5714, 1, 0.5714, 0.9677], # decimal point + 'cou' : [0.4286, 0.2581, 0.4286, 0.2903, 0.5714, 0.2903, 0.5714, 0.2581], # colon upper + 'col' : [0.4286, 0.6452, 0.4286, 0.6774, 0.5714, 0.6774, 0.5714, 0.6452], # colon lower + 'min' : [0.1429, 0.4516, 0.1429, 0.4839, 0.8571, 0.4839, 0.8571, 0.4516], # minus + 'pls' : [0.4286, 0.3226, 0.4286, 0.6129, 0.5714, 0.6129, 0.5714, 0.3226], # plus (bar, goes with minus) + 'sel' : [0.1429, 0, 0.1429, 0.1613, 0.2857, 0.1613, 0.2857, 0], # seconds left + 'ser' : [0.7143, 0, 0.7143, 0.1613, 0.8571, 0.1613, 0.8571, 0], # seconds right + 'mns' : [0.4286, 0, 0.4286, 0.1613, 0.5714, 0.1613, 0.5714, 0], # minutes + 'dgl' : [0.1429, 0.0323, 0.1429, 0.129, 0.2857, 0.129, 0.2857, 0.0323], # degrees left + 'dgr' : [0.7143, 0.0323, 0.7143, 0.129, 0.8571, 0.129, 0.8571, 0.0323], # degrees right + 'dgu' : [0.2857, 0, 0.2857, 0.0323, 0.7143, 0.0323, 0.7143, 0], # degrees upper + 'dgb' : [0.2857, 0.129, 0.2857, 0.1613, 0.7143, 0.1613, 0.7143, 0.129], # degrees lower + 'pul' : [0.1429, 0, 0.1429, 0.0323, 0.2857, 0.0323, 0.2857, 0], # percent upper left + 'plr' : [0.7143, 0.1935, 0.7143, 0.2258, 0.8571, 0.2258, 0.8571, 0.1935], # percent lower right + 'psl' : [0.1429, 0.1935, 0.1429, 0.2258, 0.7143, 0.0323, 0.7143, 0], # percent slant } # character polygon lists @@ -84,7 +83,7 @@ def max_ch(): # (active segments(list), full(bool:False=halfwidth) # - keep this list ordered or max_char() will be wrong. _chars = { - 32 : ([],True), # space + 32 : ([],False), # space 34 : (['sel','ser'],False), # " 37 : (['pul','plr','psl'],False), # % 39 : (['mns'],False), # ' @@ -108,38 +107,60 @@ def max_ch(): 68 : (['ml','bl','ll','rl','ru'],True), # D 69 : (['ul','ml','bl','lu','ll'],True), # E 70 : (['ul','ml','lu','ll'],True), # F - 176: (['dgl','dgr','dgu','dgb'],False), # ° + 176 : (['dgl','dgr','dgu','dgb'],False), # ° } # dictionary to hold cached chars _g = {} +''' +def conv(): + for k in _segs.keys(): + new = array('i',[]) + if k in ['ul','ml','bl','lu','ll','ru','rl']: + ws = 1 / 15 + else: + ws = 1 / 7 + hs = 1 / 31 + print('{:>8} '.format("'{}'".format(k)), end=': [') + for i in range(0,len(_segs[k]),2): + x = round(_segs[k][i] * ws, 4) + y = round(_segs[k][i+1] * hs, 4) + print('{:g}, {:g}'.format(x,y), end=', ') + print(']') +''' + def _clean_cache(): global _g _g = {} - + def _gen(ch): - # Generate a char using segment map. + # Generate a char using segment map and adds # - returns false if char not available ch = ord(ch) if type(ch) is str else ch if ch not in _chars.keys(): - return False - _g[ch] = _render(*_chars[ch]) - -def _render(segs,iswide): + return None + return _render(*_chars[ch]) + +def _render(segments,iswide): # Render the char using a framebuf # returns a bytearray # cache if needed # if wide is False; do a half-width char. TODO wide = _wide if iswide else ceil(_wide/2) bytewide = ((wide - 1) // 8) + 1 - _buf = bytearray(_high * bytewide) - _canvas = FrameBuffer(_buf, wide, _high, MONO_HLSB) - for poly in segs: - _canvas.poly(0,0,_segs[poly],1,True) - _buf.append(wide) - return _buf - + buf = bytearray(_high * bytewide) + canvas = FrameBuffer(buf, wide, _high, MONO_HLSB) + for poly in segments: + pa = array('i',[]) + pd = _segs[poly] + for i in range(0,len(pd),2): + pa.append(round(pd[i] * wide)) + pa.append(round(pd[i+1] * _high)) + canvas.poly(0,0,pa,1,True) + buf.append(wide) + return buf + def set(height=None, width=None, slant=None, cached=None, pre=None): # Always clean cache, then set/override defaults @@ -159,7 +180,7 @@ def set(height=None, width=None, slant=None, cached=None, pre=None): # precache if pre is not None: for ch in pre: - _gen(ch) + _, _, _ = get_ch(ch) def info(): # useful for debug; returns height, width, slant, @@ -173,5 +194,10 @@ def get_ch(ch): if c not in _chars.keys(): return None, 0, 0 if c not in _g.keys(): - _gen(ch) - return memoryview(_g[c]), _high, int(_g[c][-1]) + buf = _gen(c) + if _cache: + _g[c] = buf + else: + buf = _g[c] + return memoryview(buf), _high, int(buf[-1]) + diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 2420b655..5240a0bd 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -3,6 +3,7 @@ from sys import path path.append('drivers') from repl_1306 import REPL_1306 +path.append('demo_extra') from ezFBfont import ezFBfont import ezFBsevenSeg as sevenSeg @@ -18,6 +19,7 @@ print(sevenSeg.info()) +#sevenSeg.conv() bigtime.write('-+.:\'"°8%)',0,0) bigtime.write('01234567',0,34) bigtime.write('89ABCDEF',0,68) From e6f9c4f954f59a9a0931c2132d78a9dc59db8c6a Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 10 Mar 2026 17:08:25 +0100 Subject: [PATCH 06/17] better, working --- ezFBsevenSeg.py | 188 ++++++++++++++++++++++-------------------------- sevenSegDemo.py | 4 +- 2 files changed, 87 insertions(+), 105 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 7682ad37..82c9add7 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -42,140 +42,122 @@ def monospaced(): return False def min_ch(): - return _chars.keys[0] + return min(_all_chars) def max_ch(): - return _chars.keys()[-1] - -# segment polygons -# - indexed by an abbreviated name -# - values are a list of x,y pairs between 0->1 that are scaled for use in framebuf.poly() -_segs = { - # conventional 7 segment - 'ul' : [0.1333, 0, 0.2, 0.0323, 0.8, 0.0323, 0.8667, 0], # base, - 'ml' : [0.1333, 0.4516, 0.2, 0.4839, 0.8, 0.4839, 0.8667, 0.4516], # middle - 'bl' : [0.1333, 1, 0.2, 0.9677, 0.7333, 0.9677, 0.8667, 1], # base - 'lu' : [0.0667, 0.0323, 0.0667, 0.4194, 0.1333, 0.4194, 0.1333, 0.0645], # left upper - 'll' : [0.0667, 0.4839, 0.0667, 0.9677, 0.1333, 0.9355, 0.1333, 0.5161], # left lower - 'ru' : [0.9333, 0.0323, 0.9333, 0.4194, 0.8667, 0.4194, 0.8667, 0.0645], # right upper - 'rl' : [0.9333, 0.4839, 0.9333, 0.9677, 0.8667, 0.9355, 0.8667, 0.5161], # right lower - # Special Symbols - TODO:REDUCE - 'dec' : [0.4286, 0.9677, 0.4286, 1, 0.5714, 1, 0.5714, 0.9677], # decimal point - 'cou' : [0.4286, 0.2581, 0.4286, 0.2903, 0.5714, 0.2903, 0.5714, 0.2581], # colon upper - 'col' : [0.4286, 0.6452, 0.4286, 0.6774, 0.5714, 0.6774, 0.5714, 0.6452], # colon lower - 'min' : [0.1429, 0.4516, 0.1429, 0.4839, 0.8571, 0.4839, 0.8571, 0.4516], # minus - 'pls' : [0.4286, 0.3226, 0.4286, 0.6129, 0.5714, 0.6129, 0.5714, 0.3226], # plus (bar, goes with minus) - 'sel' : [0.1429, 0, 0.1429, 0.1613, 0.2857, 0.1613, 0.2857, 0], # seconds left - 'ser' : [0.7143, 0, 0.7143, 0.1613, 0.8571, 0.1613, 0.8571, 0], # seconds right - 'mns' : [0.4286, 0, 0.4286, 0.1613, 0.5714, 0.1613, 0.5714, 0], # minutes - 'dgl' : [0.1429, 0.0323, 0.1429, 0.129, 0.2857, 0.129, 0.2857, 0.0323], # degrees left - 'dgr' : [0.7143, 0.0323, 0.7143, 0.129, 0.8571, 0.129, 0.8571, 0.0323], # degrees right - 'dgu' : [0.2857, 0, 0.2857, 0.0323, 0.7143, 0.0323, 0.7143, 0], # degrees upper - 'dgb' : [0.2857, 0.129, 0.2857, 0.1613, 0.7143, 0.1613, 0.7143, 0.129], # degrees lower - 'pul' : [0.1429, 0, 0.1429, 0.0323, 0.2857, 0.0323, 0.2857, 0], # percent upper left - 'plr' : [0.7143, 0.1935, 0.7143, 0.2258, 0.8571, 0.2258, 0.8571, 0.1935], # percent lower right - 'psl' : [0.1429, 0.1935, 0.1429, 0.2258, 0.7143, 0.0323, 0.7143, 0], # percent slant -} + return max(_all_chars) # character polygon lists # - index is the integer character ord(), # - value is a tuple with: -# (active segments(list), full(bool:False=halfwidth) -# - keep this list ordered or max_char() will be wrong. -_chars = { - 32 : ([],False), # space - 34 : (['sel','ser'],False), # " - 37 : (['pul','plr','psl'],False), # % - 39 : (['mns'],False), # ' - 43 : (['min','pls'],False), # + - 45 : (['min'],False), # - - 46 : (['dec'],False), # . - 48 : (['ul','bl','lu','ll','ru','rl'],True), # 0 - 49 : (['ru','rl'],True), # 1 - 50 : (['ul','ml','bl','ll','ru'],True), # 2 - 51 : (['ul','ml','bl','ru','rl'],True), # 3 - 52 : (['ml','lu','ru','rl'],True), # 4 - 53 : (['ul','ml','bl','lu','rl'],True), # 5 - 54 : (['ul','ml','bl','lu','ll','rl'],True), # 6 - 55 : (['ul','ru','rl'],True), # 7 - 56 : (['ul','ml','bl','lu','ll','ru','rl'],True), # 8 - 57 : (['ul','ml','bl','lu','ru','rl'],True), # 9 - 58 : (['cou','col'],False), # : - 65 : (['ul','ml','lu','ll','ru','rl'],True), # A - 66 : (['ml','bl','lu','ll','rl'],True), # B - 67 : (['ul','bl','lu','ll'],True), # C - 68 : (['ml','bl','ll','rl','ru'],True), # D - 69 : (['ul','ml','bl','lu','ll'],True), # E - 70 : (['ul','ml','lu','ll'],True), # F - 176 : (['dgl','dgr','dgu','dgb'],False), # ° +# - keep these list ordered or max_char() will be wrong. +_chars_full = { + 32 : [], # space + 45 : ['bm'], # negative: '-' + 48 : ['bt','bb','lu','ll','ru','rl'], # 0 + 49 : ['ru','rl'], # 1 + 50 : ['bt','bm','bb','ll','ru'], # 2 + 51 : ['bt','bm','bb','ru','rl'], # 3 + 52 : ['bm','lu','ru','rl'], # 4 + 53 : ['bt','bm','bb','lu','rl'], # 5 + 54 : ['bt','bm','bb','lu','ll','rl'], # 6 + 55 : ['bt','ru','rl'], # 7 + 56 : ['bt','bm','bb','lu','ll','ru','rl'], # 8 + 57 : ['bt','bm','bb','lu','ru','rl'], # 9 + 65 : ['bt','bm','lu','ll','ru','rl'], # A + 66 : ['bm','bb','lu','ll','rl'], # B + 67 : ['bt','bb','lu','ll'], # C + 68 : ['bm','bb','ll','rl','ru'], # D + 69 : ['bt','bm','bb','lu','ll'], # E + 70 : ['bt','bm','lu','ll'], # F +} + +_chars_half = { + 46 : ['de'], # decimal point: '.' + 58 : ['cu','cl'], # semicolon: ':' + 8201 : [], # thin space: unicode u+2009 } +_all_chars = list(_chars_full.keys()) + list(_chars_half.keys()) + # dictionary to hold cached chars _g = {} -''' -def conv(): - for k in _segs.keys(): - new = array('i',[]) - if k in ['ul','ml','bl','lu','ll','ru','rl']: - ws = 1 / 15 - else: - ws = 1 / 7 - hs = 1 / 31 - print('{:>8} '.format("'{}'".format(k)), end=': [') - for i in range(0,len(_segs[k]),2): - x = round(_segs[k][i] * ws, 4) - y = round(_segs[k][i+1] * hs, 4) - print('{:g}, {:g}'.format(x,y), end=', ') - print(']') -''' - def _clean_cache(): global _g _g = {} def _gen(ch): - # Generate a char using segment map and adds - # - returns false if char not available - ch = ord(ch) if type(ch) is str else ch - if ch not in _chars.keys(): - return None - return _render(*_chars[ch]) - -def _render(segments,iswide): + # Generate a char using segment map + if ch in _chars_half.keys(): + return _render_half(_chars_half[ch]) + else: + return _render_full(_chars_full[ch]) + +def _render_full(segments): # Render the char using a framebuf # returns a bytearray - # cache if needed - # if wide is False; do a half-width char. TODO - wide = _wide if iswide else ceil(_wide/2) + bytewide = ((_wide - 1) // 8) + 1 + buf = bytearray(_high * bytewide) + canvas = FrameBuffer(buf, _wide, _high, MONO_HLSB) + _draw_full(canvas, _wide, _high, segments, thick=2, gap=1) + buf.append(_wide) + return buf + +def _render_half(segments): + # Render the char using a framebuf + # returns a bytearray + wide = ceil(_wide/2) bytewide = ((wide - 1) // 8) + 1 buf = bytearray(_high * bytewide) canvas = FrameBuffer(buf, wide, _high, MONO_HLSB) - for poly in segments: - pa = array('i',[]) - pd = _segs[poly] - for i in range(0,len(pd),2): - pa.append(round(pd[i] * wide)) - pa.append(round(pd[i+1] * _high)) - canvas.poly(0,0,pa,1,True) + _draw_full(canvas, wide, _high, segments, thick=2, gap=1) buf.append(wide) return buf - -def set(height=None, width=None, slant=None, cached=None, pre=None): +def _draw_full(canvas, X, Y, elements, thick, gap): + # Main body bars + M = int(Y/2) + for l in range(thick): + if 'bt' in elements: + canvas.hline(l, l, X-(2*l), 1) + if 'lu' in elements: + canvas.vline(l, l, M-(2*l), 1) + if 'ru' in elements: + canvas.vline(X-l-1, l, M-(2*l), 1) + if 'bm' in elements: + canvas.hline(l, M+l, X-(2*l), 1) + if 'll' in elements: + canvas.vline(l, M+l, M-(2*l), 1) + if 'rl' in elements: + canvas.vline(X-l-1, M+l, M-(2*l), 1) + if 'bb' in elements: + canvas.hline(l, Y-l-1, X-(2*l), 1) + # Now create gaps between them + for l in range(gap): + f = (l+1)//2 + (x, y) = (f, 0) if l % 2 else (0, f) + canvas.line(x, y, thick+x, thick+y, 0) + canvas.line(X - x-1, y, X-thick-x-1, thick+y, 0) + canvas.line(x, M+y, thick+x, M+thick + y, 0) + canvas.line(X-x-1, M+y, X-thick-x-1, M + thick+y, 0) + canvas.line(x, Y-y-1, thick+x, Y-thick-y - 1, 0) + canvas.line(X-x-1, Y-y-1, X-thick-x-1, Y-thick-y-1, 0) + + +# NEEDS HEAVY RE_WRITE +def set(height=None, width=None, thick=None, gap=None, cached=None, pre=None): # Always clean cache, then set/override defaults # - Pre-cache any chars passed by 'pre' - global _high, _wide, _slant, _cache + global _high, _wide, _cache _clean_cache() # modify defaults as required _high = height if height is not None else _high _wide = width if width is not None else _wide - _slant = slant if slant is not None else _slant _cache = cached if cached is not None else _cache # constrain to value and type _high = int(max(5, _high)) # integer, min = 5 _wide = int(max(5, _wide)) # integer, min = 5 - _slant = int(max(-_high, min(_high, _slant))) # integer, max = (+/-)height _cache = bool(_cache) # bool # precache if pre is not None: @@ -183,15 +165,15 @@ def set(height=None, width=None, slant=None, cached=None, pre=None): _, _, _ = get_ch(ch) def info(): - # useful for debug; returns height, width, slant, + # useful for debug; returns height, width # cache active(bool),and any current chached chars as a list c = list(_g.keys()) c.sort() - return _wide, _high, _slant, _cache, c + return _wide, _high, _cache, c def get_ch(ch): c = ord(ch) - if c not in _chars.keys(): + if c not in _all_chars: return None, 0, 0 if c not in _g.keys(): buf = _gen(c) diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 5240a0bd..d93ab231 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -15,12 +15,12 @@ bigtime = ezFBfont(display, sevenSeg) -sevenSeg.set(pre='0123456789 ') +sevenSeg.set() print(sevenSeg.info()) #sevenSeg.conv() -bigtime.write('-+.:\'"°8%)',0,0) +bigtime.write('-.:',0,0) bigtime.write('01234567',0,34) bigtime.write('89ABCDEF',0,68) From f227962b302da22d1ab15ca95fe933cc87924d66 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 10 Mar 2026 17:33:08 +0100 Subject: [PATCH 07/17] cleanup --- ezFBsevenSeg.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 82c9add7..02651ed4 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -10,8 +10,7 @@ Copyright: ''' from framebuf import FrameBuffer, MONO_HLSB -from math import ceil, floor -from array import array +from math import ceil version = '0.33' name = '0.0.1' From 0bdc81d5b130d54889de08b166aceece7545f36f Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 10 Mar 2026 21:42:03 +0100 Subject: [PATCH 08/17] better, still working --- ezFBsevenSeg.py | 28 +++++++++++++++++++++++----- sevenSegDemo.py | 8 ++++---- 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 02651ed4..a7e47f0d 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -7,7 +7,15 @@ font-to-py tool: https://github.com/peterhinch/micropython-font-to-py ''' ''' - Copyright: +MIT License + +Copyright (c) 2026 Owen Carter + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' from framebuf import FrameBuffer, MONO_HLSB from math import ceil @@ -46,10 +54,8 @@ def min_ch(): def max_ch(): return max(_all_chars) -# character polygon lists +# character element lists # - index is the integer character ord(), -# - value is a tuple with: -# - keep these list ordered or max_char() will be wrong. _chars_full = { 32 : [], # space 45 : ['bm'], # negative: '-' @@ -110,7 +116,7 @@ def _render_half(segments): bytewide = ((wide - 1) // 8) + 1 buf = bytearray(_high * bytewide) canvas = FrameBuffer(buf, wide, _high, MONO_HLSB) - _draw_full(canvas, wide, _high, segments, thick=2, gap=1) + _draw_half(canvas, wide, _high, segments, thick=2) buf.append(wide) return buf @@ -143,6 +149,18 @@ def _draw_full(canvas, X, Y, elements, thick, gap): canvas.line(x, Y-y-1, thick+x, Y-thick-y - 1, 0) canvas.line(X-x-1, Y-y-1, X-thick-x-1, Y-thick-y-1, 0) +def _draw_half(canvas, X, Y, elements, thick): + U = int(Y*0.35) + L = int(Y*0.68) + C = int(X/2) + i = int(thick/2) + if 'de' in elements: + canvas.rect(C-i, Y-thick, thick, thick, 1, True) + if 'cu' in elements: + canvas.rect(C-i, U-i, thick, thick, 1, True) + if 'cl' in elements: + canvas.rect(C-i, L-i, thick, thick, 1, True) + # NEEDS HEAVY RE_WRITE def set(height=None, width=None, thick=None, gap=None, cached=None, pre=None): diff --git a/sevenSegDemo.py b/sevenSegDemo.py index d93ab231..1f0c3016 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -8,20 +8,20 @@ import ezFBsevenSeg as sevenSeg # Display -display = REPL_1306(160, 102, clear=False, blocks=True) +display = REPL_1306(132, 102, clear=False, blocks=True) display.invert(False) # as needed display.rotate(0) # as needed display.contrast(128) # as needed bigtime = ezFBfont(display, sevenSeg) -sevenSeg.set() +sevenSeg.set(pre='0123') print(sevenSeg.info()) #sevenSeg.conv() bigtime.write('-.:',0,0) -bigtime.write('01234567',0,34) -bigtime.write('89ABCDEF',0,68) +bigtime.write('01234567\n89ABCDEF',0,34) display.show() +print(sevenSeg.info()) From db0d7fa31f6b26c69f247197b70a770a9f4a447c Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Mar 2026 18:23:12 +0100 Subject: [PATCH 09/17] convert to class, some refactoring --- ezFBsevenSeg.py | 324 ++++++++++++++++++++++++++---------------------- 1 file changed, 173 insertions(+), 151 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index a7e47f0d..91b92b28 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -20,43 +20,10 @@ from framebuf import FrameBuffer, MONO_HLSB from math import ceil -version = '0.33' -name = '0.0.1' -family = 'fixed' -weight = 'medium' -size = 32 - -_high = 32 # height -_wide = 16 # width -_cache = True # cached - -def height(): - return _high - -def baseline(): - return _high - -def max_width(): - return _wide - -def hmap(): - return True - -def reverse(): - return False - -def monospaced(): - return False - -def min_ch(): - return min(_all_chars) - -def max_ch(): - return max(_all_chars) # character element lists # - index is the integer character ord(), -_chars_full = { +_CHARS_FULL = { 32 : [], # space 45 : ['bm'], # negative: '-' 48 : ['bt','bb','lu','ll','ru','rl'], # 0 @@ -77,126 +44,181 @@ def max_ch(): 70 : ['bt','bm','lu','ll'], # F } -_chars_half = { +_CHARS_HALF = { 46 : ['de'], # decimal point: '.' 58 : ['cu','cl'], # semicolon: ':' + 183 : ['dm'], # middle dot: unicode u+00B7 + 729 : ['da'], # dot above: unicode u+02D9 8201 : [], # thin space: unicode u+2009 } -_all_chars = list(_chars_full.keys()) + list(_chars_half.keys()) - -# dictionary to hold cached chars -_g = {} - -def _clean_cache(): - global _g - _g = {} - -def _gen(ch): - # Generate a char using segment map - if ch in _chars_half.keys(): - return _render_half(_chars_half[ch]) - else: - return _render_full(_chars_full[ch]) - -def _render_full(segments): - # Render the char using a framebuf - # returns a bytearray - bytewide = ((_wide - 1) // 8) + 1 - buf = bytearray(_high * bytewide) - canvas = FrameBuffer(buf, _wide, _high, MONO_HLSB) - _draw_full(canvas, _wide, _high, segments, thick=2, gap=1) - buf.append(_wide) - return buf - -def _render_half(segments): - # Render the char using a framebuf - # returns a bytearray - wide = ceil(_wide/2) - bytewide = ((wide - 1) // 8) + 1 - buf = bytearray(_high * bytewide) - canvas = FrameBuffer(buf, wide, _high, MONO_HLSB) - _draw_half(canvas, wide, _high, segments, thick=2) - buf.append(wide) - return buf - -def _draw_full(canvas, X, Y, elements, thick, gap): - # Main body bars - M = int(Y/2) - for l in range(thick): - if 'bt' in elements: - canvas.hline(l, l, X-(2*l), 1) - if 'lu' in elements: - canvas.vline(l, l, M-(2*l), 1) - if 'ru' in elements: - canvas.vline(X-l-1, l, M-(2*l), 1) - if 'bm' in elements: - canvas.hline(l, M+l, X-(2*l), 1) - if 'll' in elements: - canvas.vline(l, M+l, M-(2*l), 1) - if 'rl' in elements: - canvas.vline(X-l-1, M+l, M-(2*l), 1) - if 'bb' in elements: - canvas.hline(l, Y-l-1, X-(2*l), 1) - # Now create gaps between them - for l in range(gap): - f = (l+1)//2 - (x, y) = (f, 0) if l % 2 else (0, f) - canvas.line(x, y, thick+x, thick+y, 0) - canvas.line(X - x-1, y, X-thick-x-1, thick+y, 0) - canvas.line(x, M+y, thick+x, M+thick + y, 0) - canvas.line(X-x-1, M+y, X-thick-x-1, M + thick+y, 0) - canvas.line(x, Y-y-1, thick+x, Y-thick-y - 1, 0) - canvas.line(X-x-1, Y-y-1, X-thick-x-1, Y-thick-y-1, 0) - -def _draw_half(canvas, X, Y, elements, thick): - U = int(Y*0.35) - L = int(Y*0.68) - C = int(X/2) - i = int(thick/2) - if 'de' in elements: - canvas.rect(C-i, Y-thick, thick, thick, 1, True) - if 'cu' in elements: - canvas.rect(C-i, U-i, thick, thick, 1, True) - if 'cl' in elements: - canvas.rect(C-i, L-i, thick, thick, 1, True) - - -# NEEDS HEAVY RE_WRITE -def set(height=None, width=None, thick=None, gap=None, cached=None, pre=None): - # Always clean cache, then set/override defaults - # - Pre-cache any chars passed by 'pre' - global _high, _wide, _cache - _clean_cache() - # modify defaults as required - _high = height if height is not None else _high - _wide = width if width is not None else _wide - _cache = cached if cached is not None else _cache - # constrain to value and type - _high = int(max(5, _high)) # integer, min = 5 - _wide = int(max(5, _wide)) # integer, min = 5 - _cache = bool(_cache) # bool - # precache - if pre is not None: - for ch in pre: - _, _, _ = get_ch(ch) - -def info(): - # useful for debug; returns height, width - # cache active(bool),and any current chached chars as a list - c = list(_g.keys()) - c.sort() - return _wide, _high, _cache, c - -def get_ch(ch): - c = ord(ch) - if c not in _all_chars: - return None, 0, 0 - if c not in _g.keys(): - buf = _gen(c) - if _cache: - _g[c] = buf - else: - buf = _g[c] - return memoryview(buf), _high, int(buf[-1]) +_ALL_CHARS = list(_CHARS_FULL.keys()) + list(_CHARS_HALF.keys()) + +class SEVEN_SEG: + version = '0.33' + name = 'seven_segment' + family = 'fixed' + weight = 'medium' + size = 32 # default + + def __init__(self): + self._high = 32 # height + self._wide = 16 # width + self._thick = 2 + self._gap = 1 + self._use_cache = True # cached + self._cache = {} + self.__name__ = '{}-{}x{}'.format(self.__module__, + self._wide, + self._high) + + ''' + Standard methods for a micropython font + ''' + def height(self): + return self._high + + def baseline(self): + return self._high + + def max_width(self): + return self._wide + + def hmap(self): + return True + + def reverse(self): + return False + + def monospaced(self): + return False + + def min_ch(self): + return min(_ALL_CHARS) + + def max_ch(self): + return max(_ALL_CHARS) + + ''' + Internal methods specific to the Seven Segment font + ''' + def _gen(self, ch): + # Generate a char using segment map + if ch in _CHARS_FULL.keys(): + return self._render_full(_CHARS_FULL[ch]) + else: + return self._render_half(_CHARS_HALF[ch]) + + def _render_full(self, segments): + # Render the char using a framebuf, returns a bytearray + bytewide = ((self._wide - 1) // 8) + 1 + buf = bytearray(self._high * bytewide) + canvas = FrameBuffer(buf, self._wide, self._high, MONO_HLSB) + self._draw_full(canvas, self._wide, self._high, self._thick, self._gap, segments) + buf.append(self._wide) + return buf + + def _render_half(self, segments): + # Render the char using a half-width framebuf, returns a bytearray + wide = ceil(self._wide/2) + bytewide = ((wide - 1) // 8) + 1 + buf = bytearray(self._high * bytewide) + canvas = FrameBuffer(buf, wide, self._high, MONO_HLSB) + self._draw_half(canvas, wide, self._high, self._thick, segments) + buf.append(wide) + return buf + + def _draw_full(self, canvas, X, Y, T, G, elements): + # Main body bars + M = int(Y/2) + for l in range(T): + if 'bt' in elements: + canvas.hline(l, l, X-(2*l), 1) + if 'lu' in elements: + canvas.vline(l, l, M-(2*l), 1) + if 'ru' in elements: + canvas.vline(X-l-1, l, M-(2*l), 1) + if 'bm' in elements: + canvas.hline(l, M+l, X-(2*l), 1) + if 'll' in elements: + canvas.vline(l, M+l, M-(2*l), 1) + if 'rl' in elements: + canvas.vline(X-l-1, M+l, M-(2*l), 1) + if 'bb' in elements: + canvas.hline(l, Y-l-1, X-(2*l), 1) + # Now create gaps between them + for l in range(G): + f = (l+1)//2 + (x, y) = (f, 0) if l % 2 else (0, f) + canvas.line(x, y, T + x, T + y, 0) + canvas.line(X - x - 1, y, X - T - x - 1, T + y, 0) + canvas.line(x, M + y, T + x, M + T + y, 0) + canvas.line(X - x - 1, M + y, X - T - x - 1, M + T + y, 0) + canvas.line(x, Y - y - 1, T + x, Y - T - y - 1, 0) + canvas.line(X - x - 1, Y - y - 1, X - T - x - 1, Y - T - y - 1, 0) + + def _draw_half(self, canvas, X, Y, T, elements): + U = int(Y*0.35) + M = int(Y/2) + L = int(Y*0.68) + C = int(X/2) + i = int(T/2) + if 'de' in elements: + canvas.rect(C-i, Y-T, T, T, 1, True) + if 'dm' in elements: + canvas.rect(C-i, M-i, T, T, 1, True) + if 'da' in elements: + canvas.rect(C-i, 0, T, T, 1, True) + if 'cu' in elements: + canvas.rect(C-i, U-i, T, T, 1, True) + if 'cl' in elements: + canvas.rect(C-i, L-i, T, T, 1, True) + + ''' + (re)Set the seven segment font parameters + - call with no parameters to clear the cache + ''' + + # NEEDS HEAVY RE_WRITE + def set(self, height=None, width=None, box_X=None, box_y=None, thick=None, gap=None, cached=None, pre=None): + # Always clean cache + self._cache = {} + # modify defaults as required + self._high = height if height is not None else self._high + self._wide = width if width is not None else self._wide + self._thick = thick if thick is not None else self._thick + self._gap = gap if gap is not None else self._gap + self._use_cache = cached if cached is not None else self._use_cache + + # constrain to value and type (todo: fixup values sensibly) + self._high = int(max(5, self._high)) # integer, min = 5 + self._wide = int(max(5, self._wide)) # integer, min = 5 + # Pre-cache any chars passed by 'pre' + if pre is not None: + for ch in pre: + _, _, _ = self.get_ch(ch) + self._use_cache = bool(self._use_cache) # bool + + # DEBUG: remove this later.. + def info(self): + # useful for debug; returns height, width + # cache active(bool),and any current chached chars as a list + c = list(self._cache.keys()) + c.sort() + return self._wide, self._high, self._thick, self._gap, self._use_cache, c + + ''' + get_ch() returns the glyph data + ''' + def get_ch(self, ch): + c = ord(ch) + if c not in _ALL_CHARS: + return None, 0, 0 + if c not in self._cache.keys(): + buf = self._gen(c) + if self._use_cache: + self._cache[c] = buf + else: + buf = self._cache[c] + return memoryview(buf), self._high, int(buf[-1]) From 48bad97f37c6de7e37317645b5c40c22c9b9e05b Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Mar 2026 18:23:34 +0100 Subject: [PATCH 10/17] update demo to suit new class --- sevenSegDemo.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 1f0c3016..309940fa 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -5,23 +5,26 @@ from repl_1306 import REPL_1306 path.append('demo_extra') from ezFBfont import ezFBfont -import ezFBsevenSeg as sevenSeg +from ezFBsevenSeg import SEVEN_SEG # Display -display = REPL_1306(132, 102, clear=False, blocks=True) +display = REPL_1306(100, 64, clear=False, blocks=True) display.invert(False) # as needed display.rotate(0) # as needed display.contrast(128) # as needed -bigtime = ezFBfont(display, sevenSeg) +# Create a font instance +bigtimefont = SEVEN_SEG() -sevenSeg.set(pre='0123') +# Set it up +bigtimefont.set(height=58, width=32, thick=5, gap=3) -print(sevenSeg.info()) +# Now create a font writer +bigtime = ezFBfont(display, bigtimefont) -#sevenSeg.conv() -bigtime.write('-.:',0,0) -bigtime.write('01234567\n89ABCDEF',0,34) +#bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF',0,0) +bigtime.write('8:2',0,0) display.show() -print(sevenSeg.info()) +print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') +print(bigtimefont.info()) From 3d1e27d7fbbe8e5549f47df412be06cd2b5e595f Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 11 Mar 2026 23:44:42 +0100 Subject: [PATCH 11/17] led size independent frpom char box --- ezFBsevenSeg.py | 124 ++++++++++++++++++++++++++---------------------- sevenSegDemo.py | 18 +++---- 2 files changed, 76 insertions(+), 66 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 91b92b28..df8de3b2 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -55,53 +55,27 @@ _ALL_CHARS = list(_CHARS_FULL.keys()) + list(_CHARS_HALF.keys()) class SEVEN_SEG: - version = '0.33' + # Some (faked) standard fields for mPY fonts + version = '1.0' name = 'seven_segment' - family = 'fixed' + family = 'lcd' weight = 'medium' size = 32 # default - def __init__(self): - self._high = 32 # height - self._wide = 16 # width - self._thick = 2 - self._gap = 1 - self._use_cache = True # cached - self._cache = {} - self.__name__ = '{}-{}x{}'.format(self.__module__, - self._wide, - self._high) - - ''' - Standard methods for a micropython font - ''' - def height(self): - return self._high - - def baseline(self): - return self._high - - def max_width(self): - return self._wide - - def hmap(self): - return True - - def reverse(self): - return False - - def monospaced(self): - return False - - def min_ch(self): - return min(_ALL_CHARS) - - def max_ch(self): - return max(_ALL_CHARS) + def __init__(self, height, width, + led_high=None, led_wide=None, + led_thick=3, led_gap=3, + use_cache=True, precache=None): + self.__name__ = '{}-{}x{}'.format(self.__module__, height, width) + # Sensible (?) defaults + led_high = int(height * 0.8) if led_high is None else led_high + led_wide = int(width * 0.8) if led_wide is None else led_wide + self.set(height, width, led_high, led_wide, led_thick, led_gap, use_cache, precache) ''' Internal methods specific to the Seven Segment font ''' + def _gen(self, ch): # Generate a char using segment map if ch in _CHARS_FULL.keys(): @@ -114,22 +88,26 @@ def _render_full(self, segments): bytewide = ((self._wide - 1) // 8) + 1 buf = bytearray(self._high * bytewide) canvas = FrameBuffer(buf, self._wide, self._high, MONO_HLSB) - self._draw_full(canvas, self._wide, self._high, self._thick, self._gap, segments) + self._draw_full(canvas, self._led_wide, self._led_high, self._led_thick, self._led_gap, segments) buf.append(self._wide) return buf def _render_half(self, segments): # Render the char using a half-width framebuf, returns a bytearray wide = ceil(self._wide/2) + led_wide = ceil(self._led_wide/2) bytewide = ((wide - 1) // 8) + 1 buf = bytearray(self._high * bytewide) canvas = FrameBuffer(buf, wide, self._high, MONO_HLSB) - self._draw_half(canvas, wide, self._high, self._thick, segments) + self._draw_half(canvas, led_wide, self._led_high, self._led_thick, segments) buf.append(wide) return buf def _draw_full(self, canvas, X, Y, T, G, elements): - # Main body bars + ''' + Draw a Full-Width character using the 7 led segments + ''' + # Draw the required bars in full (intersecting) M = int(Y/2) for l in range(T): if 'bt' in elements: @@ -146,7 +124,7 @@ def _draw_full(self, canvas, X, Y, T, G, elements): canvas.vline(X-l-1, M+l, M-(2*l), 1) if 'bb' in elements: canvas.hline(l, Y-l-1, X-(2*l), 1) - # Now create gaps between them + # Now create the gaps that seperate them for l in range(G): f = (l+1)//2 (x, y) = (f, 0) if l % 2 else (0, f) @@ -175,29 +153,61 @@ def _draw_half(self, canvas, X, Y, T, elements): canvas.rect(C-i, L-i, T, T, 1, True) ''' - (re)Set the seven segment font parameters - - call with no parameters to clear the cache + Standard public methods for a micropython font ''' + def height(self): + return self._high + + def baseline(self): + return self._high + + def max_width(self): + return self._wide + + def hmap(self): + return True + + def reverse(self): + return False + + def monospaced(self): + return False + + def min_ch(self): + return min(_ALL_CHARS) + + def max_ch(self): + return max(_ALL_CHARS) + # NEEDS HEAVY RE_WRITE - def set(self, height=None, width=None, box_X=None, box_y=None, thick=None, gap=None, cached=None, pre=None): + def set(self, height=None, width=None, + led_high=None, led_wide=None, + led_thick=None, led_gap=None, + cached=None, precache=None): + ''' + Set the font parameters + - See init() for description. + - All parameters are optional + - Always clears the cache + ''' # Always clean cache self._cache = {} - # modify defaults as required + # constrain + # TODO + # set new value as necesscary self._high = height if height is not None else self._high self._wide = width if width is not None else self._wide - self._thick = thick if thick is not None else self._thick - self._gap = gap if gap is not None else self._gap + self._led_high = led_high if led_high is not None else self._led_high + self._led_wide = led_wide if led_wide is not None else self._led_wide + self._led_thick = led_thick if led_thick is not None else self._led_thick + self._led_gap = led_gap if led_gap is not None else self._led_gap self._use_cache = cached if cached is not None else self._use_cache - - # constrain to value and type (todo: fixup values sensibly) - self._high = int(max(5, self._high)) # integer, min = 5 - self._wide = int(max(5, self._wide)) # integer, min = 5 # Pre-cache any chars passed by 'pre' - if pre is not None: - for ch in pre: + # - this works even if caching is disabled + if precache is not None: + for ch in precache: _, _, _ = self.get_ch(ch) - self._use_cache = bool(self._use_cache) # bool # DEBUG: remove this later.. def info(self): @@ -205,7 +215,7 @@ def info(self): # cache active(bool),and any current chached chars as a list c = list(self._cache.keys()) c.sort() - return self._wide, self._high, self._thick, self._gap, self._use_cache, c + return self._wide, self._high, self._led_thick, self._led_gap, self._use_cache, c ''' get_ch() returns the glyph data diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 309940fa..67d1b590 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,23 +7,23 @@ from ezFBfont import ezFBfont from ezFBsevenSeg import SEVEN_SEG +X = 20 +Y = 32 + # Display -display = REPL_1306(100, 64, clear=False, blocks=True) -display.invert(False) # as needed -display.rotate(0) # as needed -display.contrast(128) # as needed +display = REPL_1306(X * 7, Y * 3, clear=False, blocks=True) # Create a font instance -bigtimefont = SEVEN_SEG() +bigtimefont = SEVEN_SEG(height = Y, width = X) -# Set it up -bigtimefont.set(height=58, width=32, thick=5, gap=3) +# Set font up +bigtimefont.set(led_high=None, led_wide=None, led_thick=None, led_gap=None) # Now create a font writer -bigtime = ezFBfont(display, bigtimefont) +bigtime = ezFBfont(display, bigtimefont, fg=1, bg=0) #bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF',0,0) -bigtime.write('8:2',0,0) +bigtime.write('08:34\n56.79\u00B721\n ABCDEF',0,0) display.show() print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') From 6f7ba9e2b3cb0b4df3227cd7b108928aec45ebc6 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 12 Mar 2026 21:47:17 +0100 Subject: [PATCH 12/17] auto-centering, working --- ezFBsevenSeg.py | 12 +++++++++++- sevenSegDemo.py | 17 +++++++++-------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index df8de3b2..1b08ace9 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -18,7 +18,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' from framebuf import FrameBuffer, MONO_HLSB -from math import ceil +from math import ceil, floor # character element lists @@ -89,6 +89,9 @@ def _render_full(self, segments): buf = bytearray(self._high * bytewide) canvas = FrameBuffer(buf, self._wide, self._high, MONO_HLSB) self._draw_full(canvas, self._led_wide, self._led_high, self._led_thick, self._led_gap, segments) + canvas.scroll(self._addx, self._addy) + canvas.rect(0, 0, self._wide, self._addy, 0, True) + canvas.rect(0, 0, self._addx, self._high, 0, True) buf.append(self._wide) return buf @@ -100,6 +103,10 @@ def _render_half(self, segments): buf = bytearray(self._high * bytewide) canvas = FrameBuffer(buf, wide, self._high, MONO_HLSB) self._draw_half(canvas, led_wide, self._led_high, self._led_thick, segments) + addx = int(self._addx/2) + canvas.scroll(addx, self._addy) + canvas.rect(0, 0, wide, self._addy, 0, True) + canvas.rect(0, 0, addx, self._high, 0, True) buf.append(wide) return buf @@ -203,6 +210,9 @@ def set(self, height=None, width=None, self._led_thick = led_thick if led_thick is not None else self._led_thick self._led_gap = led_gap if led_gap is not None else self._led_gap self._use_cache = cached if cached is not None else self._use_cache + # Calculate led offset within char + self._addx = floor((self._wide - self._led_wide) / 2) + self._addy = floor((self._high - self._led_high) / 2) # Pre-cache any chars passed by 'pre' # - this works even if caching is disabled if precache is not None: diff --git a/sevenSegDemo.py b/sevenSegDemo.py index 67d1b590..a8a82a5e 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,24 +7,25 @@ from ezFBfont import ezFBfont from ezFBsevenSeg import SEVEN_SEG -X = 20 -Y = 32 +X = 24 +Y = 36 # Display -display = REPL_1306(X * 7, Y * 3, clear=False, blocks=True) +display = REPL_1306(X * 5, Y * 1, clear=False, blocks=True) # Create a font instance -bigtimefont = SEVEN_SEG(height = Y, width = X) +bigtimefont = SEVEN_SEG(height = Y, width = X, led_thick = 3) # Set font up -bigtimefont.set(led_high=None, led_wide=None, led_thick=None, led_gap=None) +bigtimefont.set(led_high=32, led_thick=4, led_gap=2) # Now create a font writer -bigtime = ezFBfont(display, bigtimefont, fg=1, bg=0) +bigtime = ezFBfont(display, bigtimefont, hgap=1, vgap=1, fg=0, bg=1) #bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF',0,0) -bigtime.write('08:34\n56.79\u00B721\n ABCDEF',0,0) +#bigtime.write('08:34\n56.79\u00B721\n ABCDEF',0,0) +bigtime.write('08:34',0,0) display.show() -print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') +#print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') print(bigtimefont.info()) From 188dd67e8dde1ff2d1e65cea8ef7878d038ba465 Mon Sep 17 00:00:00 2001 From: Owen Date: Fri, 13 Mar 2026 19:43:38 +0100 Subject: [PATCH 13/17] sensible defuaults, under/overscore --- ezFBsevenSeg.py | 68 +++++++++++++++++++++++++++---------------------- sevenSegDemo.py | 16 +++++++----- 2 files changed, 46 insertions(+), 38 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 1b08ace9..d2bc3e3b 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -42,6 +42,8 @@ 68 : ['bm','bb','ll','rl','ru'], # D 69 : ['bt','bm','bb','lu','ll'], # E 70 : ['bt','bm','lu','ll'], # F + 95 : ['bb'], # underscore: '_' + 175 : ['bt'], # macron (overscore): '¯' } _CHARS_HALF = { @@ -62,15 +64,22 @@ class SEVEN_SEG: weight = 'medium' size = 32 # default - def __init__(self, height, width, - led_high=None, led_wide=None, - led_thick=3, led_gap=3, + def __init__(self, width, height, + led_wide=None, led_high=None, + led_thick=None, led_gap=None, use_cache=True, precache=None): + # needed by ez font writer self.__name__ = '{}-{}x{}'.format(self.__module__, height, width) + # constrain and set width and height + self._wide = max(4, width) + self._high = max(6, height) # Sensible (?) defaults - led_high = int(height * 0.8) if led_high is None else led_high - led_wide = int(width * 0.8) if led_wide is None else led_wide - self.set(height, width, led_high, led_wide, led_thick, led_gap, use_cache, precache) + led_wide = self._wide - 1 - (self._wide // 8) if led_wide is None else led_wide + led_high = self._high - 1 - (self._high // 8) if led_high is None else led_high + led_thick = 1 + floor(width // 10) if led_thick is None else led_thick + led_gap = 1 + floor(width // 20) if led_gap is None else led_gap + # Apply initial settings + self.set(led_wide, led_high, led_thick, led_gap, use_cache, precache) ''' Internal methods specific to the Seven Segment font @@ -97,8 +106,8 @@ def _render_full(self, segments): def _render_half(self, segments): # Render the char using a half-width framebuf, returns a bytearray - wide = ceil(self._wide/2) - led_wide = ceil(self._led_wide/2) + wide = ceil(self._wide / 2) + led_wide = ceil(self._led_wide / 2) bytewide = ((wide - 1) // 8) + 1 buf = bytearray(self._high * bytewide) canvas = FrameBuffer(buf, wide, self._high, MONO_HLSB) @@ -115,7 +124,8 @@ def _draw_full(self, canvas, X, Y, T, G, elements): Draw a Full-Width character using the 7 led segments ''' # Draw the required bars in full (intersecting) - M = int(Y/2) + M = round(Y/2) + R = Y - M for l in range(T): if 'bt' in elements: canvas.hline(l, l, X-(2*l), 1) @@ -126,9 +136,9 @@ def _draw_full(self, canvas, X, Y, T, G, elements): if 'bm' in elements: canvas.hline(l, M+l, X-(2*l), 1) if 'll' in elements: - canvas.vline(l, M+l, M-(2*l), 1) + canvas.vline(l, M+l, R-(2*l), 1) if 'rl' in elements: - canvas.vline(X-l-1, M+l, M-(2*l), 1) + canvas.vline(X-l-1, M+l, R-(2*l), 1) if 'bb' in elements: canvas.hline(l, Y-l-1, X-(2*l), 1) # Now create the gaps that seperate them @@ -143,11 +153,12 @@ def _draw_full(self, canvas, X, Y, T, G, elements): canvas.line(X - x - 1, Y - y - 1, X - T - x - 1, Y - T - y - 1, 0) def _draw_half(self, canvas, X, Y, T, elements): - U = int(Y*0.35) - M = int(Y/2) - L = int(Y*0.68) - C = int(X/2) - i = int(T/2) + U = floor(Y*0.33) + M = round(Y/2) + L = round(Y*0.66) + C = X // 2 + i = T // 2 + #print('X Y U M L C T i :: ', X, Y, U, M, L, C, T, i) if 'de' in elements: canvas.rect(C-i, Y-T, T, T, 1, True) if 'dm' in elements: @@ -188,8 +199,7 @@ def max_ch(self): return max(_ALL_CHARS) # NEEDS HEAVY RE_WRITE - def set(self, height=None, width=None, - led_high=None, led_wide=None, + def set(self, led_wide=None, led_high=None, led_thick=None, led_gap=None, cached=None, precache=None): ''' @@ -198,34 +208,30 @@ def set(self, height=None, width=None, - All parameters are optional - Always clears the cache ''' - # Always clean cache - self._cache = {} - # constrain - # TODO # set new value as necesscary - self._high = height if height is not None else self._high - self._wide = width if width is not None else self._wide - self._led_high = led_high if led_high is not None else self._led_high self._led_wide = led_wide if led_wide is not None else self._led_wide + self._led_high = led_high if led_high is not None else self._led_high self._led_thick = led_thick if led_thick is not None else self._led_thick self._led_gap = led_gap if led_gap is not None else self._led_gap self._use_cache = cached if cached is not None else self._use_cache + # constrain + self._led_wide = max(3, min(self._led_wide, self._wide)) + self._led_high = max(5, min(self._led_high, self._high)) + self._led_thick = max(1, self._led_thick) # Calculate led offset within char self._addx = floor((self._wide - self._led_wide) / 2) self._addy = floor((self._high - self._led_high) / 2) + # Always clean cache + self._cache = {} # Pre-cache any chars passed by 'pre' - # - this works even if caching is disabled if precache is not None: for ch in precache: _, _, _ = self.get_ch(ch) # DEBUG: remove this later.. def info(self): - # useful for debug; returns height, width - # cache active(bool),and any current chached chars as a list - c = list(self._cache.keys()) - c.sort() - return self._wide, self._high, self._led_thick, self._led_gap, self._use_cache, c + print('Width: {}, Height: {},\nled_wide: {}, led_high: {},\nled_thick: {}, led_gap: {},\naddx: {}, addy: {}' + .format(self._wide, self._high, self._led_wide, self._led_high, self._led_thick, self._led_gap, self._addx, self._addy)) ''' get_ch() returns the glyph data diff --git a/sevenSegDemo.py b/sevenSegDemo.py index a8a82a5e..d0ae6e0e 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -8,24 +8,26 @@ from ezFBsevenSeg import SEVEN_SEG X = 24 -Y = 36 +Y = 34 # Display -display = REPL_1306(X * 5, Y * 1, clear=False, blocks=True) +display = REPL_1306(X * 5, Y * 2, clear=False, blocks=True) +display.fill(1) # Create a font instance -bigtimefont = SEVEN_SEG(height = Y, width = X, led_thick = 3) +bigtimefont = SEVEN_SEG(height = Y, width = X) # Set font up -bigtimefont.set(led_high=32, led_thick=4, led_gap=2) +#bigtimefont.set() # Now create a font writer -bigtime = ezFBfont(display, bigtimefont, hgap=1, vgap=1, fg=0, bg=1) +bigtime = ezFBfont(display, bigtimefont, hgap=1, vgap=1, fg=1, bg=0) #bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF',0,0) #bigtime.write('08:34\n56.79\u00B721\n ABCDEF',0,0) -bigtime.write('08:34',0,0) +bigtime.write('08:34\n¯-_.\u00B7\u02D9',0,0) +#bigtime.write(':8',0,0) display.show() #print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') -print(bigtimefont.info()) +bigtimefont.info() From ea19228a0c5d7284c3de3c4c3bfb16a2dbdb10a2 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 19 Mar 2026 18:40:51 +0100 Subject: [PATCH 14/17] fine tuning --- ezFBsevenSeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index d2bc3e3b..f929f468 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -77,7 +77,7 @@ def __init__(self, width, height, led_wide = self._wide - 1 - (self._wide // 8) if led_wide is None else led_wide led_high = self._high - 1 - (self._high // 8) if led_high is None else led_high led_thick = 1 + floor(width // 10) if led_thick is None else led_thick - led_gap = 1 + floor(width // 20) if led_gap is None else led_gap + led_gap = 1 + floor(width // 16) if led_gap is None else led_gap # Apply initial settings self.set(led_wide, led_high, led_thick, led_gap, use_cache, precache) From 5fda6222ece91fb9eaf58048a67188496fb667ed Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 31 Mar 2026 15:41:52 +0200 Subject: [PATCH 15/17] Alignment and extra chars --- ezFBsevenSeg.py | 4 +++- sevenSegDemo.py | 21 +++++++++++---------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index f929f468..50d243fc 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -44,6 +44,8 @@ 70 : ['bt','bm','lu','ll'], # F 95 : ['bb'], # underscore: '_' 175 : ['bt'], # macron (overscore): '¯' + 176 : ['bt','bm','lu','ru'], # degrees: '°' + 8801 : ['bt','bm','bb'] # tribar: unicode u+2261 } _CHARS_HALF = { @@ -228,8 +230,8 @@ def set(self, led_wide=None, led_high=None, for ch in precache: _, _, _ = self.get_ch(ch) - # DEBUG: remove this later.. def info(self): + ''' Useful for debug ''' print('Width: {}, Height: {},\nled_wide: {}, led_high: {},\nled_thick: {}, led_gap: {},\naddx: {}, addy: {}' .format(self._wide, self._high, self._led_wide, self._led_high, self._led_thick, self._led_gap, self._addx, self._addy)) diff --git a/sevenSegDemo.py b/sevenSegDemo.py index d0ae6e0e..5b896c1c 100644 --- a/sevenSegDemo.py +++ b/sevenSegDemo.py @@ -7,26 +7,27 @@ from ezFBfont import ezFBfont from ezFBsevenSeg import SEVEN_SEG -X = 24 -Y = 34 +X = 20 +Y = 28 # Display -display = REPL_1306(X * 5, Y * 2, clear=False, blocks=True) -display.fill(1) +display = REPL_1306(X * 8, Y * 3, clear = False, blocks = True) +display.fill(0) # Create a font instance bigtimefont = SEVEN_SEG(height = Y, width = X) # Set font up -#bigtimefont.set() +#bigtimefont.set(led_wide = None, led_high = None, led_thick = None, led_gap = None) # Now create a font writer -bigtime = ezFBfont(display, bigtimefont, hgap=1, vgap=1, fg=1, bg=0) +bigtime = ezFBfont(display, bigtimefont) -#bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF',0,0) -#bigtime.write('08:34\n56.79\u00B721\n ABCDEF',0,0) -bigtime.write('08:34\n¯-_.\u00B7\u02D9',0,0) -#bigtime.write(':8',0,0) +bigtime.write('n01234567\n89ABCDEF\nG _-¯≡\u2009.\u00B7\u02D9:', 1, 0) +#bigtime.write(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF', 0, 0) +#bigtime.write('08:34\n56.79\u00B721\n ABCDEF', 0, 0) +#bigtime.write('08:34\n¯-_.\u00B7\u02D9', 1, 1) +#bigtime.write('8.3', 0, 0) display.show() #print(' -\u2009.\u00B7\u02D9:\n01234567\n89ABCDEF') From aa0d11fed845e1e99efe06fb71f34197bdeb9363 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 31 Mar 2026 15:58:10 +0200 Subject: [PATCH 16/17] start doc --- SEVENSEG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 SEVENSEG.md diff --git a/SEVENSEG.md b/SEVENSEG.md new file mode 100644 index 00000000..fbf67886 --- /dev/null +++ b/SEVENSEG.md @@ -0,0 +1,11 @@ +# Dynamic 7-Segment display font + +* Supports all hexidecimal digits (`0123456789ABCDEF`) at full width, plus hyphen (minus, `-`), decimal (`.`), space (` `) and colon (`:`) as half-width chars. + * It also has half width chars for 'center dot' and 'upper dot', and full width chars for 'underscore', 'top bar', 'tri-bar' and 'degrees'. + +## Features: +* Scales smothly from a 4x6 (width * height) to infinity (memory limited). +* Characters are drawn on demand when first used, then cached for faster access later. + * You can pre-cache characters when the font is initialised + * Caching can also be disabled +* Automatic sizing and placement of LED elements within the character From 6940de15feaae2e30bf16b2ea4cf62e7ffc3a2b8 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 31 Mar 2026 16:02:11 +0200 Subject: [PATCH 17/17] trivia --- ezFBsevenSeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ezFBsevenSeg.py b/ezFBsevenSeg.py index 50d243fc..71013a20 100644 --- a/ezFBsevenSeg.py +++ b/ezFBsevenSeg.py @@ -25,7 +25,7 @@ # - index is the integer character ord(), _CHARS_FULL = { 32 : [], # space - 45 : ['bm'], # negative: '-' + 45 : ['bm'], # hyphen: '-' 48 : ['bt','bb','lu','ll','ru','rl'], # 0 49 : ['ru','rl'], # 1 50 : ['bt','bm','bb','ll','ru'], # 2