mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-10-17 10:55:06 +00:00
259 lines
9.4 KiB
Python
Executable File
259 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",
|
|
"location_off",
|
|
# 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)
|