Skip to content

Commit

Permalink
Merge pull request #198 from Yardie-/fontconvert
Browse files Browse the repository at this point in the history
Fontconvert additional argument string
  • Loading branch information
vroland authored Sep 25, 2022
2 parents 5249bed + 736e93a commit 562432a
Show file tree
Hide file tree
Showing 3 changed files with 272 additions and 39 deletions.
101 changes: 101 additions & 0 deletions scripts/README.md
Original file line number Diff line number Diff line change
@@ -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.

202 changes: 166 additions & 36 deletions scripts/fontconvert.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -48,12 +30,107 @@
#(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]
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
# 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)))

Expand All @@ -73,21 +150,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 = []
Expand Down Expand Up @@ -126,44 +230,70 @@ 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 = []
for index, glyph in enumerate(all_glyphs):
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\"")
print(f"const uint8_t {font_name}Bitmaps[{len(glyph_data)}] = {{")

# 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(f"const EpdGlyph {font_name}Glyphs[] = {{")

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 '<backslash>'}")
print (" { " + ", ".join([f"{a}" for a in list(g[:-1])]),"},", f"// '{chr(g.code_point) if g.code_point != 92 else '<backslash>'}'")
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} }},")
offset += i_end - i_start + 1
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}), 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}), ie. '{chr(i_start)}' - '{chr(i_end)}'", file=sys.stderr)
print("")
print("*/")

8 changes: 5 additions & 3 deletions src/epd_driver/include/epd_board_specific.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 562432a

Please sign in to comment.