From 198aec84c2f028462efb5031afd3c16722595143 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 3 May 2024 16:53:07 +0530 Subject: [PATCH] Load font variable data on demand --- kitty/fonts/list.py | 20 +++++---- tools/cmd/list_fonts/main.go | 15 +++++++ tools/cmd/list_fonts/types.go | 82 +++++++++++++++++++++++++++++++---- tools/cmd/list_fonts/ui.go | 29 +++++++++++-- 4 files changed, 125 insertions(+), 21 deletions(-) diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py index c61a5868a..62612c0d5 100644 --- a/kitty/fonts/list.py +++ b/kitty/fonts/list.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # License: GPL v3 Copyright: 2017, Kovid Goyal -from typing import Any, BinaryIO, Dict, List, Optional, Sequence +from typing import IO, Any, Dict, List, Optional, Sequence from kitty.constants import is_macos @@ -14,23 +14,21 @@ else: from .fontconfig import list_fonts, prune_family_group -def create_family_groups(monospaced: bool = True, add_variable_data: bool = False) -> Dict[str, List[ListedFont]]: +def create_family_groups(monospaced: bool = True) -> Dict[str, List[ListedFont]]: g: Dict[str, List[ListedFont]] = {} for f in list_fonts(): if not monospaced or f['is_monospace']: g.setdefault(f['family'], []).append(f) - if add_variable_data and f['is_variable']: - f['variable_data'] = get_variable_data_for_descriptor(f['descriptor']) # type: ignore return {k: prune_family_group(v) for k, v in g.items()} def as_json(indent: Optional[int] = None) -> str: import json - groups = create_family_groups(add_variable_data=True) + groups = create_family_groups() return json.dumps(groups, indent=indent) -def handle_io(from_kitten: BinaryIO, to_kitten: BinaryIO) -> None: +def handle_io(from_kitten: IO[bytes], to_kitten: IO[bytes]) -> None: import json global exception_in_io_handler @@ -39,12 +37,15 @@ def handle_io(from_kitten: BinaryIO, to_kitten: BinaryIO) -> None: to_kitten.write(b'\n') to_kitten.flush() - send_to_kitten(create_family_groups(add_variable_data=True)) + send_to_kitten(create_family_groups()) for line in from_kitten: cmd = json.loads(line) action = cmd['action'] - if action == 'ping': - send_to_kitten({'action': 'pong'}) + if action == 'read_variable_data': + ans = [] + for descriptor in cmd['descriptors']: + ans.append(get_variable_data_for_descriptor(descriptor)) + send_to_kitten(ans) def main(argv: Sequence[str]) -> None: @@ -59,6 +60,7 @@ def main(argv: Sequence[str]) -> None: if os.environ.get('KITTY_STDIO_FORWARDED'): pass_fds.append(int(os.environ['KITTY_STDIO_FORWARDED'])) p = subprocess.Popen([kitten_exe(), '__list_fonts__'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, pass_fds=pass_fds) + assert p.stdout is not None and p.stdin is not None try: handle_io(p.stdout, p.stdin) except Exception: diff --git a/tools/cmd/list_fonts/main.go b/tools/cmd/list_fonts/main.go index 024a025a1..c6dc11891 100644 --- a/tools/cmd/list_fonts/main.go +++ b/tools/cmd/list_fonts/main.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "sync" "kitty/tools/cli" "kitty/tools/tty" @@ -36,6 +37,20 @@ func to_kitty(v any) error { return nil } +var query_kitty_lock sync.Mutex + +func query_kitty(action string, cmd map[string]any, result any) error { + query_kitty_lock.Lock() + defer query_kitty_lock.Unlock() + if action != "" { + cmd["action"] = action + if err := to_kitty(cmd); err != nil { + return err + } + } + return json_decode(result) +} + func main() (rc int, err error) { json_decoder = json.NewDecoder(os.Stdin) lp, err := loop.New() diff --git a/tools/cmd/list_fonts/types.go b/tools/cmd/list_fonts/types.go index 928c928c4..63f2d6d46 100644 --- a/tools/cmd/list_fonts/types.go +++ b/tools/cmd/list_fonts/types.go @@ -2,6 +2,7 @@ package list_fonts import ( "fmt" + "sync" ) var _ = fmt.Print @@ -42,6 +43,15 @@ type MultiAxisStyle struct { Values []AxisValue `json:"values"` } +type ListedFont struct { + Family string `json:"family"` + Fullname string `json:"full_name"` + Postscript_name string `json:"postscript_name"` + Is_monospace bool `json:"is_monospace"` + Is_variable bool `json:"is_variable"` + Descriptor map[string]any `json:"descriptor"` +} + type VariableData struct { Axes []VariableAxis `json:"axes"` Named_styles []NamedStyle `json:"named_styles"` @@ -51,12 +61,68 @@ type VariableData struct { Multi_axis_styles []MultiAxisStyle `json:"multi_axis_styles"` } -type ListedFont struct { - Family string `json:"family"` - Fullname string `json:"full_name"` - Postscript_name string `json:"postscript_name"` - Is_monospace bool `json:"is_monospace"` - Is_variable bool `json:"is_variable"` - Variable_data VariableData `json:"variable_data"` - Descriptor map[string]any `json:"descriptor"` +var variable_data_cache map[string]VariableData +var variable_data_cache_mutex sync.Mutex + +func (f ListedFont) cache_key() string { + key := f.Postscript_name + if key == "" { + key = "path:" + f.Descriptor["path"].(string) + } else { + key = "psname:" + key + } + return key +} + +func ensure_variable_data_for_fonts(fonts ...ListedFont) error { + descriptors := make([]map[string]any, 0, len(fonts)) + keys := make([]string, 0, len(fonts)) + variable_data_cache_mutex.Lock() + for _, f := range fonts { + key := f.cache_key() + if _, found := variable_data_cache[key]; !found { + descriptors = append(descriptors, f.Descriptor) + keys = append(keys, key) + } + } + variable_data_cache_mutex.Unlock() + var data []VariableData + if err := query_kitty("read_variable_data", map[string]any{"descriptors": descriptors}, &data); err != nil { + return err + } + variable_data_cache_mutex.Lock() + for i, key := range keys { + variable_data_cache[key] = data[i] + } + variable_data_cache_mutex.Unlock() + return nil +} + +func initialize_variable_data_cache() { + variable_data_cache = make(map[string]VariableData) +} + +func _cached_vd(key string) (ans VariableData, found bool) { + variable_data_cache_mutex.Lock() + defer variable_data_cache_mutex.Unlock() + ans, found = variable_data_cache[key] + return +} + +func variable_data_for(f ListedFont) VariableData { + key := f.cache_key() + ans, found := _cached_vd(key) + if found { + return ans + } + if err := ensure_variable_data_for_fonts(f); err != nil { + panic(err) + } + ans, found = _cached_vd(key) + return ans +} + +func has_variable_data_for_font(font ListedFont) bool { + _, found := _cached_vd(font.cache_key()) + return found } diff --git a/tools/cmd/list_fonts/ui.go b/tools/cmd/list_fonts/ui.go index 1e396467a..115fa95c0 100644 --- a/tools/cmd/list_fonts/ui.go +++ b/tools/cmd/list_fonts/ui.go @@ -31,8 +31,9 @@ type handler struct { err_in_worker_thread error // Listing - rl *readline.Readline - family_list FamilyList + rl *readline.Readline + family_list FamilyList + variable_data_requested_for *utils.Set[string] } func (h *handler) set_worker_error(err error) { @@ -77,6 +78,22 @@ func (h *handler) draw_family_summary(start_x int, sz loop.ScreenSize) (err erro h.lp.SprintStyled("fg=green bold", center_string(family, int(sz.WidthCells)-start_x)), "", } + fonts := h.fonts[family] + if len(fonts) == 0 { + return fmt.Errorf("The family: %s has no fonts", family) + } + if has_variable_data_for_font(fonts[0]) { + } else { + lines = append(lines, "Reading font data, please wait…") + key := fonts[0].cache_key() + if !h.variable_data_requested_for.Has(key) { + h.variable_data_requested_for.Add(key) + go func() { + h.set_worker_error(ensure_variable_data_for_fonts(fonts...)) + h.lp.WakeupMainThread() + }() + } + } for i, line := range lines { if i >= int(sz.HeightCells)-1 { @@ -115,7 +132,7 @@ func (h *handler) draw_listing_screen() (err error) { h.lp.Println(SEPARATOR) } if h.family_list.Len() > 0 { - if err = h.draw_family_summary(mw+2, sz); err != nil { + if err = h.draw_family_summary(mw+3, sz); err != nil { return err } } @@ -207,9 +224,11 @@ func (h *handler) handle_listing_text(text string, from_key_event bool, in_brack func (h *handler) initialize() { h.lp.SetCursorVisible(false) h.rl = readline.New(h.lp, readline.RlInit{DontMarkPrompts: true, Prompt: "Family: "}) + h.variable_data_requested_for = utils.NewSet[string](256) h.draw_screen() + initialize_variable_data_cache() go func() { - h.set_worker_error(json_decode(&h.fonts)) + h.set_worker_error(query_kitty("", nil, &h.fonts)) h.lp.WakeupMainThread() }() } @@ -243,6 +262,8 @@ func (h *handler) on_wakeup() (err error) { h.state = LISTING_FAMILIES h.family_list.UpdateFamilies(utils.StableSortWithKey(maps.Keys(h.fonts), strings.ToLower)) return h.draw_screen() + case LISTING_FAMILIES: + return h.draw_screen() } return }