Load font variable data on demand

This commit is contained in:
Kovid Goyal 2024-05-03 16:53:07 +05:30
parent 6baa915058
commit 3b80ee0981
No known key found for this signature in database
GPG key ID: 06BC317B515ACE7C
4 changed files with 125 additions and 21 deletions

View file

@ -1,7 +1,7 @@
#!/usr/bin/env python
# License: GPL v3 Copyright: 2017, Kovid Goyal <kovid at kovidgoyal.net>
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 @@
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 send_to_kitten(x: Any) -> 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:

View file

@ -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()

View file

@ -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
}

View file

@ -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
}