#!/usr/bin/env python3 """ Embeds every file of an asset folder as a C byte array. Usage: generate-assets.py Writes generated_assets.h and generated_assets.c. A file "fonts/AdwaitaSans.ttf" becomes the symbols asset_fonts_AdwaitaSans_ttf (the bytes) and asset_fonts_AdwaitaSans_ttf_size (the byte count). The size is also available as the constant asset_fonts_AdwaitaSans_ttf_SIZE for use in constant initializers. Text files get an extra terminating 0 byte that is not part of the size, so they can be used as C strings. Output files are only rewritten when their content changes, to avoid needless recompiles. """ import os import re import sys TEXT_EXTENSIONS = {".pem", ".csv", ".html", ".json", ".codepoints", ".i18n", ".txt"} def symbol_name(relative_path: str) -> str: return "asset_" + re.sub(r"[^A-Za-z0-9]", "_", relative_path) def write_if_changed(path: str, content: str): if os.path.exists(path): with open(path, "r", encoding="utf-8", newline="") as file: if file.read() == content: return with open(path, "w", encoding="utf-8", newline="") as file: file.write(content) def main(): if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} ") sys.exit(1) asset_folder = os.path.abspath(sys.argv[1]) output_folder = os.path.abspath(sys.argv[2]) os.makedirs(output_folder, exist_ok=True) relative_paths = [] for directory, _, file_names in os.walk(asset_folder): for file_name in file_names: relative_paths.append(os.path.relpath(os.path.join(directory, file_name), asset_folder).replace(os.sep, "/")) relative_paths.sort() header = [ "// Generated by Buildscripts/generate-assets.py. Do not edit.", "#pragma once", "", "#include ", "#include ", "", "#ifdef __cplusplus", 'extern "C" {', "#endif", "", ] source = [ "// Generated by Buildscripts/generate-assets.py. Do not edit.", '#include "generated_assets.h"', "", ] for relative_path in relative_paths: with open(os.path.join(asset_folder, relative_path), "rb") as file: data = file.read() symbol = symbol_name(relative_path) size = len(data) if os.path.splitext(relative_path)[1].lower() in TEXT_EXTENSIONS: data += b"\0" header.append(f"// {relative_path}") header.append(f"extern const uint8_t {symbol}[];") header.append(f"extern const size_t {symbol}_size;") header.append(f"#define {symbol}_SIZE {size}") source.append(f"// {relative_path}") source.append(f"const uint8_t {symbol}[] = {{") for offset in range(0, len(data), 24): source.append(" " + ", ".join(f"0x{byte:02x}" for byte in data[offset:offset + 24]) + ",") if len(data) == 0: source.append(" 0") source.append("};") source.append(f"const size_t {symbol}_size = {size};") source.append("") header += ["", "#ifdef __cplusplus", "}", "#endif", ""] write_if_changed(os.path.join(output_folder, "generated_assets.h"), "\n".join(header)) write_if_changed(os.path.join(output_folder, "generated_assets.c"), "\n".join(source)) if __name__ == "__main__": main()