mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 19:05:07 +00:00
**Fonts** Dynamically load text and icon fonts when the device boots. Font bitdepth depends on whether the device is an ESP32 one and whether it has external RAM available. When no external RAM is available on ESP32, only small and medium font variants are generated; large font variants refer to the medium size ones. Implemented Appearance settings app for changing font and font size. **Other changes** - Added keyboard shortcuts to delete or rename files in directory views. - Improved app-grid tile sizing to provide more room for labels. - Reduced the default LED strip brightness.
258 lines
9.4 KiB
Python
Executable File
258 lines
9.4 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Generates the Material Symbols icon resources:
|
|
- include/lvgl/icons/*.h: UTF-8 string defines per icon
|
|
- include/lvgl/icons/names.h and source/icon_names.c: icon name lists per icon font
|
|
- Data/system/fonts/MaterialSymbolsRounded.ttf: TTF subset with only the used icons
|
|
- Data/system/fonts/MaterialSymbolsRounded.codepoints: name to codepoint map of the used icons
|
|
|
|
The icon fonts are rasterized from the TTF subset on device at startup.
|
|
|
|
Requires the fonttools package: pip install fonttools
|
|
"""
|
|
import os
|
|
import urllib.request
|
|
import zlib
|
|
|
|
from fontTools import subset
|
|
from fontTools.ttLib import TTFont
|
|
from fontTools.varLib import instancer
|
|
|
|
def download_file(url: str, filename: str):
|
|
if not os.path.exists(filename):
|
|
print(f"Downloading {filename} from {url}")
|
|
urllib.request.urlretrieve(url, filename)
|
|
else:
|
|
print(f"{filename} already exists, skipping download.")
|
|
|
|
def read_code_points_map(file_path: str):
|
|
codepoints = {}
|
|
with open(file_path, 'r') as f:
|
|
for line in f:
|
|
parts = line.strip().split()
|
|
if len(parts) >= 2:
|
|
name, cp = parts[0].lower(), parts[1]
|
|
codepoints[name] = cp
|
|
if len(codepoints) == 0:
|
|
raise ValueError(f"Code points map is empty or wasn't found at {file_path}")
|
|
return codepoints
|
|
|
|
|
|
def get_code_points(codepoints, names: list) -> list:
|
|
result = []
|
|
for name in names:
|
|
safe_name = name.lower()
|
|
if not safe_name in codepoints:
|
|
raise ValueError(f"Code point '{safe_name}' not found in map")
|
|
result.append(f"0x{codepoints[safe_name].upper()}")
|
|
return result
|
|
|
|
def generate_subset_ttf(font_file: str, codepoints: list, output_path: str) -> bytes:
|
|
print(f"Generating {output_path}")
|
|
font = TTFont(font_file, recalcTimestamp=False)
|
|
if "fvar" in font:
|
|
# Pin all axes to their defaults, which are the outlines lv_font_conv used to render
|
|
font = instancer.instantiateVariableFont(font, {axis.axisTag: None for axis in font["fvar"].axes})
|
|
options = subset.Options()
|
|
options.layout_features = []
|
|
options.drop_tables += ["GSUB", "GPOS", "GDEF"]
|
|
options.hinting = False
|
|
options.notdef_outline = True
|
|
subsetter = subset.Subsetter(options)
|
|
subsetter.populate(unicodes=codepoints)
|
|
subsetter.subset(font)
|
|
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
|
font.save(output_path)
|
|
with open(output_path, "rb") as f:
|
|
return f.read()
|
|
|
|
def generate_codepoints_file(codepoint_map: dict, names: list, output_path: str) -> bytes:
|
|
print(f"Generating {output_path}")
|
|
content = "".join(f"{name} {codepoint_map[name]}\n" for name in names)
|
|
with open(output_path, "w") as f:
|
|
f.write(content)
|
|
return content.encode("utf-8")
|
|
|
|
def generate_icon_name_lists(icon_sets: list, version_input: bytes):
|
|
version = zlib.crc32(version_input + repr(icon_sets).encode("utf-8")) & 0xFFFFFFFF
|
|
|
|
header_path = os.path.join("include", "lvgl", "icons", "names.h")
|
|
print(f"Generating {header_path}")
|
|
with open(header_path, "w") as f:
|
|
f.write("// SPDX-License-Identifier: Apache-2.0\n\n")
|
|
f.write("#pragma once\n\n")
|
|
f.write("#include <stddef.h>\n\n")
|
|
f.write("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n")
|
|
f.write("/** Changes whenever the icon TTF subset, its codepoints file or the icon name lists change */\n")
|
|
f.write(f"#define LVGL_ICON_FONT_VERSION 0x{version:08X}u\n\n")
|
|
f.write("// Icon names per icon font, resolved through the .codepoints file next to the TTF\n")
|
|
for name, _ in icon_sets:
|
|
f.write(f"extern const char* const lvgl_icon_{name}_names[];\n")
|
|
f.write(f"extern const size_t lvgl_icon_{name}_name_count;\n\n")
|
|
f.write("#ifdef __cplusplus\n}\n#endif\n")
|
|
|
|
source_path = os.path.join("source", "icon_names.c")
|
|
print(f"Generating {source_path}")
|
|
with open(source_path, "w") as f:
|
|
f.write("// SPDX-License-Identifier: Apache-2.0\n")
|
|
f.write("// Generated by generate-icons.py\n\n")
|
|
f.write("#include <lvgl/icons/names.h>\n\n")
|
|
for name, names in icon_sets:
|
|
f.write(f"const char* const lvgl_icon_{name}_names[] = {{\n")
|
|
for icon_name in names:
|
|
f.write(f" \"{icon_name.lower()}\",\n")
|
|
f.write("};\n\n")
|
|
f.write(f"const size_t lvgl_icon_{name}_name_count = sizeof(lvgl_icon_{name}_names) / sizeof(lvgl_icon_{name}_names[0]);\n\n")
|
|
|
|
def generate_icon_names(codepoint_map: dict, codepoint_names: list, variable_name: str):
|
|
filename = f"{variable_name}.h"
|
|
print(f"Generating {filename}")
|
|
output_path = os.path.join("include", "lvgl", "icons", filename)
|
|
with open(output_path, 'w') as f:
|
|
f.write("// SPDX-License-Identifier: Apache-2.0\n\n")
|
|
f.write("#pragma once\n\n")
|
|
for name in codepoint_names:
|
|
safe_name = name.lower()
|
|
if safe_name in codepoint_map:
|
|
hex_val = codepoint_map[safe_name]
|
|
# Convert hex to int
|
|
val = int(hex_val, 16)
|
|
# Convert to UTF-8 escaped string
|
|
utf8_bytes = chr(val).encode('utf-8')
|
|
escaped_str = "".join(f"\\x{b:02X}" for b in utf8_bytes)
|
|
|
|
f.write(f'#define LVGL_ICON_{variable_name.upper()}_{safe_name.upper()} "{escaped_str}"\n')
|
|
else:
|
|
print(f"Warning: {safe_name} not found in codepoint map")
|
|
|
|
# --------------- Symbol Fonts ---------------
|
|
|
|
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
|
shared_symbol_code_point_names = [
|
|
"add",
|
|
"apps",
|
|
"area_chart", # System Info
|
|
"app_registration", # App Settings app
|
|
"calendar_month",
|
|
"cable",
|
|
"circle",
|
|
"close",
|
|
"cloud", # Web server
|
|
"check",
|
|
"delete",
|
|
"devices", # Developer app
|
|
"display_settings", # Display Settings app
|
|
"edit_note",
|
|
"electric_bolt", # Power (settings) app
|
|
"folder",
|
|
"deployed_code", # 3D cube
|
|
"download",
|
|
"forum", # Chat app
|
|
"gamepad",
|
|
"help", # Diceware help
|
|
"hub", # App Hub
|
|
"image", # Screenshot app
|
|
"keyboard_arrow_up",
|
|
"lightbulb",
|
|
"language", # Globe
|
|
"lists", # Chat app toolbar
|
|
"mail",
|
|
"menu",
|
|
"mop",
|
|
"more_vert",
|
|
"music_note",
|
|
"note_add",
|
|
"palette",
|
|
"power_settings_new", # Power off for T-Lora Pager
|
|
"refresh", # e.g. App Hub reload button
|
|
"search",
|
|
"settings",
|
|
"toolbar", # Apps without custom icon
|
|
"navigation", # GPS (settings) app
|
|
"keyboard_alt", # Keyboard (settings) app
|
|
"usb", # Power (settings) app
|
|
"wifi", # WiFi (settings) app
|
|
"bluetooth", # Bluetooth (settings) app
|
|
"terminal",
|
|
"logo_dev",
|
|
"camera",
|
|
"lightstrip",
|
|
]
|
|
|
|
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
|
statusbar_symbol_code_point_names = [
|
|
# Location tracking
|
|
"location_on",
|
|
# Development server
|
|
"cloud",
|
|
# Low on available memory
|
|
"memory",
|
|
# SD Card
|
|
"sd_card",
|
|
"sd_card_alert",
|
|
# Wi-Fi
|
|
"signal_wifi_0_bar",
|
|
"network_wifi_1_bar",
|
|
"network_wifi_2_bar",
|
|
"network_wifi_3_bar",
|
|
"signal_wifi_4_bar",
|
|
"signal_wifi_off",
|
|
"signal_wifi_bad",
|
|
# Battery
|
|
"battery_android_frame_1",
|
|
"battery_android_frame_2",
|
|
"battery_android_frame_3",
|
|
"battery_android_frame_4",
|
|
"battery_android_frame_5",
|
|
"battery_android_frame_6",
|
|
"battery_android_frame_full",
|
|
"battery_android_frame_bolt",
|
|
# Bluetooth
|
|
"bluetooth",
|
|
"bluetooth_searching",
|
|
"bluetooth_connected",
|
|
"bluetooth_disabled",
|
|
"usb",
|
|
]
|
|
|
|
# Get more from https://fonts.google.com/icons?icon.set=Material+Symbols&icon.style=Rounded
|
|
launcher_symbol_code_point_names = [
|
|
"apps",
|
|
"folder",
|
|
"settings"
|
|
]
|
|
|
|
# Resolve file path relative to this script so it can be executed from any CWD
|
|
base_dir = os.path.dirname(__file__)
|
|
if base_dir:
|
|
os.chdir(base_dir)
|
|
|
|
codepoints_url = "https://github.com/google/material-design-icons/raw/refs/heads/master/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.codepoints"
|
|
ttf_url = "https://github.com/google/material-design-icons/raw/refs/heads/master/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.ttf"
|
|
|
|
codepoints_filename = "MaterialSymbolsRounded.codepoints"
|
|
ttf_filename = "MaterialSymbolsRounded.ttf"
|
|
subset_ttf_path = os.path.join("..", "..", "Data", "system", "fonts", "MaterialSymbolsRounded.ttf")
|
|
subset_codepoints_path = os.path.join("..", "..", "Data", "system", "fonts", "MaterialSymbolsRounded.codepoints")
|
|
|
|
download_file(codepoints_url, codepoints_filename)
|
|
download_file(ttf_url, ttf_filename)
|
|
|
|
codepoints_map_path = codepoints_filename
|
|
codepoints_map = read_code_points_map(codepoints_map_path)
|
|
|
|
icon_sets = [
|
|
("shared", shared_symbol_code_point_names),
|
|
("statusbar", statusbar_symbol_code_point_names),
|
|
("launcher", launcher_symbol_code_point_names),
|
|
]
|
|
|
|
for variable_name, names in icon_sets:
|
|
generate_icon_names(codepoints_map, names, variable_name)
|
|
|
|
all_names = sorted({name.lower() for _, names in icon_sets for name in names})
|
|
all_codepoints = [int(cp, 16) for cp in get_code_points(codepoints_map, all_names)]
|
|
subset_data = generate_subset_ttf(ttf_filename, all_codepoints, subset_ttf_path)
|
|
codepoints_data = generate_codepoints_file(codepoints_map, all_names, subset_codepoints_path)
|
|
generate_icon_name_lists(icon_sets, subset_data + codepoints_data)
|