mcap/src/base/fonts.jai

160 lines
6.5 KiB
Plaintext

Font_File :: struct {
font_name: string;
font_data: string;
}
// The fonts are compiled into the executable. The paths are relative to the working directory of
// first.jai; assets/fonts holds a few more to swap in. #run,host reads the file on the build
// machine even when cross-compiling, and sees nothing declared outside its own block.
OpenSans_Bold :: Font_File.{"OpenSans-Bold", #run,host -> string { #import "File"; return read_entire_file("assets/fonts/OpenSans-Bold.ttf"); }};
OpenSans_Regular :: Font_File.{"OpenSans-Regular", #run,host -> string { #import "File"; return read_entire_file("assets/fonts/OpenSans-Regular.ttf"); }};
RobotoMono_Regular :: Font_File.{"RobotoMono-Regular", #run,host -> string { #import "File"; return read_entire_file("assets/fonts/RobotoMono-Regular.ttf"); }};
default_global_font_size :: 12.0;
default_header_font_size :: 0.5 * default_global_font_size + default_global_font_size;
font_data_default: *ImGui.ImFont;
alternate_font: *ImGui.ImFont;
header_font: *ImGui.ImFont;
push_imgui_font :: (font: *ImGui.ImFont, scale: float32) #expand {
style := ImGui.GetStyle();
ImGui.PushFont(font, style.FontSizeBase * scale);
`defer ImGui.PopFont();
}
app_imgui_load_fonts :: () {
io := ImGui.GetIO();
cjk_fonts_load();
{ font_config: ImGui.ImFontConfig;
font_config.Constructor(*font_config); // *sigh*
// By default, ImGui takes ownership of the font data and attempts to free it, resulting in a crash,
// so we have to explicitly tell it to not do this.
font_config.FontDataOwnedByAtlas = false;
memcpy(font_config.Name.data, RobotoMono_Regular.font_name.data, RobotoMono_Regular.font_name.count);
font_data_default = ImGui.IO.Fonts.AddFontFromMemoryTTF(io.Fonts, RobotoMono_Regular.font_data.data,
xx RobotoMono_Regular.font_data.count, default_global_font_size, *font_config);
merge_cjk_fallbacks(font_data_default);
}
{ // Header font for menu
font_config: ImGui.ImFontConfig;
font_config.Constructor(*font_config);
font_config.FontDataOwnedByAtlas = false;
memcpy(font_config.Name.data, OpenSans_Bold.font_name.data, OpenSans_Bold.font_name.count);
header_font = ImGui.IO.Fonts.AddFontFromMemoryTTF(io.Fonts, OpenSans_Bold.font_data.data,
xx OpenSans_Bold.font_data.count, default_header_font_size, *font_config);
merge_cjk_fallbacks(header_font);
}
// Add another font:
{ font_config: ImGui.ImFontConfig;
font_config.Constructor(*font_config);
font_config.FontDataOwnedByAtlas = false;
memcpy(font_config.Name.data, OpenSans_Regular.font_name.data, OpenSans_Regular.font_name.count);
alternate_font = ImGui.IO.Fonts.AddFontFromMemoryTTF(io.Fonts, OpenSans_Regular.font_data.data,
xx OpenSans_Regular.font_data.count, default_global_font_size, *font_config);
merge_cjk_fallbacks(alternate_font);
}
assert(font_data_default != null);
// Set global default font:
io.FontDefault = font_data_default;
}
Cjk_Script :: enum u8 { SIMPLIFIED; TRADITIONAL; JAPANESE; KOREAN; }
Cjk_Font :: struct {
script: Cjk_Script;
path: string;
name: string; // for the log and ImFontConfig.Name
face: u32; // index within a .ttc
data: string; // once loaded: mapped or read, kept for the life of the atlas
}
cjk_fonts: [..] Cjk_Font; // in fallback order
cjk_candidate :: (list: *[..] Cjk_Font, script: Cjk_Script, path: string, name: string, face: u32 = 0) {
array_add(list, .{script = script, path = path, name = name, face = face});
}
// The scripts share their Han characters and the first merged font that has a glyph draws it, so
// the user's language picks which comes first.
cjk_fallback_order :: (language: string) -> [4] Cjk_Script {
subtags := split(to_lower_copy(replace(language, "_", "-",, temp),, temp), "-",, temp);
primary := ifx subtags.count then subtags[0] else "";
if primary == "ja" return Cjk_Script.[.JAPANESE, .SIMPLIFIED, .TRADITIONAL, .KOREAN];
if primary == "ko" return Cjk_Script.[.KOREAN, .SIMPLIFIED, .TRADITIONAL, .JAPANESE];
if primary == "zh" {
for subtags if it == "hant" || it == "tw" || it == "hk" || it == "mo" return Cjk_Script.[.TRADITIONAL, .SIMPLIFIED, .JAPANESE, .KOREAN];
}
return Cjk_Script.[.SIMPLIFIED, .TRADITIONAL, .JAPANESE, .KOREAN];
}
// One font per script, the first candidate found. A file several scripts share is loaded once.
cjk_fonts_load :: () {
push_allocator(context.default_allocator);
language := platform_preferred_language();
candidates := platform_cjk_font_candidates();
for script: cjk_fallback_order(language) {
for candidate: candidates {
if candidate.script != script || !file_exists(candidate.path) continue;
font := candidate;
for cjk_fonts if it.path == font.path font.data = it.data;
if !font.data {
ok: bool;
font.data, ok = platform_map_file_read_only(font.path);
if !ok {
font.data, ok = read_entire_file(font.path);
if !ok continue;
log("[fonts] Could not map %; read it into memory instead.", font.path);
}
}
font.path = copy_string(font.path);
font.name = copy_string(font.name);
array_add(*cjk_fonts, font);
break;
}
}
names: [..] string;
names.allocator = temp;
for cjk_fonts array_add(*names, it.name);
log("[fonts] CJK fallback for %: %.", ifx language then language else "an unknown language",
ifx names.count then join(..names, ", ",, temp) else "none found");
}
// Since ImGui 1.92 a merged font is only asked for glyphs the ones before it lack, as they are drawn.
merge_cjk_fallbacks :: (dst: *ImGui.ImFont) {
io := ImGui.GetIO();
for cjk_fonts {
font_config: ImGui.ImFontConfig;
font_config.Constructor(*font_config);
font_config.FontDataOwnedByAtlas = false;
font_config.MergeMode = true;
font_config.DstFont = dst;
font_config.FontNo = it.face;
memcpy(font_config.Name.data, it.name.data, min(it.name.count, font_config.Name.count - 1));
ImGui.IO.Fonts.AddFontFromMemoryTTF(io.Fonts, it.data.data, xx it.data.count, 0, *font_config);
}
}
DEFAULT_FONT_SCALE_INCREMENT :: 0.05;
FONT_SCALE_MIN :: 0.5; FONT_SCALE_MAX :: 4.0;
imgui_decrease_font_scale :: (scale_increment:=DEFAULT_FONT_SCALE_INCREMENT) {
style := ImGui.GetStyle();
style.FontScaleMain = clamp(style.FontScaleMain - scale_increment,
FONT_SCALE_MIN, FONT_SCALE_MAX);
}
imgui_increase_font_scale :: (scale_increment:=DEFAULT_FONT_SCALE_INCREMENT) {
style := ImGui.GetStyle();
style.FontScaleMain = clamp(style.FontScaleMain + scale_increment,
FONT_SCALE_MIN, FONT_SCALE_MAX);
}