Load font variable data on demand
This commit is contained in:
parent
6baa915058
commit
3b80ee0981
4 changed files with 125 additions and 21 deletions
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue