diff --git a/.gitignore b/.gitignore index e11d046fd..600c47b16 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.idea/ /.kdev4/ /.vs/ +.venv/ /cmake-build-*/ /build*/ diff --git a/dist/fonts/move_private_use_area.py b/dist/fonts/move_private_use_area.py new file mode 100644 index 000000000..e58b2d5c9 --- /dev/null +++ b/dist/fonts/move_private_use_area.py @@ -0,0 +1,69 @@ +from fontTools.ttLib import TTFont +from fontTools.ttLib.tables._c_m_a_p import CmapSubtable +import argparse + +# Default PUAs +SOURCE_PUA_START = 0xEA00 +SOURCE_PUA_END = 0x100F2 +TARGET_PUA_START = 0xF0000 + +def move_pua_glyphs(input_font_path, output_font_path): + font = TTFont(input_font_path) + + cmap_table = font['cmap'] + glyph_set = font.getGlyphSet() + + # Track moved glyphs + moved = 0 + new_mapping = {} + + # Collect original mappings in the PUA + for cmap in cmap_table.tables: + if cmap.isUnicode(): + for codepoint, glyph_name in cmap.cmap.items(): + if SOURCE_PUA_START <= codepoint <= SOURCE_PUA_END: + offset = codepoint - SOURCE_PUA_START + new_codepoint = TARGET_PUA_START + offset + new_mapping[new_codepoint] = glyph_name + moved += 1 + + if moved == 0: + print("No glyphs found in the source Private Use Area.") + return + + # Remove old PUA entries from existing cmap subtables + for cmap in cmap_table.tables: + if cmap.isUnicode(): + cmap.cmap = { + cp: gn for cp, gn in cmap.cmap.items() + if not (SOURCE_PUA_START <= cp <= SOURCE_PUA_END) + } + + # Create or update a format 12 cmap subtable + found_format12 = False + for cmap in cmap_table.tables: + if cmap.format == 12 and cmap.platformID == 3 and cmap.platEncID in (10, 1): + cmap.cmap.update(new_mapping) + found_format12 = True + break + + if not found_format12: + # Create a new format 12 subtable + cmap12 = CmapSubtable.newSubtable(12) + cmap12.platformID = 3 + cmap12.platEncID = 10 # UCS-4 + cmap12.language = 0 + cmap12.cmap = new_mapping + cmap_table.tables.append(cmap12) + + print(f"Moved {moved} glyphs from U+{SOURCE_PUA_START:X}–U+{SOURCE_PUA_END:X} to U+{TARGET_PUA_START:X}+") + font.save(output_font_path) + print(f"Saved modified font to {output_font_path}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Move PUA glyphs in a TTF file to another Unicode range.") + parser.add_argument("input", help="Input TTF file path") + parser.add_argument("output", help="Output TTF file path") + + args = parser.parse_args() + move_pua_glyphs(args.input, args.output) diff --git a/dist/fonts/ttf_to_header_file.py b/dist/fonts/ttf_to_header_file.py new file mode 100644 index 000000000..adcca1ed2 --- /dev/null +++ b/dist/fonts/ttf_to_header_file.py @@ -0,0 +1,60 @@ +import argparse +from fontTools.ttLib import TTFont +import os + +def unicode_to_utf8_escape(codepoint): + return ''.join([f'\\x{b:02x}' for b in chr(codepoint).encode('utf-8')]) + +def format_macro_name(prefix, glyph_name): + # Convert names like 'repo-forked' -> 'ICON_VS_REPO_FORKED' + return "ICON_" + prefix + "_" + glyph_name.upper().replace('-', '_') + +def generate_font_header(font_path, output_path, font_macro_name, font_file_macro): + font = TTFont(font_path) + + # Use cmap to get Unicode to glyph mapping + codepoint_to_names = {} + for table in font["cmap"].tables: + if table.isUnicode(): + for codepoint, glyph_name in table.cmap.items(): + codepoint_to_names.setdefault(codepoint, []).append(glyph_name) + + if not codepoint_to_names: + print("No Unicode-mapped glyphs found in the font.") + return + + # Remove any glyph that is lower than 0xFF + codepoint_to_names = {cp: names for cp, names in codepoint_to_names.items() if cp >= 0xFF} + + min_cp = min(codepoint_to_names) + max_cp = max(codepoint_to_names) + + with open(output_path, "w", encoding="utf-8") as out: + out.write("#pragma once\n\n") + out.write(f'#define FONT_ICON_FILE_NAME_{font_macro_name} "{font_file_macro}"\n\n') + out.write(f"#define ICON_MIN_{font_macro_name} 0x{min_cp:04x}\n") + out.write(f"#define ICON_MAX_16_{font_macro_name} 0x{max_cp:04x}\n") + out.write(f"#define ICON_MAX_{font_macro_name} 0x{max_cp:04x}\n") + + written = set() + for codepoint in sorted(codepoint_to_names): + utf8 = unicode_to_utf8_escape(codepoint) + comment = f"// U+{codepoint:04X}" + glyph_names = sorted(set(codepoint_to_names[codepoint])) + for i, glyph_name in enumerate(glyph_names): + macro = format_macro_name(font_macro_name, glyph_name) + if macro in written: + continue + out.write(f"#define {macro} \"{utf8}\"\t{comment}\n") + written.add(macro) + + print(f"Header generated at {output_path}") + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate C header file from TTF glyphs.") + parser.add_argument("font", help="Input .ttf font file") + parser.add_argument("output", help="Output .h file") + parser.add_argument("macro_name", help="Macro prefix") + args = parser.parse_args() + + generate_font_header(args.font, args.output, args.macro_name, os.path.basename(args.font))