From 684e9bc9c6883ba23c4ede318ac5b10de704e4a3 Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Sat, 23 Jul 2022 18:47:28 +0800 Subject: [PATCH 1/7] Modify documentation for epd_powerdown_lilygo_t5_47 added function definition to epd_driver.h to allow Arduino to find it and fixed up the documentation --- src/epd_driver/include/epd_board_specific.h | 8 ++++--- src/epd_driver/include/epd_driver.h | 24 +++++++++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/epd_driver/include/epd_board_specific.h b/src/epd_driver/include/epd_board_specific.h index 35585bad..779374dd 100644 --- a/src/epd_driver/include/epd_board_specific.h +++ b/src/epd_driver/include/epd_board_specific.h @@ -36,11 +36,13 @@ esp_err_t epd_gpio_set_value(uint8_t value) __attribute__ ((deprecated)); On the Lilygo the epd power flag was re-purposed as power enable for everything. This is a hardware thing. \warning This workaround may still leave power on to epd and as such may cause other problems such as grey screen. - Please also use epd_poweroff() and epd_deinit() when you sleep the system wake on touch will still work. - + + Please use epd_poweroff() and epd_deinit() whenever you sleep the system. + The following code can be used to sleep the lilygo and power down the peripherals and wake the unit on touch. + However is should be noted that the touch controller is not powered and as such the touch coordinates will not be captured. Arduino specific code: \code{.c} - epd_powerdown_lilygo_t5_47(); + epd_poweroff(); epd_deinit(); esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH); esp_deep_sleep_start(); diff --git a/src/epd_driver/include/epd_driver.h b/src/epd_driver/include/epd_driver.h index 8a31f2a7..bd848e1f 100644 --- a/src/epd_driver/include/epd_driver.h +++ b/src/epd_driver/include/epd_driver.h @@ -213,6 +213,30 @@ void epd_poweron(); /** Disable display power supply. */ void epd_poweroff(); +/** This is a Lilygo47 specific function + + This is a work around a hardware issue with the Lilygo47 epd_poweroff() turns off the epaper completely + however the hardware of the Lilygo47 is different than the official boards. Which means that on the Lilygo47 this + disables power to the touchscreen. + + This is a workaround to allow to disable display power but not the touch screen. + On the Lilygo the epd power flag was re-purposed as power enable + for everything. This is a hardware thing. + \warning This workaround may still leave power on to epd and as such may cause other problems such as grey screen. + + Please use epd_poweroff() and epd_deinit() whenever you sleep the system. + The following code can be used to sleep the lilygo and power down the peripherals and wake the unit on touch. + However is should be noted that the touch controller is not powered and as such the touch coordinates will not be captured. + Arduino specific code: + \code{.c} + epd_poweroff(); + epd_deinit(); + esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH); + esp_deep_sleep_start(); + \endcode +*/ +void epd_powerdown_lilygo_t5_47(); + /** Clear the whole screen by flashing it. */ void epd_clear(); From 78491a68532252fa01b246e97e9ad6d2f8fc47bb Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Sat, 23 Jul 2022 18:51:12 +0800 Subject: [PATCH 2/7] add string command line argument This add another command line argument "string" allowing the user to specify the particular charcters to be drawn from the ttf file and included in the font header file. --- scripts/fontconvert.py | 193 ++++++++++++++++++++++++++++++++++------- 1 file changed, 161 insertions(+), 32 deletions(-) mode change 100755 => 100644 scripts/fontconvert.py diff --git a/scripts/fontconvert.py b/scripts/fontconvert.py old mode 100755 new mode 100644 index aa233c90..21c98e1e --- a/scripts/fontconvert.py +++ b/scripts/fontconvert.py @@ -1,29 +1,11 @@ -#!python3 -import freetype -import zlib +#!python3 import sys -import re -import math -import argparse -from collections import namedtuple - -parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.") -parser.add_argument("name", action="store", help="name of the font.") -parser.add_argument("size", type=int, help="font size to use.") -parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority.") -parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.") -parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.") -args = parser.parse_args() - -GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"]) - -font_stack = [freetype.Face(f) for f in args.fontstack] -compress = args.compress -size = args.size -font_name = args.name # inclusive unicode code point intervals # must not overlap and be in ascending order +# modify intervals here +# however if the "string" command line argument is used these are ignored + intervals = [ (32, 126), (160, 255), @@ -48,12 +30,106 @@ #(0x1F600, 0x1F680), ] + + +try: + import freetype +except ImportError as error: + sys.exit("To run this script the freetype module needs to be installed.\nThis can be done using:\npip install freetype-py") +import zlib +import sys +import re +import math +import argparse +from collections import namedtuple +#see https://freetype-py.readthedocs.io/en/latest/ for documentation +parser = argparse.ArgumentParser(description="Generate a header file from a font to be used with epdiy.") +parser.add_argument("name", action="store", help="name of the font.") +parser.add_argument("size", type=int, help="font size to use.") +parser.add_argument("fontstack", action="store", nargs='+', help="list of font files, ordered by descending priority. This is not actually implemented please just use one file for now.") +parser.add_argument("--compress", dest="compress", action="store_true", help="compress glyph bitmaps.") +parser.add_argument("--additional-intervals", dest="additional_intervals", action="append", help="Additional code point intervals to export as min,max. This argument can be repeated.") +parser.add_argument("--string", action="store", help="A string of all required characters. intervals are made up of this" ) + +args = parser.parse_args() +command_line = "" +prev_arg = "" +for arg in sys.argv: + # ~ if prev_arg == "--string": + # ~ command_line = command_line + " '" + arg +"'" + # ~ else: + command_line = command_line + " " + arg + # ~ prev_arg = arg + +# ~ print (command_line) +GlyphProps = namedtuple("GlyphProps", ["width", "height", "advance_x", "left", "top", "compressed_size", "data_offset", "code_point"]) + +font_stack = [freetype.Face(f) for f in args.fontstack] +font_files = args.fontstack +face_index = 0 +font_file = font_files[face_index] +compress = args.compress +size = args.size +font_name = args.name + +for face in font_stack: + # shift by 6 bytes, because sizes are given as 6-bit fractions + # the display has about 150 dpi. + face.set_char_size(size << 6, size << 6, 150, 150) + + +# assign intervals from argument parrameters ie. handle the string arg + +if args.string != None: + font_file = font_files[face_index] + chars = sorted(set(args.string)) + #make array of code pointscode_ponts.append(ord(char)) + code_points = list() + intervals = [] # empty the intevals array NB. if you want to allways add default characters comment out this line + # go through the sorted characters and make the intervals + for char in chars: + if( face.get_char_index(ord(char)) != 0 ): + # this character is in the font file so add it to the new string. + code_points.append(ord(char)) + else: + print("The character ", char, " is not available in ", font_file, file=sys.stderr) + lower = code_points[0] + len_x = len(code_points) + x = 0 + while x < len_x: + # ~ print ("loop value x = ", x , file=sys.stderr) + a = code_points[x]; + b = a; + if( x < len_x - 1): + b = code_points[x + 1]; + + if( a == b - 1 ): + # ~ print("sequential", a, b, file=sys.stderr) + if( lower == -1): + lower = a + else: + # ~ print("non sequential", a, b , file=sys.stderr) + if( lower == -1): + # ~ print("single character") + interval = (a , a) + else: + interval = (lower, a) + # ~ print("interval", interval , file=sys.stderr) + intervals.append(interval) + lower = -1 + x = x + 1 + + +# base intervals are assigned dditional intervals from arguments add_ints = [] -if args.additional_intervals: +if args.additional_intervals != None: add_ints = [tuple([int(n, base=0) for n in i.split(",")]) for i in args.additional_intervals] intervals = sorted(intervals + add_ints) +# ~ print("Intervals are now: ", intervals, file=sys.stderr) + + def norm_floor(val): return int(math.floor(val / (1 << 6))) @@ -73,21 +149,48 @@ def chunks(l, n): total_packed = 0 all_glyphs = [] +# new globals +total_chars = 0 +ascender = 0 +descender = 100 +f_height = 0 + def load_glyph(code_point): + global face_index face_index = 0 while face_index < len(font_stack): face = font_stack[face_index] glyph_index = face.get_char_index(code_point) if glyph_index > 0: face.load_glyph(glyph_index, freetype.FT_LOAD_RENDER) + #count characters found and find bounds of characters + global ascender + if ascender < face.size.ascender: + ascender = face.size.ascender + global descender + if descender > face.size.descender: + descender = face.size.descender + global f_height + if f_height < face.size.height: + f_height = face.size.height + global total_chars + total_chars += 1 return face break face_index += 1 + # this needs work + # this needs to be handled better to show failed character and continue not just die a questionable death + # this appears to have been designed to combine several font files + # but that is not clear to the end user and this then looks like a bug print (f"falling back to font {face_index} for {chr(code_point)}.", file=sys.stderr) raise ValueError(f"code point {code_point} not found in font stack!") for i_start, i_end in intervals: for code_point in range(i_start, i_end + 1): + # handle missing characters in font file + if( face.get_char_index(code_point) == 0 ): + print("Character ", chr(code_point), "(", code_point, ") is not in ", font_file, file=sys.stderr) + continue face = load_glyph(code_point) bitmap = face.glyph.bitmap pixels = [] @@ -126,7 +229,8 @@ def load_glyph(code_point): all_glyphs.append((glyph, compressed)) # pipe seems to be a good heuristic for the "real" descender -face = load_glyph(ord('|')) +# face = load_glyph(ord('|')) +# removed as max descender and assender are handled above glyph_data = [] glyph_props = [] @@ -134,17 +238,32 @@ def load_glyph(code_point): props, compressed = glyph glyph_data.extend([b for b in compressed]) glyph_props.append(props) +print("", file=sys.stderr) +print(f"Original font file {font_file} as {font_name} using {total_chars} characters", file=sys.stderr) print("total", total_packed, file=sys.stderr) print("compressed", total_size, file=sys.stderr) print("#pragma once") print("#include \"epd_driver.h\"") + +# add font file origin and characters at the head of the output file +print("/*") +print ( "Created with") +print(command_line) +print(f"As '{font_name}' with available {total_chars} characters") +for i, g in enumerate(glyph_props): + print (f"{chr(g.code_point)}", end ="" ) +print("") +print("*/") + print(f"const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{") for c in chunks(glyph_data, 16): print (" " + " ".join(f"0x{b:02X}," for b in c)) print ("};"); + +print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]') print(f"const EpdGlyph {font_name}Glyphs[] = {{") for i, g in enumerate(glyph_props): print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else ''}") @@ -158,12 +277,22 @@ def load_glyph(code_point): print ("};"); print(f"const EpdFont {font_name} = {{") -print(f" {font_name}Bitmaps,") -print(f" {font_name}Glyphs,") -print(f" {font_name}Intervals,") -print(f" {len(intervals)},") -print(f" {1 if compress else 0},") -print(f" {norm_ceil(face.size.height)},") -print(f" {norm_ceil(face.size.ascender)},") -print(f" {norm_floor(face.size.descender)},") +print(f" {font_name}_Bitmaps, // (*bitmap) Glyph bitmap pointer, all concatenated together") +print(f" {font_name}_Glyphs, // glyphs Glyph array") +print(f" {font_name}_Intervals, // intervals Valid unicode intervals for this font") +print(f" {len(intervals)}, // interval_count Number of unicode intervals.intervals") +print(f" {1 if compress else 0}, // compressed Does this font use compressed glyph bitmaps?") +print(f" {norm_ceil(f_height)}, // advance_y Newline distance (y axis)") +print(f" {norm_ceil(ascender)}, // ascender Maximal height of a glyph above the base line") +print(f" {norm_floor(descender)}, // descender Maximal height of a glyph below the base line") print("};") +print("/*") +print("Included intervals") +for i_start, i_end in intervals: + print (f" ( {i_start}, {i_end}), # {chr(i_start)} - {chr(i_end)}") +print("Included intervals", file=sys.stderr) +for i_start, i_end in intervals: + print (f" ( {i_start}, {i_end}), # {chr(i_start)} - {chr(i_end)}", file=sys.stderr) +print("") +print("*/") + From afd4cf8e2785526801a41bf12a20bcb647c796bb Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Tue, 26 Jul 2022 18:22:56 +0800 Subject: [PATCH 3/7] Remove epd_powerdown_lilygo_t5_47() Arduino can't find this but this simplifies the PR --- src/epd_driver/include/epd_driver.h | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/epd_driver/include/epd_driver.h b/src/epd_driver/include/epd_driver.h index bd848e1f..8a31f2a7 100644 --- a/src/epd_driver/include/epd_driver.h +++ b/src/epd_driver/include/epd_driver.h @@ -213,30 +213,6 @@ void epd_poweron(); /** Disable display power supply. */ void epd_poweroff(); -/** This is a Lilygo47 specific function - - This is a work around a hardware issue with the Lilygo47 epd_poweroff() turns off the epaper completely - however the hardware of the Lilygo47 is different than the official boards. Which means that on the Lilygo47 this - disables power to the touchscreen. - - This is a workaround to allow to disable display power but not the touch screen. - On the Lilygo the epd power flag was re-purposed as power enable - for everything. This is a hardware thing. - \warning This workaround may still leave power on to epd and as such may cause other problems such as grey screen. - - Please use epd_poweroff() and epd_deinit() whenever you sleep the system. - The following code can be used to sleep the lilygo and power down the peripherals and wake the unit on touch. - However is should be noted that the touch controller is not powered and as such the touch coordinates will not be captured. - Arduino specific code: - \code{.c} - epd_poweroff(); - epd_deinit(); - esp_sleep_enable_ext1_wakeup(GPIO_SEL_13, ESP_EXT1_WAKEUP_ANY_HIGH); - esp_deep_sleep_start(); - \endcode -*/ -void epd_powerdown_lilygo_t5_47(); - /** Clear the whole screen by flashing it. */ void epd_clear(); From dc98adabd2412d998fbce3add24e6500524aede7 Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Wed, 3 Aug 2022 15:58:28 +0800 Subject: [PATCH 4/7] Bug fix --- scripts/fontconvert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/fontconvert.py b/scripts/fontconvert.py index 21c98e1e..1ecd3152 100644 --- a/scripts/fontconvert.py +++ b/scripts/fontconvert.py @@ -257,19 +257,19 @@ def load_glyph(code_point): print("") print("*/") -print(f"const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{") +print(f"const uint8_t {font_name}_Bitmaps[{len(glyph_data)}] = {{") for c in chunks(glyph_data, 16): print (" " + " ".join(f"0x{b:02X}," for b in c)) print ("};"); print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]') -print(f"const EpdGlyph {font_name}Glyphs[] = {{") +print(f"const EpdGlyph {font_name}_Glyphs[] = {{") for i, g in enumerate(glyph_props): print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else ''}") print ("};"); -print(f"const EpdUnicodeInterval {font_name}Intervals[] = {{") +print(f"const EpdUnicodeInterval {font_name}_Intervals[] = {{") offset = 0 for i_start, i_end in intervals: print (f" {{ 0x{i_start:X}, 0x{i_end:X}, 0x{offset:X} }},") From 79b9280768d24ce3b0cc8e48d8a947d0a61d9bdf Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Mon, 8 Aug 2022 20:31:53 +0800 Subject: [PATCH 5/7] Always add ASCII 32 to the string As the code point for a space needs to be added for a space to appear in the end result it is added by default when using the string argument. If it is not in the ttf it will simply get skipped and warned about. --- scripts/fontconvert.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/fontconvert.py b/scripts/fontconvert.py index 1ecd3152..e6b78879 100644 --- a/scripts/fontconvert.py +++ b/scripts/fontconvert.py @@ -82,7 +82,8 @@ if args.string != None: font_file = font_files[face_index] - chars = sorted(set(args.string)) + string = " " + args.string # always add space to the string it is easily forgotten + chars = sorted(set(string)) #make array of code pointscode_ponts.append(ord(char)) code_points = list() intervals = [] # empty the intevals array NB. if you want to allways add default characters comment out this line From 5a23874ebd715fcd93405b33d22b98e5cd30505d Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Mon, 8 Aug 2022 20:48:20 +0800 Subject: [PATCH 6/7] cleaned up some output --- scripts/fontconvert.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/fontconvert.py b/scripts/fontconvert.py index e6b78879..94286557 100644 --- a/scripts/fontconvert.py +++ b/scripts/fontconvert.py @@ -267,7 +267,7 @@ def load_glyph(code_point): print ('// GlyphProps[width, height, advance_x, left, top, compressed_size, data_offset, code_point]') print(f"const EpdGlyph {font_name}_Glyphs[] = {{") for i, g in enumerate(glyph_props): - print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// {chr(g.code_point) if g.code_point != 92 else ''}") + print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// '{chr(g.code_point) if g.code_point != 92 else ''}'") print ("};"); print(f"const EpdUnicodeInterval {font_name}_Intervals[] = {{") @@ -290,10 +290,10 @@ def load_glyph(code_point): print("/*") print("Included intervals") for i_start, i_end in intervals: - print (f" ( {i_start}, {i_end}), # {chr(i_start)} - {chr(i_end)}") + print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'") print("Included intervals", file=sys.stderr) for i_start, i_end in intervals: - print (f" ( {i_start}, {i_end}), # {chr(i_start)} - {chr(i_end)}", file=sys.stderr) + print (f" ( {i_start}, {i_end}), ie. '{chr(i_start)}' - '{chr(i_end)}'", file=sys.stderr) print("") print("*/") From e371cd10c86f77affc1ce4f0f04d667a4a6d95e3 Mon Sep 17 00:00:00 2001 From: Greg Dickson Date: Wed, 31 Aug 2022 20:33:32 +0800 Subject: [PATCH 7/7] Add README with example --- scripts/README.md | 101 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 scripts/README.md diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..5d74802f --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,101 @@ +## Scripts in this folder are for adding addtional capabilities to epdiy. + + + +## imgconvert.py + +#### usage: + +python3 imgconvert.py [-h] -i INPUTFILE -n NAME -o OUTPUTFILE [-maxw MAX_WIDTH] + [-maxh MAX_HEIGHT] + +**optional arguments:** + + * **-h, --help** show this help message and exit + + * **-i INPUTFILE** + + * **-n NAME** + + * **-o OUTPUTFILE** + + * **-maxw MAX_WIDTH** + + * **-maxh MAX_HEIGHT** + + +========================================================== + +## fontconvert.py + +#### usage: + +python3 fontconvert.py [-h] [--compress] [--additional-intervals ADDITIONAL_INTERVALS] + [--string STRING] + name size fontstack [fontstack ...] + +Generate a header file from a font to be used with epdiy. + +**positional arguments:** + + * **name** name of the font to be used in epdiy. + * **size** font size to use. + * **fontstack** list of font files, ordered by descending priority. This is not actually implemented as yet. Please just use one file for now. + +**optional arguments:** + + * **-h**, --help show this help message and exit + + * **--compress** compress glyph bitmaps. + + * **--additional-intervals** ADDITIONAL_INTERVALS + + Additional code point intervals to export as min,max. This argument + can be repeated. + + * **--string STRING** A quoted string of all required characters. The intervals are will be made from these characters if they exist in the ttf file. Missing characters will warn about their abscence. + + + +####example: + 1. Download a ttf from where you like to a directory. As in: "~/Downloads/any_old_ttf.ttf" +in the download directory + + 2. Run + + `python3 fontconvert.py my_font 30 ~/Downloads/any_old_ttf.ttf --string '/0123456789:;@ABCDEFGH[\]^_`abcdefgh\{|}~¡¢£¤¥¦§¨©ª' > fonts.h` + + * you will need to use special escapes for characters like ' or " This is system dependant though. + + 3. copy fonts.h into your app folder or where ever your app can find it. + 4. include it into your project with +`#include fonts.h` +Then use it just like any other font file in epdiy. + +**To run this script the freetype module needs to be installed. This can be done with `pip install freetype-py` You will be warned if it is not accessible by the script.** + +========================================================== + +##waveform_hdrgen.py + +####usage: + +waveform_hdrgen.py [-h] [--list-modes] [--temperature-range TEMPERATURE_RANGE] + [--export-modes EXPORT_MODES] + name + +**positional arguments:** + name name of the waveform object. + +**optional arguments:** + + * **-h, --help** show this help message and exit + + * **--list-modes** list the available modes for tis file. + + * **--temperature-range TEMPERATURE_RANGE** + only export waveforms in the temperature range of min,max °C. + + * **--export-modes EXPORT_MODES** + comma-separated list of waveform mode IDs to export. +