branch: externals/fontaine commit 00a8b41a67601a5eb4edc74a154e7e4a8d9eae69 Author: Protesilaos Stavrou <i...@protesilaos.com> Commit: Protesilaos Stavrou <i...@protesilaos.com>
Accept common fallback values for fontaine-presets Update the documentation to reflect all the user-facing changes. Thanks to Ted Reed for proposing this idea and testing my prototype in fontaine's official mailing list: <https://lists.sr.ht/~protesilaos/fontaine/%3c87y1zcmo67....@zenithia.net%3E>. --- README.org | 240 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fontaine.el | 131 +++++++++++++++++++-------------- 2 files changed, 318 insertions(+), 53 deletions(-) diff --git a/README.org b/README.org index aae05ec1f3..6164cbab97 100644 --- a/README.org +++ b/README.org @@ -101,6 +101,8 @@ The doc string of ~fontaine-presets~ explains all properties in detail and documents some important caveats or information about font settings in Emacs. +[[#h:35bc7f51-6368-4718-ad25-b276a1f2cc08][Shared and implicit fallback values for presets]]. + #+findex: fontaine-set-preset The command ~fontaine-set-preset~ applies the desired preset. If there is only one available, it implements it outright. Otherwise it produces @@ -165,6 +167,242 @@ which, in turn, is what the font or source is. However, I will not blame you if you can only interpret it as a descriptive acronym: FONTs Are Irrelevant in Non-graphical Emacs (because that is actually true). +** Shared and implicit fallback values for presets +:PROPERTIES: +:CUSTOM_ID: h:35bc7f51-6368-4718-ad25-b276a1f2cc08 +:END: +#+cindex: Concise fontaine-presets + +The user option ~fontaine-presets~ may look like this (its default +value): + +#+begin_src emacs-lisp +(setq fontaine-presets + '((regular + :default-family "Hack" + :default-weight normal + :default-height 100 + :fixed-pitch-family "Fira Code" + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "Noto Sans" + :variable-pitch-weight normal + :variable-pitch-height 1.0 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family "Source Code Pro" + :italic-slant italic + :line-spacing 1) + (large + :default-family "Iosevka" + :default-weight normal + :default-height 150 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family nil ; use whatever the underlying face has + :italic-slant italic + :line-spacing 1))) +#+end_src + +Notice that not all properties need to be specified, as they have +reasonable fallback values. The above can be written thus (removed +lines are left empty for didactic purposes): + +#+begin_src emacs-lisp +(setq fontaine-presets + '((regular + :default-family "Hack" + + :default-height 100 + :fixed-pitch-family "Fira Code" + + + :variable-pitch-family "Noto Sans" + + + + + :italic-family "Source Code Pro" + + :line-spacing 1) + (large + :default-family "Iosevka" + + :default-height 150 + + + + :variable-pitch-family "FiraGO" + + + + + + + :line-spacing 1))) +#+end_src + +Without the empty lines, we have this, which yields the same results as +the first example: + +#+begin_src emacs-lisp +(setq fontaine-presets + '((regular + :default-family "Hack" + :default-height 100 + :fixed-pitch-family "Fira Code" + :variable-pitch-family "Noto Sans" + :italic-family "Source Code Pro" + :line-spacing 1) + (large + :default-family "Iosevka" + :default-height 150 + :variable-pitch-family "FiraGO" + :line-spacing 1))) +#+end_src + +We call the properties of the removed lines "implicit fallback values". + +This already shows us that the value of ~fontaine-presets~ does not need +to be extensive. To further improve its conciseness, it accepts a +special preset that provides a list of "shared fallback properties": the +=t= preset. This one is used to define properties that are common to +multiple presets, such as the =regular= and =large= we have illustrated +thus far. Here is how verbose presets can be expressed succinctly: + +#+begin_src emacs-lisp +;; Notice the duplication of properties and how we will avoid it. +(setq fontaine-presets + '((regular + :default-family "Iosevka Comfy" + :default-weight normal + :default-height 100 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family nil + :italic-slant italic + :line-spacing nil) + (medium + :default-family "Iosevka Comfy" + :default-weight semilight + :default-height 140 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family nil + :italic-slant italic + :line-spacing nil) + (large + :default-family "Iosevka Comfy" + :default-weight semilight + :default-height 180 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight extrabold + :italic-family nil + :italic-slant italic + :line-spacing nil))) + +(setq fontaine-presets + '((regular + :default-height 100) + (medium + :default-weight semilight + :default-height 140) + (large + :default-weight semilight + :default-height 180 + :bold-weight extrabold) + (t ; our shared fallback properties + :default-family "Iosevka Comfy" + :default-weight normal + ;; :default-height 100 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family nil + :italic-slant italic + :line-spacing nil))) +#+end_src + +The =t= preset does not need to explicitly cover all properties. It can +rely on the aforementioned "implicit fallback values" to further reduce +its verbosity (though the user can always write all properties if they +intend to change their values). We then have this transformation: + +#+begin_src emacs-lisp +;; The verbose form +(setq fontaine-presets + '((regular + :default-height 100) + (medium + :default-weight semilight + :default-height 140) + (large + :default-weight semilight + :default-height 180 + :bold-weight extrabold) + (t ; our shared fallback properties + :default-family "Iosevka Comfy" + :default-weight normal + ;; :default-height 100 + :fixed-pitch-family nil ; falls back to :default-family + :fixed-pitch-weight nil ; falls back to :default-weight + :fixed-pitch-height 1.0 + :variable-pitch-family "FiraGO" + :variable-pitch-weight normal + :variable-pitch-height 1.05 + :bold-family nil ; use whatever the underlying face has + :bold-weight bold + :italic-family nil + :italic-slant italic + :line-spacing nil))) + +;; The concise one which relies on "implicit fallback values" +(setq fontaine-presets + '((regular + :default-height 100) + (medium + :default-weight semilight + :default-height 140) + (large + :default-weight semilight + :default-height 180 + :bold-weight extrabold) + (t ; our shared fallback properties + :default-family "Iosevka Comfy" + :default-weight normal + :variable-pitch-family "FiraGO" + :variable-pitch-height 1.05))) +#+end_src + * Installation :PROPERTIES: :CUSTOM_ID: h:031b9bea-d42b-4be0-82c7-42712cde94cc @@ -286,6 +524,8 @@ Fontaine is meant to be a collective effort. Every bit of help matters. + Contributions to the code or manual :: Christopher League, Eli Zaretskii. ++ Ideas and user feedback :: Ted Reed. + * GNU Free Documentation License :PROPERTIES: :APPENDIX: t diff --git a/fontaine.el b/fontaine.el index 8533587273..7a9f24c92f 100644 --- a/fontaine.el +++ b/fontaine.el @@ -170,6 +170,12 @@ The car of each cell is an arbitrary symbol that identifies and/or describes the set of properties (e.g. 'small', 'reading'). +A preset whose car is t is treated as the default option. This +makes it easier to specify multiple presets without duplicating +their properties. The other presets beside t act as overrides of +the defaults and, as such, need only consist of the properties +that change from the default. + The cdr is a plist that specifies the typographic properties of the faces `default', `fixed-pitch', `variable-pitch', `bold', and `italic'. It also covers the `line-spacing' variable. @@ -216,6 +222,9 @@ The properties in detail: Use the desired preset with the command `fontaine-set-preset'. +For detailed configuration: Info node `(fontaine) Shared and +implicit fallback values for presets'. + Caveats or further notes: - On a Windows system, setting a `default' weight other than @@ -265,7 +274,8 @@ Caveats or further notes: (const reverse-oblique))) ((const :tag "Line spacing" :line-spacing) ,(get 'line-spacing 'custom-type)))) - :key-type symbol)) + :key-type symbol) + :link '(info-link "(fontaine) Shared and implicit fallback values for presets")) (defcustom fontaine-latest-state-file (locate-user-emacs-file "fontaine-latest-state.eld") @@ -352,63 +362,78 @@ combine the other two lists." ;;;; Apply preset configurations -(defun fontaine--apply-default-preset (preset &optional frame) - "Set `default' face attributes based on PRESET for optional FRAME." - (if-let ((properties (alist-get preset fontaine-presets))) - (progn - (fontaine--set-face-attributes - 'default - (plist-get properties :default-family) - (plist-get properties :default-weight) - (plist-get properties :default-height) - frame) - (setq-default line-spacing (plist-get properties :line-spacing))) - (user-error "%s is not in `fontaine-presets'" preset))) - -(defun fontaine--apply-fixed-pitch-preset (preset &optional frame) - "Set `fixed-pitch' face attributes based on PRESET for optional FRAME." - (if-let ((properties (alist-get preset fontaine-presets))) - (fontaine--set-face-attributes - 'fixed-pitch - (or (plist-get properties :fixed-pitch-family) (plist-get properties :default-family)) - (or (plist-get properties :fixed-pitch-weight) (plist-get properties :default-weight)) - (or (plist-get properties :fixed-pitch-height) 1.0) - frame) - (user-error "%s is not in `fontaine-presets'" preset))) - -(defun fontaine--apply-variable-pitch-preset (preset &optional frame) - "Set `variable-pitch' face attributes based on PRESET for optional FRAME." - (if-let ((properties (alist-get preset fontaine-presets))) - (fontaine--set-face-attributes - 'variable-pitch - (or (plist-get properties :variable-pitch-family) (plist-get properties :default-family)) - (or (plist-get properties :variable-pitch-weight) (plist-get properties :default-weight)) - (or (plist-get properties :variable-pitch-height) 1.0) - frame) - (user-error "%s is not in `fontaine-presets'" preset))) - -(defun fontaine--apply-bold-preset (preset &optional frame) +(defmacro fontaine--apply-preset (fn doc args) + "Produce function to apply preset. +FN is the symbol of the function, DOC is its documentation, and +ARGS are its routines." + `(defun ,fn (preset &optional frame) + ,doc + (if-let ((properties (append (alist-get preset fontaine-presets) + (alist-get t fontaine-presets)))) + ,args + (user-error "%s is not in `fontaine-presets' or is empty" preset)))) + +(fontaine--apply-preset + fontaine--apply-default-preset + "Set `default' face attributes based on PRESET for optional FRAME." + (progn + (fontaine--set-face-attributes + 'default + (plist-get properties :default-family) + (plist-get properties :default-weight) + (plist-get properties :default-height) + frame) + (setq-default line-spacing (plist-get properties :line-spacing)))) + +(fontaine--apply-preset + fontaine--apply-fixed-pitch-preset + "Set `fixed-pitch' face attributes based on PRESET for optional FRAME." + (fontaine--set-face-attributes + 'fixed-pitch + (or (plist-get properties :fixed-pitch-family) (plist-get properties :default-family)) + (or (plist-get properties :fixed-pitch-weight) (plist-get properties :default-weight)) + (or (plist-get properties :fixed-pitch-height) 1.0) + frame)) + +(fontaine--apply-preset + fontaine--apply-variable-pitch-preset + "Set `variable-pitch' face attributes based on PRESET for optional FRAME." + (fontaine--set-face-attributes + 'variable-pitch + (or (plist-get properties :variable-pitch-family) (plist-get properties :default-family)) + (or (plist-get properties :variable-pitch-weight) (plist-get properties :default-weight)) + (or (plist-get properties :variable-pitch-height) 1.0) + frame)) + +(fontaine--apply-preset + fontaine--apply-bold-preset "Set `bold' face attributes based on PRESET for optional FRAME." - (if-let ((properties (alist-get preset fontaine-presets))) - (fontaine--set-face-attributes - 'bold - (or (plist-get properties :bold-family) 'unspecified) - (or (plist-get properties :bold-weight) 'bold) - frame) - (user-error "%s is not in `fontaine-presets'" preset))) - -(defun fontaine--apply-italic-preset (preset &optional frame) + (fontaine--set-face-attributes + 'bold + (or (plist-get properties :bold-family) 'unspecified) + (or (plist-get properties :bold-weight) 'bold) + frame)) + +(fontaine--apply-preset + fontaine--apply-italic-preset "Set `italic' face attributes based on PRESET for optional FRAME." - (if-let ((properties (alist-get preset fontaine-presets))) - (fontaine--set-italic-slant - (or (plist-get properties :italic-family) 'unspecified) - (or (plist-get properties :italic-slant) 'italic) - frame) - (user-error "%s is not in `fontaine-presets'" preset))) + (fontaine--set-italic-slant + (or (plist-get properties :italic-family) 'unspecified) + (or (plist-get properties :italic-slant) 'italic) + frame)) (defvar fontaine--font-display-hist '() "History of inputs for display-related font associations.") +(defun fontaine--presets-no-fallback () + "Return list of `fontaine-presets', minus the fallback value." + (delete + nil + (mapcar (lambda (symbol) + (unless (eq (car symbol) t) + symbol)) + fontaine-presets))) + (defun fontaine--set-fonts-prompt () "Prompt for font set (used by `fontaine-set-fonts')." (let* ((def (nth 1 fontaine--font-display-hist)) @@ -418,7 +443,7 @@ combine the other two lists." (intern (completing-read prompt - fontaine-presets + (fontaine--presets-no-fallback) nil t nil 'fontaine--font-display-hist def)))) (defvar fontaine-set-preset-hook nil