From 7fbd856bf81e3a8511fb3a430acb62c893b4de8d Mon Sep 17 00:00:00 2001 From: Larpon Date: Thu, 13 Jan 2022 11:16:18 +0100 Subject: [PATCH] os: add font module, move from gg (#13144) --- examples/gg/worker_thread.v | 1 - examples/hot_reload/bounce.v | 1 - examples/hot_reload/graph.v | 1 - vlib/gg/gg.c.v | 3 +- vlib/gg/text_rendering.c.v | 117 ++---------------------------- vlib/gg/text_rendering.v | 7 -- vlib/os/font/font.v | 135 +++++++++++++++++++++++++++++++++++ 7 files changed, 141 insertions(+), 124 deletions(-) create mode 100644 vlib/os/font/font.v diff --git a/examples/gg/worker_thread.v b/examples/gg/worker_thread.v index a2c9852ab8..04d0f175ad 100644 --- a/examples/gg/worker_thread.v +++ b/examples/gg/worker_thread.v @@ -36,7 +36,6 @@ fn main() { bg_color: bg_color frame_fn: frame init_fn: init - font_path: gg.system_font_path() ) app.gg.run() } diff --git a/examples/hot_reload/bounce.v b/examples/hot_reload/bounce.v index b85642d946..ece66cc91f 100644 --- a/examples/hot_reload/bounce.v +++ b/examples/hot_reload/bounce.v @@ -42,7 +42,6 @@ fn main() { create_window: true frame_fn: frame bg_color: gx.white - font_path: gg.system_font_path() ) // window.onkeydown(key_down) println('Starting the game loop...') diff --git a/examples/hot_reload/graph.v b/examples/hot_reload/graph.v index 1707278d6e..907320aa7f 100644 --- a/examples/hot_reload/graph.v +++ b/examples/hot_reload/graph.v @@ -29,7 +29,6 @@ fn main() { frame_fn: frame resizable: true bg_color: gx.white - font_path: gg.system_font_path() ) context.gg.run() } diff --git a/vlib/gg/gg.c.v b/vlib/gg/gg.c.v index c97ba48439..5022884142 100644 --- a/vlib/gg/gg.c.v +++ b/vlib/gg/gg.c.v @@ -4,6 +4,7 @@ module gg import os +import os.font import gx import sokol import sokol.sapp @@ -182,7 +183,7 @@ fn gg_init_sokol_window(user_data voidptr) { ) or { panic(err) } g.font_inited = true } else { - sfont := system_font_path() + sfont := font.default() if g.config.font_path != '' { eprintln('font file "$g.config.font_path" does not exist, the system font ($sfont) was used instead.') } diff --git a/vlib/gg/text_rendering.c.v b/vlib/gg/text_rendering.c.v index 0a0f2582e7..382db5b7fe 100644 --- a/vlib/gg/text_rendering.c.v +++ b/vlib/gg/text_rendering.c.v @@ -7,6 +7,7 @@ import sokol.sfons import sokol.sgl import gx import os +import os.font struct FT { pub: @@ -83,20 +84,20 @@ fn new_ft(c FTConfig) ?&FT { mut bold_path := if c.custom_bold_font_path != '' { c.custom_bold_font_path } else { - get_font_path_variant(c.font_path, .bold) + font.get_path_variant(c.font_path, .bold) } bytes_bold := os.read_bytes(bold_path) or { debug_font_println('failed to load font "$bold_path"') bold_path = c.font_path bytes } - mut mono_path := get_font_path_variant(c.font_path, .mono) + mut mono_path := font.get_path_variant(c.font_path, .mono) bytes_mono := os.read_bytes(mono_path) or { debug_font_println('failed to load font "$mono_path"') mono_path = c.font_path bytes } - mut italic_path := get_font_path_variant(c.font_path, .italic) + mut italic_path := font.get_path_variant(c.font_path, .italic) bytes_italic := os.read_bytes(italic_path) or { debug_font_println('failed to load font "$italic_path"') italic_path = c.font_path @@ -228,113 +229,3 @@ pub fn (ctx &Context) text_size(s string) (int, int) { ctx.ft.fons.text_bounds(0, 0, s, &buf[0]) return int((buf[2] - buf[0]) / ctx.scale), int((buf[3] - buf[1]) / ctx.scale) } - -pub fn system_font_path() string { - env_font := os.getenv('VUI_FONT') - if env_font != '' && os.exists(env_font) { - return env_font - } - $if windows { - debug_font_println('Using font "C:\\Windows\\Fonts\\arial.ttf"') - return 'C:\\Windows\\Fonts\\arial.ttf' - } - $if macos { - fonts := ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf', - '/Library/Fonts/Arial.ttf'] - for font in fonts { - if os.is_file(font) { - debug_font_println('Using font "$font"') - return font - } - } - } - $if android { - xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml', - '/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml', - '/etc/fallback_fonts.xml'] - font_locations := ['/system/fonts', '/data/fonts'] - for xml_file in xml_files { - if os.is_file(xml_file) && os.is_readable(xml_file) { - xml := os.read_file(xml_file) or { continue } - lines := xml.split('\n') - mut candidate_font := '' - for line in lines { - if line.contains('').all_before('<').trim(' \n\t\r') - if candidate_font.contains('.ttf') { - for location in font_locations { - candidate_path := os.join_path(location, candidate_font) - if os.is_file(candidate_path) && os.is_readable(candidate_path) { - debug_font_println('Using font "$candidate_path"') - return candidate_path - } - } - } - } - } - } - } - } - mut fm := os.execute("fc-match --format='%{file}\n' -s") - if fm.exit_code == 0 { - lines := fm.output.split('\n') - for l in lines { - if !l.contains('.ttc') { - debug_font_println('Using font "$l"') - return l - } - } - } else { - panic('fc-match failed to fetch system font') - } - panic('failed to init the font') -} - -fn get_font_path_variant(font_path string, variant FontVariant) string { - // TODO: find some way to make this shorter and more eye-pleasant - // NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work - mut file := os.file_name(font_path) - mut fpath := font_path.replace(file, '') - file = file.replace('.ttf', '') - - match variant { - .normal {} - .bold { - if fpath.ends_with('-Regular') { - file = file.replace('-Regular', '-Bold') - } else if file.starts_with('DejaVuSans') { - file += '-Bold' - } else if file.to_lower().starts_with('arial') { - file += 'bd' - } else { - file += '-bold' - } - $if macos { - if os.exists('SFNS-bold') { - file = 'SFNS-bold' - } - } - } - .italic { - if file.ends_with('-Regular') { - file = file.replace('-Regular', '-Italic') - } else if file.starts_with('DejaVuSans') { - file += '-Oblique' - } else if file.to_lower().starts_with('arial') { - file += 'i' - } else { - file += 'Italic' - } - } - .mono { - if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') { - file = file.replace('-Regular', 'Mono-Regular') - } else if file.to_lower().starts_with('arial') { - // Arial has no mono variant - } else { - file += 'Mono' - } - } - } - return fpath + file + '.ttf' -} diff --git a/vlib/gg/text_rendering.v b/vlib/gg/text_rendering.v index c2db6c00cb..f148d9e3d4 100644 --- a/vlib/gg/text_rendering.v +++ b/vlib/gg/text_rendering.v @@ -4,13 +4,6 @@ module gg import gx -enum FontVariant { - normal = 0 - bold - mono - italic -} - struct FTConfig { font_path string custom_bold_font_path string diff --git a/vlib/os/font/font.v b/vlib/os/font/font.v new file mode 100644 index 0000000000..cd0345afee --- /dev/null +++ b/vlib/os/font/font.v @@ -0,0 +1,135 @@ +// Copyright (c) 2019-2022 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license that can be found in the LICENSE file. + +module font + +import os + +// Variant enumerates the different variants a font can have. +pub enum Variant { + normal = 0 + bold + mono + italic +} + +[if debug_font ?] +fn debug_font_println(s string) { + println(s) +} + +// default returns an absolute path the default system TTF font. +// If the env variable `VUI_FONT` is set this is used instead. +// NOTE that, in some cases, the function calls out to external OS programs +// so running this in a hot loop is not advised. +pub fn default() string { + env_font := os.getenv('VUI_FONT') + if env_font != '' && os.exists(env_font) { + return env_font + } + $if windows { + debug_font_println('Using font "C:\\Windows\\Fonts\\arial.ttf"') + return 'C:\\Windows\\Fonts\\arial.ttf' + } + $if macos { + fonts := ['/System/Library/Fonts/SFNS.ttf', '/System/Library/Fonts/SFNSText.ttf', + '/Library/Fonts/Arial.ttf'] + for font in fonts { + if os.is_file(font) { + debug_font_println('Using font "$font"') + return font + } + } + } + $if android { + xml_files := ['/system/etc/system_fonts.xml', '/system/etc/fonts.xml', + '/etc/system_fonts.xml', '/etc/fonts.xml', '/data/fonts/fonts.xml', + '/etc/fallback_fonts.xml'] + font_locations := ['/system/fonts', '/data/fonts'] + for xml_file in xml_files { + if os.is_file(xml_file) && os.is_readable(xml_file) { + xml := os.read_file(xml_file) or { continue } + lines := xml.split('\n') + mut candidate_font := '' + for line in lines { + if line.contains('').all_before('<').trim(' \n\t\r') + if candidate_font.contains('.ttf') { + for location in font_locations { + candidate_path := os.join_path(location, candidate_font) + if os.is_file(candidate_path) && os.is_readable(candidate_path) { + debug_font_println('Using font "$candidate_path"') + return candidate_path + } + } + } + } + } + } + } + } + mut fm := os.execute("fc-match --format='%{file}\n' -s") + if fm.exit_code == 0 { + lines := fm.output.split('\n') + for l in lines { + if !l.contains('.ttc') { + debug_font_println('Using font "$l"') + return l + } + } + } else { + panic('fc-match failed to fetch system font') + } + panic('failed to init the font') +} + +// get_path_variant returns the `font_path` file name replaced with the +// file name of the font's `variant` version if it exists. +pub fn get_path_variant(font_path string, variant Variant) string { + // TODO: find some way to make this shorter and more eye-pleasant + // NotoSans, LiberationSans, DejaVuSans, Arial and SFNS should work + mut file := os.file_name(font_path) + mut fpath := font_path.replace(file, '') + file = file.replace('.ttf', '') + + match variant { + .normal {} + .bold { + if fpath.ends_with('-Regular') { + file = file.replace('-Regular', '-Bold') + } else if file.starts_with('DejaVuSans') { + file += '-Bold' + } else if file.to_lower().starts_with('arial') { + file += 'bd' + } else { + file += '-bold' + } + $if macos { + if os.exists('SFNS-bold') { + file = 'SFNS-bold' + } + } + } + .italic { + if file.ends_with('-Regular') { + file = file.replace('-Regular', '-Italic') + } else if file.starts_with('DejaVuSans') { + file += '-Oblique' + } else if file.to_lower().starts_with('arial') { + file += 'i' + } else { + file += 'Italic' + } + } + .mono { + if !file.ends_with('Mono-Regular') && file.ends_with('-Regular') { + file = file.replace('-Regular', 'Mono-Regular') + } else if file.to_lower().starts_with('arial') { + // Arial has no mono variant + } else { + file += 'Mono' + } + } + } + return fpath + file + '.ttf' +}