2018-08-18 21:40:59 +02:00
|
|
|
# coding=utf-8
|
|
|
|
import voluptuous as vol
|
|
|
|
|
|
|
|
from esphomeyaml import core
|
|
|
|
from esphomeyaml.components import display
|
2018-12-05 21:22:06 +01:00
|
|
|
import esphomeyaml.config_validation as cv
|
2018-08-18 21:40:59 +02:00
|
|
|
from esphomeyaml.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_SIZE
|
2018-12-05 21:22:06 +01:00
|
|
|
from esphomeyaml.core import CORE, HexInt
|
|
|
|
from esphomeyaml.cpp_generator import ArrayInitializer, MockObj, Pvariable, RawExpression, add
|
|
|
|
from esphomeyaml.cpp_types import App
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
DEPENDENCIES = ['display']
|
2018-12-05 21:22:06 +01:00
|
|
|
MULTI_CONF = True
|
2018-08-18 21:40:59 +02:00
|
|
|
|
2018-11-12 23:30:31 +01:00
|
|
|
Font = display.display_ns.class_('Font')
|
|
|
|
Glyph = display.display_ns.class_('Glyph')
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
def validate_glyphs(value):
|
|
|
|
if isinstance(value, list):
|
|
|
|
value = vol.Schema([cv.string])(value)
|
|
|
|
value = vol.Schema([cv.string])(list(value))
|
|
|
|
|
|
|
|
def comparator(x, y):
|
|
|
|
x_ = x.encode('utf-8')
|
|
|
|
y_ = y.encode('utf-8')
|
|
|
|
|
|
|
|
for c in range(min(len(x_), len(y_))):
|
|
|
|
if x_[c] < y_[c]:
|
|
|
|
return -1
|
|
|
|
if x_[c] > y_[c]:
|
|
|
|
return 1
|
|
|
|
|
|
|
|
if len(x_) < len(y_):
|
|
|
|
return -1
|
2019-01-02 14:11:11 +01:00
|
|
|
if len(x_) > len(y_):
|
2018-08-18 21:40:59 +02:00
|
|
|
return 1
|
2019-01-02 14:11:11 +01:00
|
|
|
raise vol.Invalid(u"Found duplicate glyph {}".format(x))
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
value.sort(cmp=comparator)
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
def validate_pillow_installed(value):
|
|
|
|
try:
|
|
|
|
import PIL
|
|
|
|
except ImportError:
|
2018-08-25 22:27:30 +02:00
|
|
|
raise vol.Invalid("Please install the pillow python package to use this feature. "
|
2019-01-02 14:11:11 +01:00
|
|
|
"(pip install pillow)")
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
if PIL.__version__[0] < '4':
|
|
|
|
raise vol.Invalid("Please update your pillow installation to at least 4.0.x. "
|
2019-01-02 14:11:11 +01:00
|
|
|
"(pip install -U pillow)")
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
def validate_truetype_file(value):
|
|
|
|
if value.endswith('.zip'): # for Google Fonts downloads
|
|
|
|
raise vol.Invalid(u"Please unzip the font archive '{}' first and then use the .ttf files "
|
|
|
|
u"inside.".format(value))
|
|
|
|
if not value.endswith('.ttf'):
|
|
|
|
raise vol.Invalid(u"Only truetype (.ttf) files are supported. Please make sure you're "
|
|
|
|
u"using the correct format or rename the extension to .ttf")
|
2018-09-23 18:58:41 +02:00
|
|
|
return cv.file_(value)
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_GLYPHS = u' !"%()+,-.:0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
|
|
|
CONF_RAW_DATA_ID = 'raw_data_id'
|
|
|
|
|
|
|
|
FONT_SCHEMA = vol.Schema({
|
|
|
|
vol.Required(CONF_ID): cv.declare_variable_id(Font),
|
|
|
|
vol.Required(CONF_FILE): validate_truetype_file,
|
|
|
|
vol.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
2018-08-22 22:04:56 +02:00
|
|
|
vol.Optional(CONF_SIZE, default=20): vol.All(cv.int_, vol.Range(min=1)),
|
2018-08-18 21:40:59 +02:00
|
|
|
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_variable_id(None),
|
|
|
|
})
|
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
CONFIG_SCHEMA = vol.All(validate_pillow_installed, FONT_SCHEMA)
|
2018-08-18 21:40:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
def to_code(config):
|
|
|
|
from PIL import ImageFont
|
|
|
|
|
2018-12-05 21:22:06 +01:00
|
|
|
path = CORE.relative_path(config[CONF_FILE])
|
|
|
|
try:
|
|
|
|
font = ImageFont.truetype(path, config[CONF_SIZE])
|
|
|
|
except Exception as e:
|
|
|
|
raise core.EsphomeyamlError(u"Could not load truetype file {}: {}".format(path, e))
|
|
|
|
|
|
|
|
ascent, descent = font.getmetrics()
|
|
|
|
|
|
|
|
glyph_args = {}
|
|
|
|
data = []
|
|
|
|
for glyph in config[CONF_GLYPHS]:
|
|
|
|
mask = font.getmask(glyph, mode='1')
|
|
|
|
_, (offset_x, offset_y) = font.font.getsize(glyph)
|
|
|
|
width, height = mask.size
|
|
|
|
width8 = ((width + 7) // 8) * 8
|
|
|
|
glyph_data = [0 for _ in range(height * width8 // 8)] # noqa: F812
|
|
|
|
for y in range(height):
|
|
|
|
for x in range(width):
|
|
|
|
if not mask.getpixel((x, y)):
|
|
|
|
continue
|
|
|
|
pos = x + y * width8
|
|
|
|
glyph_data[pos // 8] |= 0x80 >> (pos % 8)
|
|
|
|
glyph_args[glyph] = (len(data), offset_x, offset_y, width, height)
|
|
|
|
data += glyph_data
|
|
|
|
|
|
|
|
raw_data = MockObj(config[CONF_RAW_DATA_ID])
|
|
|
|
add(RawExpression('static const uint8_t {}[{}] PROGMEM = {}'.format(
|
|
|
|
raw_data, len(data),
|
|
|
|
ArrayInitializer(*[HexInt(x) for x in data], multiline=False))))
|
|
|
|
|
|
|
|
glyphs = []
|
|
|
|
for glyph in config[CONF_GLYPHS]:
|
|
|
|
glyphs.append(Glyph(glyph, raw_data, *glyph_args[glyph]))
|
|
|
|
|
|
|
|
rhs = App.make_font(ArrayInitializer(*glyphs), ascent, ascent + descent)
|
|
|
|
Pvariable(config[CONF_ID], rhs)
|