face.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. package choose_fonts
  2. import (
  3. "fmt"
  4. "maps"
  5. "math"
  6. "slices"
  7. "strconv"
  8. "strings"
  9. "sync"
  10. "kitty/tools/tui"
  11. "kitty/tools/tui/loop"
  12. "kitty/tools/utils"
  13. "kitty/tools/wcswidth"
  14. )
  15. var _ = fmt.Print
  16. type face_panel struct {
  17. handler *handler
  18. family, which string
  19. settings faces_settings
  20. current_preview *RenderedSampleTransmit
  21. current_preview_key faces_preview_key
  22. preview_cache map[faces_preview_key]map[string]RenderedSampleTransmit
  23. preview_cache_mutex sync.Mutex
  24. }
  25. // Create a new FontSpec that keeps features and axis values and named styles
  26. // same as the current setting. Names are all reset apart from style name.
  27. func (self *face_panel) new_font_spec() (*FontSpec, error) {
  28. fs, err := NewFontSpec(self.get(), self.current_preview.Features)
  29. if err != nil {
  30. return nil, err
  31. }
  32. if fs.system.val == "auto" {
  33. if fs, err = NewFontSpec(self.current_preview.Spec, self.current_preview.Features); err != nil {
  34. return nil, err
  35. }
  36. }
  37. // reset these selectors as we will be using some style/axis based selector instead
  38. fs.family = settable_string{self.family, true}
  39. fs.postscript_name = settable_string{}
  40. fs.full_name = settable_string{}
  41. if len(self.current_preview.Variable_data.Axes) > 0 {
  42. fs.variable_name = settable_string{self.current_preview.Variable_data.Variations_postscript_name_prefix, true}
  43. } else {
  44. fs.variable_name = settable_string{}
  45. }
  46. return &fs, nil
  47. }
  48. func (self *face_panel) set_variable_spec(named_style string, axis_overrides map[string]float64) error {
  49. fs, err := self.new_font_spec()
  50. if err != nil {
  51. return err
  52. }
  53. if axis_overrides != nil {
  54. axis_values := self.current_preview.current_axis_values()
  55. maps.Copy(axis_values, axis_overrides)
  56. fs.axes = axis_values
  57. fs.style = settable_string{"", false}
  58. } else if named_style != "" {
  59. fs.style = settable_string{named_style, true}
  60. fs.axes = nil
  61. }
  62. self.set(fs.String())
  63. return nil
  64. }
  65. func (self *face_panel) set_style(named_style string) error {
  66. fs, err := self.new_font_spec()
  67. if err != nil {
  68. return err
  69. }
  70. fs.style = settable_string{named_style, true}
  71. self.set(fs.String())
  72. return nil
  73. }
  74. func (self *face_panel) render_lines(start_y int, lines ...string) (y int) {
  75. sz, _ := self.handler.lp.ScreenSize()
  76. _, y, str := self.handler.render_lines.InRectangle(lines, 0, start_y, int(sz.WidthCells), int(sz.HeightCells)-y, &self.handler.mouse_state, self.on_click)
  77. self.handler.lp.QueueWriteString(str)
  78. return
  79. }
  80. const current_val_style = "fg=cyan bold"
  81. const control_name_style = "fg=yellow bright bold"
  82. func (self *face_panel) draw_axis(sz loop.ScreenSize, y int, ax VariableAxis, axis_value float64) int {
  83. lp := self.handler.lp
  84. buf := strings.Builder{}
  85. buf.WriteString(fmt.Sprintf("%s: ", lp.SprintStyled(control_name_style, utils.IfElse(ax.Strid != "", ax.Strid, ax.Tag))))
  86. num_of_cells := int(sz.WidthCells) - wcswidth.Stringwidth(buf.String())
  87. if num_of_cells < 5 {
  88. return y
  89. }
  90. frac := (min(axis_value, ax.Maximum) - ax.Minimum) / (ax.Maximum - ax.Minimum)
  91. current_cell := int(math.Floor(frac * float64(num_of_cells-1)))
  92. for i := 0; i < num_of_cells; i++ {
  93. buf.WriteString(utils.IfElse(i == current_cell, lp.SprintStyled(current_val_style, `⬤`),
  94. tui.InternalHyperlink("•", fmt.Sprintf("axis:%d/%d:%s", i, num_of_cells-1, ax.Tag))))
  95. }
  96. return self.render_lines(y, buf.String())
  97. }
  98. func is_current_named_style(style_group_name, style_name string, vd VariableData, ns NamedStyle) bool {
  99. for _, dax := range vd.Design_axes {
  100. if dax.Name == style_group_name {
  101. if val, found := ns.Axis_values[dax.Tag]; found {
  102. for _, v := range dax.Values {
  103. if v.Value == val {
  104. return v.Name == style_name
  105. }
  106. }
  107. }
  108. break
  109. }
  110. }
  111. return false
  112. }
  113. func (self *face_panel) draw_variable_fine_tune(sz loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) {
  114. s := styles_for_variable_data(preview.Variable_data)
  115. lines := []string{}
  116. lp := self.handler.lp
  117. for _, sg := range s.style_groups {
  118. if len(sg.styles) < 2 {
  119. continue
  120. }
  121. formatted := make([]string, len(sg.styles))
  122. for i, style_name := range sg.styles {
  123. if is_current_named_style(sg.name, style_name, preview.Variable_data, preview.Variable_named_style) {
  124. formatted[i] = self.handler.lp.SprintStyled(current_val_style, style_name)
  125. } else {
  126. formatted[i] = tui.InternalHyperlink(style_name, "variable_style:"+style_name)
  127. }
  128. }
  129. line := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(formatted, ", ")
  130. lines = append(lines, line)
  131. }
  132. y = self.render_lines(start_y, lines...)
  133. sub_title := "Fine tune the appearance by clicking in the variable axes below:"
  134. axis_values := self.current_preview.current_axis_values()
  135. for _, ax := range self.current_preview.Variable_data.Axes {
  136. if ax.Hidden {
  137. continue
  138. }
  139. if sub_title != "" {
  140. y = self.render_lines(y+1, sub_title, "")
  141. sub_title = ``
  142. }
  143. y = self.draw_axis(sz, y, ax, axis_values[ax.Tag])
  144. }
  145. return y, nil
  146. }
  147. func (self *face_panel) draw_family_style_select(_ loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) {
  148. lp := self.handler.lp
  149. s := styles_in_family(self.family, self.handler.listing.fonts[self.family])
  150. lines := []string{}
  151. for _, sg := range s.style_groups {
  152. formatted := make([]string, len(sg.styles))
  153. for i, style_name := range sg.styles {
  154. if style_name == preview.Style {
  155. formatted[i] = lp.SprintStyled(current_val_style, style_name)
  156. } else {
  157. formatted[i] = tui.InternalHyperlink(style_name, "style:"+style_name)
  158. }
  159. }
  160. line := lp.SprintStyled(control_name_style, sg.name) + ": " + strings.Join(formatted, ", ")
  161. lines = append(lines, line)
  162. }
  163. y = self.render_lines(start_y, lines...)
  164. return y, nil
  165. }
  166. func (self *face_panel) draw_font_features(_ loop.ScreenSize, start_y int, preview RenderedSampleTransmit) (y int, err error) {
  167. lp := self.handler.lp
  168. y = start_y
  169. if len(preview.Features) == 0 {
  170. return
  171. }
  172. formatted := make([]string, 0, len(preview.Features))
  173. sort_keys := make(map[string]string)
  174. for feat_tag, data := range preview.Features {
  175. var text, sort_key string
  176. if preview.Applied_features[feat_tag] != "" {
  177. text = preview.Applied_features[feat_tag]
  178. sort_key = text
  179. if sort_key[0] == '-' || sort_key[1] == '+' {
  180. sort_key = sort_key[1:]
  181. }
  182. text = strings.Replace(text, "+", lp.SprintStyled("fg=green", "+"), 1)
  183. text = strings.Replace(text, "-", lp.SprintStyled("fg=red", "-"), 1)
  184. text = strings.Replace(text, "=", lp.SprintStyled("fg=cyan", "="), 1)
  185. if data.Name != "" {
  186. text = data.Name + ": " + text
  187. sort_key = data.Name
  188. }
  189. } else {
  190. if data.Name != "" {
  191. text = data.Name
  192. sort_key = data.Name + ": " + text
  193. } else {
  194. text = feat_tag
  195. sort_key = text
  196. }
  197. text = lp.SprintStyled("dim", text)
  198. }
  199. f := tui.InternalHyperlink(text, "feature:"+feat_tag)
  200. sort_keys[f] = strings.ToLower(sort_key)
  201. formatted = append(formatted, f)
  202. }
  203. utils.StableSortWithKey(formatted, func(a string) string { return sort_keys[a] })
  204. line := lp.SprintStyled(control_name_style, `Features`) + ": " + strings.Join(formatted, ", ")
  205. y = self.render_lines(start_y, ``, line)
  206. return
  207. }
  208. func (self *handler) draw_preview_header(x int) {
  209. sz, _ := self.lp.ScreenSize()
  210. width := int(sz.WidthCells) - x
  211. p := center_string(self.lp.SprintStyled("italic", " preview "), width, "─")
  212. self.lp.QueueWriteString(self.lp.SprintStyled("dim", p))
  213. }
  214. func (self *face_panel) render_preview(key faces_preview_key) {
  215. var r map[string]RenderedSampleTransmit
  216. s := key.settings
  217. self.handler.set_worker_error(kitty_font_backend.query("render_family_samples", map[string]any{
  218. "text_style": self.handler.text_style, "font_family": s.font_family,
  219. "bold_font": s.bold_font, "italic_font": s.italic_font, "bold_italic_font": s.bold_italic_font,
  220. "width": key.width, "height": key.height, "output_dir": self.handler.temp_dir,
  221. }, &r))
  222. self.preview_cache_mutex.Lock()
  223. defer self.preview_cache_mutex.Unlock()
  224. self.preview_cache[key] = r
  225. }
  226. func (self *face_panel) draw_screen() (err error) {
  227. lp := self.handler.lp
  228. lp.SetCursorVisible(false)
  229. sz, _ := lp.ScreenSize()
  230. styled := lp.SprintStyled
  231. wt := "Regular"
  232. switch self.which {
  233. case "bold_font":
  234. wt = "Bold"
  235. case "italic_font":
  236. wt = "Italic"
  237. case "bold_italic_font":
  238. wt = "Bold-Italic font"
  239. }
  240. lp.QueueWriteString(self.handler.format_title(fmt.Sprintf("%s: %s face", self.family, wt), 0))
  241. lines := []string{
  242. fmt.Sprintf("Press %s to accept any changes or %s to cancel. Click on a style name below to switch to it.", styled("fg=green", "Enter"), styled("fg=red", "Esc")), "",
  243. fmt.Sprintf("Current setting: %s", self.get()), "",
  244. }
  245. y := self.render_lines(2, lines...)
  246. num_lines_per_font := (int(sz.HeightCells) - y - 1) - 2
  247. num_lines := max(1, num_lines_per_font)
  248. key := faces_preview_key{settings: self.settings, width: int(sz.WidthCells * sz.CellWidth), height: int(sz.CellHeight) * num_lines}
  249. self.current_preview_key = key
  250. self.preview_cache_mutex.Lock()
  251. defer self.preview_cache_mutex.Unlock()
  252. previews, found := self.preview_cache[key]
  253. if !found {
  254. self.preview_cache[key] = make(map[string]RenderedSampleTransmit)
  255. go func() {
  256. self.render_preview(key)
  257. self.handler.lp.WakeupMainThread()
  258. }()
  259. return
  260. }
  261. if len(previews) < 4 {
  262. return
  263. }
  264. preview := previews[self.which]
  265. self.current_preview = &preview
  266. if len(preview.Variable_data.Axes) > 0 {
  267. y, err = self.draw_variable_fine_tune(sz, y, preview)
  268. } else {
  269. y, err = self.draw_family_style_select(sz, y, preview)
  270. }
  271. if err != nil {
  272. return err
  273. }
  274. if y, err = self.draw_font_features(sz, y, preview); err != nil {
  275. return err
  276. }
  277. num_lines = int(math.Ceil(float64(preview.Canvas_height) / float64(sz.CellHeight)))
  278. if int(sz.HeightCells)-y >= num_lines+2 {
  279. y++
  280. lp.MoveCursorTo(1, y+1)
  281. self.handler.draw_preview_header(0)
  282. y++
  283. lp.MoveCursorTo(1, y+1)
  284. self.handler.graphics_manager.display_image(0, preview.Path, preview.Canvas_width, preview.Canvas_height)
  285. }
  286. return
  287. }
  288. func (self *face_panel) initialize(h *handler) (err error) {
  289. self.handler = h
  290. self.preview_cache = make(map[faces_preview_key]map[string]RenderedSampleTransmit)
  291. return
  292. }
  293. func (self *face_panel) on_wakeup() error {
  294. return self.handler.draw_screen()
  295. }
  296. func (self *face_panel) get() string {
  297. switch self.which {
  298. case "font_family":
  299. return self.settings.font_family
  300. case "bold_font":
  301. return self.settings.bold_font
  302. case "italic_font":
  303. return self.settings.italic_font
  304. case "bold_italic_font":
  305. return self.settings.bold_italic_font
  306. }
  307. panic(fmt.Sprintf("Unknown self.which value: %s", self.which))
  308. }
  309. func (self *face_panel) set(setting string) {
  310. switch self.which {
  311. case "font_family":
  312. self.settings.font_family = setting
  313. case "bold_font":
  314. self.settings.bold_font = setting
  315. case "italic_font":
  316. self.settings.italic_font = setting
  317. case "bold_italic_font":
  318. self.settings.bold_italic_font = setting
  319. }
  320. }
  321. func (self *face_panel) update_feature_in_setting(pff ParsedFontFeature) error {
  322. fs, err := self.new_font_spec()
  323. if err != nil {
  324. return err
  325. }
  326. found := false
  327. for _, f := range fs.features {
  328. if f.tag == pff.tag {
  329. f.val = pff.val
  330. found = true
  331. break
  332. }
  333. }
  334. if !found {
  335. fs.features = append(fs.features, &pff)
  336. }
  337. self.set(fs.String())
  338. return nil
  339. }
  340. func (self *face_panel) remove_feature_in_setting(tag string) error {
  341. fs, err := self.new_font_spec()
  342. if err != nil {
  343. return err
  344. }
  345. if len(fs.features) > 0 {
  346. fs.features = slices.DeleteFunc(fs.features, func(x *ParsedFontFeature) bool {
  347. return x.tag == tag
  348. })
  349. }
  350. self.set(fs.String())
  351. return nil
  352. }
  353. func (self *face_panel) change_feature_value(tag string, val uint, remove bool) error {
  354. if remove {
  355. return self.remove_feature_in_setting(tag)
  356. }
  357. pff := ParsedFontFeature{tag: tag, val: val}
  358. return self.update_feature_in_setting(pff)
  359. }
  360. func (self *face_panel) handle_click_on_feature(feat_tag string) error {
  361. d := self.current_preview.Features[feat_tag]
  362. if d.Is_index {
  363. var current_val uint
  364. for q, serialized := range self.current_preview.Applied_features {
  365. if q == feat_tag && serialized != "" {
  366. if _, num, found := strings.Cut(serialized, "="); found {
  367. if v, err := strconv.ParseUint(num, 10, 0); err == nil {
  368. current_val = uint(v)
  369. }
  370. } else {
  371. current_val = utils.IfElse(serialized[0] == '-', uint(0), uint(1))
  372. }
  373. return self.handler.if_pane.on_enter(self.family, self.which, self.settings, feat_tag, d, current_val)
  374. }
  375. }
  376. return self.handler.if_pane.on_enter(self.family, self.which, self.settings, feat_tag, d, current_val)
  377. } else {
  378. for q, serialized := range self.current_preview.Applied_features {
  379. if q == feat_tag && serialized != "" {
  380. if serialized[0] == '-' {
  381. return self.remove_feature_in_setting(feat_tag)
  382. }
  383. return self.update_feature_in_setting(ParsedFontFeature{tag: feat_tag, is_bool: true, val: 0})
  384. }
  385. }
  386. return self.update_feature_in_setting(ParsedFontFeature{tag: feat_tag, is_bool: true, val: 1})
  387. }
  388. }
  389. func (self *face_panel) on_click(id string) (err error) {
  390. scheme, val, _ := strings.Cut(id, ":")
  391. switch scheme {
  392. case "style":
  393. if err = self.set_style(val); err != nil {
  394. return err
  395. }
  396. case "variable_style":
  397. if err = self.set_variable_spec(val, nil); err != nil {
  398. return err
  399. }
  400. case "feature":
  401. if err = self.handle_click_on_feature(val); err != nil {
  402. return err
  403. }
  404. case "axis":
  405. p, tag, _ := strings.Cut(val, ":")
  406. num, den, _ := strings.Cut(p, "/")
  407. n, _ := strconv.Atoi(num)
  408. d, _ := strconv.Atoi(den)
  409. frac := float64(n) / float64(d)
  410. for _, ax := range self.current_preview.Variable_data.Axes {
  411. if ax.Tag == tag {
  412. axval := ax.Minimum + (ax.Maximum-ax.Minimum)*frac
  413. if err = self.set_variable_spec("", map[string]float64{tag: axval}); err != nil {
  414. return err
  415. }
  416. break
  417. }
  418. }
  419. }
  420. // Render preview synchronously to void flashing
  421. key := self.current_preview_key
  422. key.settings = self.settings
  423. self.preview_cache_mutex.Lock()
  424. previews := self.preview_cache[key]
  425. self.preview_cache_mutex.Unlock()
  426. if len(previews) < 4 {
  427. self.render_preview(key)
  428. }
  429. return self.handler.draw_screen()
  430. }
  431. func (self *face_panel) on_key_event(event *loop.KeyEvent) (err error) {
  432. if event.MatchesPressOrRepeat("esc") {
  433. event.Handled = true
  434. self.handler.current_pane = &self.handler.faces
  435. return self.handler.draw_screen()
  436. } else if event.MatchesPressOrRepeat("enter") {
  437. event.Handled = true
  438. self.handler.current_pane = &self.handler.faces
  439. self.handler.faces.settings = self.settings
  440. return self.handler.draw_screen()
  441. }
  442. return
  443. }
  444. func (self *face_panel) on_text(text string, from_key_event bool, in_bracketed_paste bool) (err error) {
  445. return
  446. }
  447. func (self *face_panel) on_enter(family, which string, settings faces_settings) error {
  448. self.family = family
  449. self.settings = settings
  450. self.which = which
  451. self.handler.current_pane = self
  452. return self.handler.draw_screen()
  453. }