February 8, 2019

CSS in org-publish

Since I am generating this site using org-mode’s ox-html.el exporter, I needed to understand how these generate CSS. By default the exporter opts for inline CSS styles generated from the current Emacs theme’s face definitions. This makes the exported HTML self contained but ultimately the styling is just a my current Emacs theme and not easily changeable. ox-html.el uses htmlize.el to turn Emacs’ faces into CSS. By setting org-html-htmlize-output-type to 'css instead of the default 'inline-css, it will create CSS classes derived from the face name to style the HTML elements. Calling org-html-htmlize-generate-css will “produces a buffer that contains class definitions for all faces used in the current Emacs session.” It’ll look something like this:

css code
...
.org-comment {
  /* font-lock-comment-face */
  color: #2aa1ae;
  background-color: #292e34;
}
.org-constant {
  /* font-lock-constant-face */
  color: #a45bad;
}
...

But at over 4000 lines, its probably unwise to just throw this in a script tag and call it good. In fact the docstring for org-html-htmlize-generate-css even says “You can copy and paste the ones you need into your CSS file.” But how should I know which ones I need without reading through all my exported html files?

Finding all Org CSS classes in HTML

Javascript is well suited to solving this. The following snippet will get all the CSS classes with an org- prefix used in an HTML page that are not present in the loaded stylesheets:

js code
function unique(array) {
  var prev;
  return array.sort().filter(e => e !== prev && (prev = e));
}
let org_classes = unique([].concat(...[...document.querySelectorAll('*')].map(
  elt => [...elt.classList]))).filter(e => e.startsWith('org-'));
let classes = [...document.styleSheets].map(
  sheet => [...sheet.cssRules].map(rule => rule.selectorText)).flat();
org_classes.filter(org_class => classes.find(
  elt => elt === "."+org_class) === undefined);

Since I have several directories full of html, either I need to run this on every page (tedious), use node (gross)1, or do some clever command line foo:

bash code
rm $FNAME
cat > /tmp/$FNAME <<EOL
<!DOCTYPE html>
<html lang="en">
<head>
<!-- 2019-02-05 Tue 18:54 -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Akira Kyle</title>
<meta name="generator" content="Org mode">
<link href='/styles.css' rel='stylesheet' type='text/css'>
</head>
<body>
EOL
find . -name "*.html" -exec gsed '/^<body>$/,/^<\/body>$/{//!b};d' {} + >> /tmp/$FNAME
cat >> /tmp/$FNAME <<EOL
</body>
</html>
EOL
mv /tmp/$FNAME $FNAME

You may be howling in despair at the sight of regexes being used to parse HTML. Let me put your soul at ease: gaze upon ox-html line 2077 and ox-html line 2121.2

Now that we have all our HTML files concatenated into a single large HTML file, we can run or javascript snippet to get all the org- classes we’re missing in our stylesheet then copy them over from the org-html-htmlize-generate-css buffer and modify them as we see fit.3

Footnotes:

1

If you really want node, checkout this post, but see how functional my js is compared to theirs.

3

I’m just running the js snippet in Firefox’s developer console

Email: akira@akirakyle.com
GPG Public Key: 963C 2413 0BD3 BF1B 624C EF4C 8850 284C 20B8 078D