Export the colors of some emacs faces to CSS
Context
This website is created with Org Mode.
In .org
files code blocks have automatic syntax highlighting.
The variable org-html-htmlize-output-type
controls how the syntax highlighting is exported to HTML and CSS:
If set to "css"
, only some CSS classes are set on the HTML elements, but their attributes and values are not exported.
This is the most sensible setting for this website, because the export becomes independent from the currently active theme.
However, it means that the CSS attributes and values have to come from somewhere else.
Org Mode has the function org-html-htmlize-generate-css
, which is supposed to generate a CSS file with attribute-value pairs for each class, but there were some problems with it:
- In version 28.2 (the latest one when this website was started) it was broken (see this bug).
- It generates a CSS class definition for every emacs face that is defined which can be a lot.
- It does not export colors as CSS variables. This is useful to allow them to be modified more easily later and to be able to toggle between a light and a dark theme.
Solution
After digging around in the htmlize and org-html source files for a while I came up with the following solution. First we define a list of faces that we want to export:
(defun exported-faces-list () '(font-lock-comment-face font-lock-comment-delimiter-face font-lock-string-face font-lock-doc-face font-lock-doc-markup-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 bold italic bold-italic underline fixed-pitch fixed-pitch-serif variable-pitch shadow link link-visited highlight region secondary-selection line-number line-number-current-line line-number-major-tick line-number-minor-tick fill-column-indicator escape-glyph homoglyph nobreak-space nobreak-hyphen))
Now we define a function export-face-colors
that exports only the colors associated with those faces.
For a face with name foo
it will create CSS properties --org-src-foo-fg
and --org-src-foo-bg
for the foreground and background colors respectively (if they exist).
(defun export-face-colors (name) (let ((fl-exported (exported-faces-list))) (with-temp-buffer (insert (format "[data-theme=\"%s\"] {\n" name)) (let* ((face-map (htmlize-make-face-map (cl-adjoin 'default fl-exported)))) ;; default (insert " /* default */\n") (insert (format " --org-src-default-fg: %s;\n" (htmlize-fstruct-foreground (gethash 'default face-map)))) (insert (format " --org-src-default-bg: %s;\n" (htmlize-fstruct-background (gethash 'default face-map)))) ;; go through the faces (insert " /* faces */\n") (dolist (face fl-exported) (let ((fstruct (gethash face face-map))) (when (htmlize-fstruct-foreground fstruct) (insert (format " --org-src-%s-fg: %s;\n" (htmlize-fstruct-css-name fstruct) (htmlize-fstruct-foreground fstruct)))) (when (htmlize-fstruct-background fstruct) (insert (format " --org-src-%s-bg: %s;\n" (htmlize-fstruct-css-name fstruct) (htmlize-fstruct-background fstruct))))))) (insert "}\n\n") (buffer-string))))
We also define a function that exports the class definitions for the faces but with the colors replaced by the corresponding CSS variables. Here we also export text properties like bold, italic or underline here.
(defun export-faces () (let ((fl-exported (exported-faces-list))) (with-temp-buffer (let* ((face-map (htmlize-make-face-map (cl-adjoin 'default fl-exported)))) ;; default (insert "/* default */\n") (insert ".org-src-container {\n") (insert " color: var(--org-src-default-fg);\n") (insert " background-color: var(--org-src-default-bg);\n") (insert "}\n\n") ;; go through the faces (insert "/* faces */\n") (dolist (face fl-exported) (let* ((fstruct (gethash face face-map)) (css-name (htmlize-fstruct-css-name fstruct))) (insert (format ".org-%s {\n" css-name)) (when (htmlize-fstruct-foreground fstruct) (insert (format " color: var(--org-src-%s-fg);\n" css-name))) (when (htmlize-fstruct-background fstruct) (insert (format " background-color: var(--org-src-%s-bg);\n" css-name))) (when (htmlize-fstruct-boldp fstruct) (insert " font-weight: bold;\n")) (when (htmlize-fstruct-italicp fstruct) (insert " font-style: italic;\n")) (when (htmlize-fstruct-underlinep fstruct) (insert " text-decoration: underline;\n")) (when (htmlize-fstruct-overlinep fstruct) (insert " text-decoration: overline;\n")) (when (htmlize-fstruct-strikep fstruct) (insert " text-decoration: line-through;\n")) (insert "}\n\n") ))) (buffer-string))))
Finally we run export-face-colors
with different themes loaded and write the output to a file:
(with-temp-buffer (insert (format "/* Last generated: %s */\n\n" (format-time-string "%Y-%m-%d %H:%M:%S" (current-time)))) (load-theme 'sanityinc-tomorrow-night t) (insert (export-face-colors "dark")) (load-theme 'sanityinc-tomorrow-day t) (insert (export-face-colors "light")) (insert (export-faces)) (write-region nil nil "~/web/assets/css/syntax.css" nil)) (load-theme 'monokai t)