;;; ui-theme-fonts.el --- User interface, themes, and font configuration -*- lexical-binding: t -*-
;; Copyright (C) 2024 Cytrogen
;; This file contains:
;; - Font configuration with CJK support
;; - Theme loading and customization
;; - Visual enhancements
;;; Commentary:
;; Comprehensive font setup supporting English, Chinese, and special symbols.
;; Includes theme configuration and visual mode customizations.
;;; Code:
;; Font Configuration
;; Font fallback system for cross-platform compatibility
(defvar my/font-fallback-alist
'(;; CJK Sans fonts (思源黑体)
("Source Han Sans SC" . ("Source Han Sans CN" "Noto Sans CJK SC"))
;; CJK Serif fonts (思源宋体)
("思源宋体 CN" . ("Source Han Serif CN" "Noto Serif CJK SC"))
;; CJK Sans for headings
("Noto Sans SC" . ("Noto Sans CJK SC" "Source Han Sans CN"))
;; Nerd Font icons
("Symbols Nerd Font Mono" . ("Symbols Nerd Font"))
;; Emoji fonts
("Segoe UI Emoji" . ("Noto Color Emoji" "Symbola"))
("Apple Color Emoji" . ("Noto Color Emoji" "Symbola")))
"Alist mapping preferred font names to fallback alternatives.
Each entry is (PREFERRED-FONT . (FALLBACK1 FALLBACK2 ...)).")
(defun my/find-available-font (preferred &optional fallbacks)
"Return PREFERRED font if available, otherwise try FALLBACKS in order.
If FALLBACKS is nil, look up PREFERRED in `my/font-fallback-alist'.
Returns the first available font name, or nil if none found."
(let ((candidates (cons preferred
(or fallbacks
(cdr (assoc preferred my/font-fallback-alist))))))
(cl-find-if (lambda (f) (find-font (font-spec :family f))) candidates)))
;; Advanced multi-script font setup
(defun my/setup-blog-fonts ()
"Setup fonts with precise control over CJK, Punctuation, and Nerd Icons.
Refactored to strictly separate Fixed Pitch (Code) and Variable Pitch (Writing) faces."
(interactive)
(require 'org)
;; Skip font setup in batch mode or when no display
(if (or (not (display-graphic-p))
(not (fboundp 'find-font)))
(message "Skipping font setup in non-graphic environment")
(message "Setting up fonts in graphic environment...")
(set-char-table-range char-script-table '(#x3000 . #x303F) 'han)
(set-char-table-range char-script-table '(#xFF00 . #xFFEF) 'han)
(set-char-table-range char-script-table '(#x2000 . #x206F) 'han)
;; 英文/数字: 强制 JetBrains Mono
(set-face-attribute 'default nil :font "JetBrains Mono-13")
;; 中文: 强制 Source Han Sans SC (思源黑体) 用于 UI 和基础对齐
(let ((cjk-sans (my/find-available-font "Source Han Sans SC")))
(when cjk-sans
(set-fontset-font t 'han (font-spec :family cjk-sans))
(set-fontset-font t 'cjk-misc (font-spec :family cjk-sans))))
;; 专门用于代码块、表格、属性栏
(set-face-attribute 'fixed-pitch nil :family "JetBrains Mono" :height 1.0)
;; 为 fixed-pitch 补充中文部分,保持表格对齐
(let ((fixed-fontset (face-attribute 'fixed-pitch :fontset))
(default-font (face-attribute 'default :font))
(cjk-sans (my/find-available-font "Source Han Sans SC")))
(when (eq fixed-fontset 'unspecified)
(when (and default-font (not (eq default-font 'unspecified)))
(setq fixed-fontset (create-fontset-from-fontset-spec
(font-xlfd-name default-font)))))
(when (and fixed-fontset cjk-sans)
(set-fontset-font fixed-fontset 'han (font-spec :family cjk-sans))))
;; 英文(变宽): 优先使用 Source Serif 4,如果没有则回退到 Georgia
(let* ((en-serif-font (if (find-font (font-spec :family "Source Serif 4"))
"Source Serif 4"
"Georgia"))
;; 创建一个专属的 fontset,命名为 fontset-variable
(v-fontset-name "fontset-variable"))
;; 安全地创建 fontset
(condition-case err
(let ((v-fontset (create-fontset-from-fontset-spec
(font-xlfd-name
(font-spec :family en-serif-font
:registry "fontset-variable")))))
;; 强制指定 slant 为 normal,防止英文也跟着变斜
(set-face-attribute 'variable-pitch nil
:family en-serif-font
:height 1.1
:weight 'regular
:slant 'normal
:fontset v-fontset-name)
(let ((cjk-serif-name (my/find-available-font "思源宋体 CN")))
(if cjk-serif-name
(progn
;; 针对汉字 (han)
(set-fontset-font v-fontset-name 'han
(font-spec :family cjk-serif-name
:weight 'normal
:slant 'normal)) ;; 关键:显式禁止倾斜
;; 针对中文标点 (#x3000 - #x303F)
(set-fontset-font v-fontset-name '(#x3000 . #x303F)
(font-spec :family cjk-serif-name
:weight 'normal
:slant 'normal)))
(message "Warning: No CJK Serif font found (tried 思源宋体 CN and fallbacks)."))))
(error
(message "Warning: Variable pitch fontset creation failed: %s" (error-message-string err))
;; Fallback to simple variable-pitch setup
(set-face-attribute 'variable-pitch nil
:family en-serif-font
:height 1.1
:weight 'regular
:slant 'normal))))
(let ((heading-font-family "Open Sans")
(heading-cjk-family (my/find-available-font "Noto Sans SC")))
(when (find-font (font-spec :family heading-font-family))
(dolist (face '(org-level-1 org-level-2 org-level-3 org-level-4
org-level-5 org-level-6 org-level-7 org-level-8))
(when (facep face)
(set-face-attribute face nil :family heading-font-family :weight 'bold)
;; 确保标题中文使用黑体
(when heading-cjk-family
(let ((face-fontset (face-attribute face :fontset))
(default-font (face-attribute 'default :font)))
(when (eq face-fontset 'unspecified)
;; 如果 face 没有独立的 fontset,创建一个
(when (and default-font (not (eq default-font 'unspecified)))
(setq face-fontset (create-fontset-from-fontset-spec
(font-xlfd-name default-font)))))
(when face-fontset
(set-fontset-font face-fontset 'han
(font-spec :family heading-cjk-family :weight 'bold)))))))))
;; 让 Org 的代码块、元数据、表格、引用块强制继承 fixed-pitch
(dolist (face '(org-block org-block-begin-line org-block-end-line
org-code org-table org-verbatim org-formula
org-checkbox org-date org-priority org-special-keyword
org-tag line-number))
(when (facep face)
(set-face-attribute face nil :inherit 'fixed-pitch)))
;; 让语法高亮 faces 在 variable-pitch-mode 下保持等宽
;; 解决 org src block 内 defun/setq 等关键字显示为 serif 的问题
(dolist (face '(font-lock-comment-face
font-lock-comment-delimiter-face
font-lock-string-face
font-lock-doc-face
font-lock-keyword-face
font-lock-builtin-face
font-lock-function-name-face
font-lock-variable-name-face
font-lock-type-face
font-lock-constant-face
font-lock-warning-face
font-lock-negation-char-face
font-lock-preprocessor-face
font-lock-regexp-grouping-backslash
font-lock-regexp-grouping-construct))
(when (facep face)
(set-face-attribute face nil :inherit 'fixed-pitch)))
;; 特殊处理:Meta line (如 #+TITLE) 通常需要更淡的颜色,但也需要等宽
(when (facep 'org-meta-line)
(set-face-attribute 'org-meta-line nil :inherit '(font-lock-comment-face fixed-pitch)))
(let ((nerd-font-name (my/find-available-font "Symbols Nerd Font Mono")))
(when nerd-font-name
(set-fontset-font t '(#xE000 . #xF8FF)
(font-spec :family nerd-font-name) nil 'prepend)))
;; Emoji 字体配置 (跨平台)
(let* ((emoji-candidates
(if (eq system-type 'windows-nt)
'("Segoe UI Emoji" "Noto Color Emoji" "Symbola" "Apple Color Emoji")
'("Noto Color Emoji" "Symbola" "Noto Emoji")))
(primary-emoji-font (cl-find-if (lambda (f) (find-font (font-spec :family f))) emoji-candidates)))
(if primary-emoji-font
(let ((emoji-spec (font-spec :family primary-emoji-font)))
(set-fontset-font t 'emoji emoji-spec nil 'prepend)
(set-fontset-font t '(#x1F000 . #x1FFFF) emoji-spec nil 'prepend)
(set-fontset-font t '(#x2600 . #x27BF) emoji-spec nil 'prepend))
(message "Warning: No suitable Emoji font found.")))
(message "Fonts setup: Default(JB Mono) | Writing(Source Serif 4/Georgia) | Fixed(JB Mono)")
;; 设置代码块和行内代码的背景色
(my/setup-org-code-faces)))
(defun my/setup-org-code-faces ()
"Setup org-mode code block and inline code faces with theme-adaptive colors."
(let* ((bg (face-background 'default))
(is-dark (< (apply '+ (color-values bg)) (* 3 32768)))
;; 代码块比正文背景更深
(block-bg (if is-dark "#1e1e1e" "#e8e8e8"))
(block-border-bg (if is-dark "#151515" "#d8d8d8"))
(block-border-fg (if is-dark "#606060" "#707070"))
(inline-bg (if is-dark "#2a2a2a" "#e0e0e0"))
(inline-fg (if is-dark "#e06c75" "#c7254e")))
;; 使用 face-spec-set 直接设置 face,不影响其他设置
(face-spec-set 'org-block
`((t (:background ,block-bg :extend t :inherit fixed-pitch))))
(face-spec-set 'org-block-begin-line
`((t (:background ,block-border-bg :foreground ,block-border-fg :extend t :inherit fixed-pitch))))
(face-spec-set 'org-block-end-line
`((t (:background ,block-border-bg :foreground ,block-border-fg :extend t :inherit fixed-pitch))))
(face-spec-set 'org-code
`((t (:background ,inline-bg :foreground ,inline-fg :inherit fixed-pitch))))
(face-spec-set 'org-verbatim
`((t (:background ,inline-bg :foreground ,inline-fg :inherit fixed-pitch))))))
(defun my/force-restore-fonts (&rest _args)
(message "Theme changed, re-applying fonts...")
(my/setup-blog-fonts))
;; Theme Configuration
;; Custom theme loading and adjustments
(add-to-list 'custom-theme-load-path "~/.emacs.d/themes/")
(load-theme 'darcula t)
;; Font restoration after theme changes
(advice-add 'load-theme :after #'my/force-restore-fonts)
;; Visual Enhancements
;; Line wrapping and writing modes
(add-hook 'org-mode-hook #'visual-line-mode)
(add-hook 'markdown-mode-hook #'visual-line-mode)
(when (fboundp 'adaptive-wrap-prefix-mode)
(add-hook 'visual-line-mode-hook #'adaptive-wrap-prefix-mode))
(defun my/org-mode-visual-setup ()
"Activate visual adjustments for Org mode writing."
(variable-pitch-mode 1) ; 开启变宽字体模式(正文使用 Source Serif 4/宋体)
(visual-line-mode 1)) ; 开启视觉折行(按词换行,而非按字符截断)
(add-hook 'org-mode-hook 'my/org-mode-visual-setup)
;; Apply fonts on startup
(if (daemonp)
(add-hook 'after-make-frame-functions
(lambda (frame) (with-selected-frame
frame (my/setup-blog-fonts))))
(my/setup-blog-fonts))
(provide 'ui-theme-fonts)
;;; ui-theme-fonts.el ends here