!> Text for vkframes: fonts (found by name on a search path, read once) and !> labels (text that always faces the screen). !> !> A font is given as a file name (e.g. 'DejaVuSans.ttf', the default) or a !> path. A name is looked for in, in order: the directories of the !> VKFRAMES_FONT_PATH environment variable (separated by ':' or ';'), the !> `fonts` directory of the active conda/pixi environment (where pixi installs !> DejaVu), and the usual system font directories of macOS, Linux and Windows. module vkf_text use, intrinsic :: iso_c_binding, only: c_ptr, c_null_ptr, c_associated, c_int, c_float, c_double use vkf_math, only: wp use vkf_layer, only: vkf_font_read, vkf_label_create, vkf_label_set_layout use vkf_files, only: find_in_dirs implicit none private character(len=*), parameter, public :: default_font = 'DejaVuSans.ttf' !! the font used when none is given ! vsg::StandardLayout's alignments integer, parameter :: VSG_BASELINE = 0 !! (vertical: the base line) integer, parameter :: VSG_LEFT = 1 !! (horizontal: left) integer, parameter :: VSG_TOP = 1 !! (vertical: top) integer, parameter :: VSG_CENTER = 2 !! horizontal or vertical: center integer, parameter :: VSG_RIGHT = 3 !! (horizontal: right) integer, parameter :: VSG_BOTTOM = 3 !! (vertical: bottom) !> A font read (or looked for) by name. type :: font_entry character(len=:), allocatable :: name !! the font's name as given ('' replaced by default_font) type(c_ptr) :: font = c_null_ptr !! its handle (null: not found or not read) end type font_entry !> Fonts read so far (each is read once; kept for the whole run). type(font_entry), allocatable, save :: fonts(:) public :: find_font, get_font, label_create, label_set_layout contains !> The path of font `name` (a file name or a path), or '' if not found. function find_font(name) result(path) character(len=*), intent(in) :: name !! a file name or a path character(len=:), allocatable :: path character(len=4096) :: env character(len=:), allocatable :: dirs character(len=*), parameter :: system_dirs = & '/System/Library/Fonts/Supplemental;/System/Library/Fonts;/Library/Fonts;' // & ! macOS '/usr/share/fonts/truetype/dejavu;/usr/share/fonts/TTF;/usr/share/fonts/dejavu;' // & ! Linux '/usr/share/fonts/truetype;/usr/share/fonts;/usr/local/share/fonts;' // & 'C:/Windows/Fonts' ! Windows integer :: status logical :: exists path = '' if (len_trim(name) == 0) return if (index(name, '/') > 0 .or. index(name, '\') > 0) then inquire(file=name, exist=exists) if (exists) path = name return end if dirs = '' call get_environment_variable('VKFRAMES_FONT_PATH', env, status=status) if (status == 0) dirs = trim(env)//';' call get_environment_variable('CONDA_PREFIX', env, status=status) if (status == 0) dirs = dirs//trim(env)//'/fonts;'//trim(env)//'/Library/fonts;' dirs = dirs//system_dirs path = find_in_dirs(name, dirs) end function find_font !> The font handle for `name` (found with [[find_font]], read on first use); !> the default font if `name` is ''; null if it cannot be found or read. function get_font(name) result(font) character(len=*), intent(in) :: name !! as find_font's ('': the default font) type(c_ptr) :: font type(font_entry), allocatable :: tmp(:) character(len=:), allocatable :: key, path integer :: k key = trim(name) if (len(key) == 0) key = default_font font = c_null_ptr if (allocated(fonts)) then do k = 1, size(fonts) if (fonts(k)%name == key) then font = fonts(k)%font return end if end do end if path = find_font(key) if (len(path) == 0) then write(*, '(a)') 'vkframes: font '//key//' not found (set VKFRAMES_FONT_PATH)' else font = vkf_font_read(path, len(path, kind=c_int)) end if ! remember the result (also a failure, so it is not looked for again) if (.not. allocated(fonts)) allocate(fonts(0)) allocate(tmp(size(fonts) + 1)) tmp(1:size(fonts)) = fonts tmp(size(tmp))%name = key tmp(size(tmp))%font = font call move_alloc(tmp, fonts) end function get_font !> A label node: `text` at `position` in color `rgba`, in font `font_name` !> ('' for the default), aligned by `alignment` (an ALIGN_* constant of !> vkf_frames). `size` > 0: `size` window units tall; `size` < 0: -size !> scene units tall, but at most `max_size` window units on screen. Null if !> the font is missing. function label_create(text, position, rgba, size, max_size, alignment, font_name) result(node) character(len=*), intent(in) :: text !! the label's text real(wp), intent(in) :: position(3) !! (the coordinates of the node it is put under) real(wp), intent(in) :: rgba(4) !! color [r, g, b, a] (0 to 1) real(wp), intent(in) :: size !! > 0: height in window units; < 0: -height in scene units real(wp), intent(in) :: max_size !! for size < 0: the largest height on screen (window units) integer, intent(in) :: alignment !! an ALIGN_* constant of vkf_frames character(len=*), intent(in) :: font_name !! font file ('' for the default) type(c_ptr) :: node type(c_ptr) :: font integer :: h, v node = c_null_ptr font = get_font(font_name) if (.not. c_associated(font)) return call vsg_alignment(alignment, h, v) node = vkf_label_create(font, text, len(text, kind=c_int), real(rgba, c_float), real(position, c_double), & real(size, c_float), real(max_size, c_float), int(h, c_int), int(v, c_int)) end function label_create !> Change label `node`'s position, size, maximum size and alignment (as !> label_create's), in place. False on failure. logical function label_set_layout(node, position, size, max_size, alignment) result(ok) type(c_ptr), intent(in) :: node !! a label_create node real(wp), intent(in) :: position(3), size, max_size !! as label_create's integer, intent(in) :: alignment !! an ALIGN_* constant of vkf_frames integer :: h, v call vsg_alignment(alignment, h, v) ok = vkf_label_set_layout(node, real(position, c_double), real(size, c_float), real(max_size, c_float), & int(h, c_int), int(v, c_int)) == 0 end function label_set_layout !> The horizontal and vertical alignments of vsg::StandardLayout for an ALIGN_* !> constant (vkf_frames: 0 left-top ... 8 right-bottom, 9 to 11 base line). pure subroutine vsg_alignment(alignment, h, v) integer, intent(in) :: alignment !! an ALIGN_* constant of vkf_frames integer, intent(out) :: h, v !! VSG_* horizontal and vertical alignments integer, parameter :: horizontal(0:11) = [VSG_LEFT, VSG_LEFT, VSG_LEFT, VSG_CENTER, VSG_CENTER, VSG_CENTER, & VSG_RIGHT, VSG_RIGHT, VSG_RIGHT, VSG_LEFT, VSG_CENTER, VSG_RIGHT] integer, parameter :: vertical(0:11) = [VSG_TOP, VSG_CENTER, VSG_BOTTOM, VSG_TOP, VSG_CENTER, VSG_BOTTOM, & VSG_TOP, VSG_CENTER, VSG_BOTTOM, VSG_BASELINE, VSG_BASELINE, VSG_BASELINE] integer :: a a = max(0, min(11, alignment)) h = horizontal(a) v = vertical(a) end subroutine vsg_alignment end module vkf_text