这一帖中,我会简单说明如何从带 text property 的文本到 HTML 的生成过程。同时对 engrave-faces 的用法做简单的总结。
由 token 到 HTML
在 engrave-faces-buffer
中,tokenization 和输出是在一个循环中进行的。
(while (not (eobp))
(setq next-change (engrave-faces--next-face-change (point)))
(setq text (buffer-substring-no-properties (point) next-change))
;; Don't bother writing anything if there's no text (this
;; happens in invisible regions).
(when (> (length text) 0)
(princ (funcall face-transformer
(let ((prop (get-text-property (point) 'face)))
(cond
((null prop) 'default)
((and (listp prop) (eq (car prop) 'quote))
(eval prop t))
(t prop)))
text)
engraved-buf))
(goto-char next-change))
可见在获取 token 文本和 face 属性后,输出工作交给了 face-transformer
,在 HTML 后端中该函数为 engrave-faces-html--face-mapper
,实现如下:
(defun engrave-faces-html--face-mapper (faces content)
"Create a HTML representation of CONTENT With FACES applied."
(let ((protected-content (engrave-faces-html--protect-string content))
(style (engrave-faces-preset-style faces)))
(if (string-match-p "\\`[\n[:space:]]+\\'" content)
protected-content
(if (and style (eq engrave-faces-html-output-style 'preset))
(concat "<span class=\"" engrave-faces-html-class-prefix
(plist-get (cdr style) :slug) "\">"
protected-content "</span>")
(engrave-faces-html--face-apply faces protected-content)))))
该函数中的参数 faces
来自 engrave-faces-buffer
中调用 get-text-property
得到的 face 属性,在 engrave-faces-html--face-mapper
中它作为 engrave-faces-preset-style
的参数来获取具体的 对应样式,粗略来说它的工作原理是从 engrave-faces-preset-style
这个 alist 中找到符号(如 font-lock-type-face
)对应的 face。以下是它的部分项:
(;; faces.el --- excluding: bold, italic, bold-italic, underline, and some others
(default :short "default" :slug "D" :foreground "#000000" :background "#ffffff")
(shadow :short "shadow" :slug "h" :foreground "#7f7f7f")
(success :short "success" :slug "sc" :foreground "#228b22" :weight bold)
(warning :short "warning" :slug "w" :foreground "#ff8e00" :weight bold)
(error :short "error" :slug "e" :foreground "#ff0000" :weight bold)
;; font-lock.el
(font-lock-comment-face :short "fl-comment" :slug "c" :foreground "#b22222")
...)
不过 engrave-faces-preset-style
已经是个废弃的名字了,现在更好的名字是 engrave-faces-current-preset-style
。这个列表与导出后端是无关的,我们可以自定义某些关键字对应的颜色和缩写。 engrave-faces
提供了根据当前主题生成对应 preset-style 的命令: engrave-faces-generate-preset
。在 moe-light
主题中它的输入如下:
(engrave-faces-generate-preset) =>
((default :short "default" :slug "D" :foreground "#5f5f5f" :background "#fdfde7" :slant normal :weight regular)
(shadow :short "shadow" :slug "h" :foreground "#7f7f7f") (success :short "success" :slug "sc" :foreground "#a1db00")
(warning :short "warning" :slug "w" :foreground "#ff8700" :weight bold) (error :short "error" :slug "e" :foreground "#ff4b4b")
(font-lock-comment-face :short "fl-comment" :slug "c" :foreground "#b2b2b2" :slant italic)
(font-lock-comment-delimiter-face :short "fl-comment-delim" :slug "cd" :foreground "#b2b2b2" :slant italic)
(font-lock-string-face :short "fl-string" :slug "s" :foreground "#ff1f8b") (font-lock-doc-face :short "fl-doc" :slug "d" :foreground "#cc0000")
(font-lock-doc-markup-face :short "fl-doc-markup" :slug "m" :foreground "#1f5bff")
(font-lock-keyword-face :short "fl-keyword" :slug "k" :foreground "#00af00")
(font-lock-builtin-face :short "fl-builtin" :slug "b" :foreground "#b218b2")
(font-lock-function-name-face :short "fl-function" :slug "f" :foreground "#ef2929")
(font-lock-variable-name-face :short "fl-variable" :slug "v" :foreground "#ff8700")
(font-lock-type-face :short "fl-type" :slug "t" :foreground "#18b2b2")
(font-lock-constant-face :short "fl-constant" :slug "o" :foreground "#1f5bff")
...)
engrave-faces
也提供了 engrave-faces-use-theme
来交互式选择主题来设定当前使用的 preset-face,它会修改 engrave-faces-current-preset-style
为选择的主题对应的 preset-style。
如果 engrave-faces-html-output-style
为 preset
的话,HTML 后端会使用 ef-
前缀加上 style 中的 :slug
(可以理解为短语的意思)组成 CSS 类,附加到文本的 <span>
标签中。如果 engrave-faces-html-output-style
为 nil 的话,样式就会以 style
属性内嵌到 <span>
中,内嵌导出由 engrave-faces-html--face-apply
负责。
如果使用内嵌式导出,那么在未指定主题的情况下当前的导出效果会依赖于当前主题。因此使用 engrave-face-html-output-style
为默认的 preset
有利于样式的一致性。engrave-faces-html
为我们提供了根据主题导出对应 CSS 的函数 engrave-faces-html-gen-stylesheet
。我们可以将这段 CSS 嵌入到需要导出的 HTML 文档中。
engrave-faces 总览
草,本来我是准备裁剪一下 htmlize.el 用作 org-mode 的 HTML 导出的代码块高亮后端的,现在发现 engrave-faces 完美地满足了我的需求,简单裁裁就能以两三百行的规模塞到自己的 org HTML 后端里面了。不过既然标题写了 htmlize,我还是会对它进行一定的分析。草,突然懒起来了,标题不如改成 engrave-faces.el 的实现原理简单分析吧。
下面是对 engrave-faces 的功能总结,希望对想要使用的读者有所帮助。
在 engrave-faces.el 的开头 User options 定义部分, engrave-faces-attributes-of-interest
确定了需要从 face
中提取的属性,这包括 :foreground
, :background
, slant
, weight
, :height
和 strike-through
。如果不想让导出含有刺眼的背景,我们可以去掉 :background
。
engrave-faces-define-backend
可以用来定义新的导出后端,具体的用法可以参考 engrave-faces
的三个已有后端。
engrave-faces-file
和 engrave-faces-buffer
是导出的核心函数,实际上我上面的分析都是围绕 engrave-faces-buffer
开展的。
engrave-faces-merge-attribute
可在后端代码中用于从 faces
获取具体的 attributes
。具体来说的话可以参考以下例子:
(engrave-faces-merge-attributes 'font-lock-keyword-face) =>
(:foreground "#9370db" :background nil :slant nil :weight nil :height nil :strike-through nil)
engrave-faces-themes
定义默认的 face 对应颜色和样式。engrave-faces-current-preset-style
则是当前选中的 faces 样式。我们可以使用 engrave-faces-generate-preset
和 engrave-faces-get-theme
生成 preset style,并通过 engrave-faces-use-theme
命令根据主题选择 preset style。
在 engrave-faces-html.el 中,我们可以通过 engrave-faces-html-output-style
选择导出使用 CSS 类或内嵌样式,通过 engrave-faces-html-class-prefix
设置 CSS 类名的前缀,通过 engrave-faces-html-gen-stylesheet
生成主题对应的 CSS 样式。