我的博客是用 org-publish 生成的,后面我给博客添加了主题切换(见 给博客添加 dark mode), 当时留下了一个问题 ⸺ org-publish 生成的代码高亮是内联在 HTML 标签里的,我没法通过 light-dark 去设置亮色/暗色主题下不同的颜色,只能选择一种代码高亮主题,要么是亮色,要么是暗色。
关于博客的构建见:
因为只能二选一,我最终选择的是暗色。这会导致在亮色主题下,出现一大块黑色的内容,看着有点突兀。但比在暗色主题下,出现一大块白色的内容更好,毕竟在暗色主题下,白色太刺眼了。
内联的颜色默认是当前 Emacs 的主题色,因为我平时的习惯是 随机切换主题,我当前用的 Emacs 主题和 org-publish 用到的主题未必是一致的,所以我每次在 org-publish 前还需要将 Emacs 主题重置一下。
重置主题相关的代码
(defun spike-leung/apply-theme-when-publish (&rest args)
"Switch theme when do `org-publish'.ARGS will pass to `org-publish'."
(require 'modus-themes)
(require 'ef-themes)
(require 'doric-themes)
(let ((current-theme (car custom-enabled-themes)))
(load-theme 'modus-vivendi t)
(apply args)
(when current-theme
(disable-theme 'modus-vivendi)
(enable-theme current-theme)
(load-theme current-theme :no-confirm))))
(advice-remove 'org-publish #'spike-leung/apply-theme-when-publish)
(advice-remove 'load-theme #'spike-leung/set-olivetti-fringe-face)
(advice-add 'org-publish :around #'spike-leung/apply-theme-when-publish)
(advice-add 'load-theme :after #'spike-leung/set-olivetti-fringe-face)
最近又在折腾博客样式,就顺便去看了一下 org-publish 中代码块的高亮是如何实现的,于是看到了 org-html-htmlize-output-type 这个参数,将其设置为 css ,代码高亮就会使用添加类名的方式实现,而不是内联,这样我就可以基于类名去应用 light-dark 了。
(ノ>ω<)ノ 好耶!
具体做法:
使用 org-html-htmlize-generate-css 这个方法去生成当前 Emacs 主题的 CSS:
- 找一个喜欢的 Emacs 亮色主题,生成一份
light.txt - 再找一个喜欢的暗色主题,生成一份
dark.txt
org-html-htmlize-generate-css 的描述是:
Create the CSS for all font definitions in the current Emacs session.
有的 font definitions 应该是需要启用了某个 mode 才会生成,可能需要在 Emacs 中,把那些常用的语言文件都访问一下,例如打开一下 .css 、 .js 、 .html 等文件,使得 font definitions 尽可能齐全。
写一个脚本,合并 light.txt 和 dark.txt ,生成一份 CSS 文件,将两个主题的颜色合并,使用 light-dark 定义亮色/暗色主题下的颜色。
我现在用的 JS 脚本
如果你打算使用这个脚本,你可能需要调整一下代码中文件的路径。或者你可以找
import fs from "fs"
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const LightThemeFilePath = path.join(__dirname, ".", "light.txt");
const DarkThemeFilePath = path.join(__dirname, ".", "dark.txt");
const OutputFilePath = path.join(__dirname, "..", "publish/styles/fontify-code.css");
function parseCSS(content) {
const rules = {};
// org- 对应的是 ox-html.el 中 `org-html-htmlize-font-prefix` 的值
const ruleRegex = /\.org-[^{]+\{[^}]+\}/g;
(content.match(ruleRegex) || []).forEach(rule => {
const selector = rule.match(/(\.org-[\w-]+)/)[1];
const block = rule.match(/\{([^}]+)\}/)[1];
const props = {}, comments = {};
let lastComment = null;
block.split('\n').forEach(line => {
line = line.trim();
if (!line) return;
if (line.startsWith('/*') && line.endsWith('*/')) {
lastComment = line; return;
}
const m = line.match(/^([a-z-]+)\s*:\s*([^;]+)/);
if (m) {
props[m[1]] = m[2].trim();
}
});
rules[selector] = { properties: props };
});
return rules;
}
function generate(light, dark) {
const selectors = Array.from(new Set([...Object.keys(light), ...Object.keys(dark)])).sort();
return selectors.map(sel => {
const l = light[sel] || { properties: {} };
const d = dark[sel] || { properties: {} };
const props = [...new Set([...Object.keys(l.properties), ...Object.keys(d.properties)])];
let block = `${sel}{`;
props.forEach(prop => {
const lv = l.properties[prop] || '', dv = d.properties[prop] || '';
const isColor = /color|background|border|outline/.test(prop);
if (isColor && lv && dv) {
block += `${prop}:${lv};${prop}:light-dark(${lv},${dv});`;
} else if (lv) {
block += `${prop}:${lv};`;
} else if (dv) {
block += `${prop}:${dv};`;
}
});
return block + '}';
}).join('');
}
const light = parseCSS(fs.readFileSync(LightThemeFilePath, 'utf8'));
const dark = parseCSS(fs.readFileSync(DarkThemeFilePath, 'utf8'));
fs.writeFileSync(OutputFilePath, generate(light, dark));
console.log('Generated Finished');
最后在博客中引用生成好的 CSS 文件就好了。
Happy hacking - Emacs ♥ you!
如果你有什么想法,也可以在 让 org-publish 生成的代码高亮,基于亮色/暗色主题切换 参与讨论。