I am creating a new Jekyll blog setup where I want to self-host and generate server-side everything I can (so basically everything besides Disqus and Google Analytics). I want my site to work well even on browsers with JS turned off, which is why I turned to Kramdown+Rouge for syntax highlighting and KaTex for rendeding TeX formula during site generation.
I found a problem though. When I have example like:
```bash
$ echo test
test
```
It will break inside KaTeX command:
{% katexmm %}
{{ content }}
{% endkatexmm %}
I figured out the cause:
<span class="gp">$</span>
katexmm
extects that each $
is paired with another $
or escaped as \$
What I want to know is how to fix this, e.g. by escaping all $
inside pre
, but leaving other $
(which are intended to use as actual LaTeX snippets) intact. Or maybe somehow configuring jekyll-katex to ignore unpaired $
? (throw_error: false
option works after text was already matched using dollar signs, so it doesn't help).
I want to fix this in config or in place of applying katexmm
, so that I wouldn't have to modify the content of any post.
I managed to get rid of errors by modifying tag from jekyll-katex
to work around code
(inline, use single tilde) and pre code
(block made using indention or 3 tildes):
# frozen_string_literal: true
require 'jekyll'
require 'jekyll-katex/configuration'
require 'jekyll-katex/katex_js'
require 'nokogiri'
module Jekyll
module Tags
# Defines the custom Liquid tag for compile-time rendering of KaTeX math.
# This differs from the katex tag in that it allows use of `$` and `$$` fencing to mark math mode blocks similar to
# standard latex.
# {% katexmm %}
# This is a mixed environment where you can write text as normal but fence off latex math using `$`. Escape
# using `\$`. For example.
# $latex math with \$$
# $$display mode latex$$
# {% endkatexmm %}
class KatexMathModeFixed < Liquid::Block
LOG_TOPIC = 'KatexMathModeFixed:'
KATEX ||= Jekyll::Katex::KATEX_JS
LATEX_TOKEN_PATTERN = /(?<!\\)([$]{2}|[$]{1})(.+?)(?<!\\)\1/m
def initialize(tag_name, markup, tokens)
super
@markup = markup
@tokens = tokens
@display_mode_rendering = Jekyll::Katex::Configuration.global_rendering_options.merge(displayMode: true)
@inline_mode_rendering = Jekyll::Katex::Configuration.global_rendering_options.merge(displayMode: false)
end
def render(context)
enclosed_block = super
fixed_block = fix_code(enclosed_block)
rendered_str = fixed_block.to_s.gsub(LATEX_TOKEN_PATTERN) do |match|
display_mode = match.to_s.start_with? '$$'
rendering_options = display_mode ? @display_mode_rendering : @inline_mode_rendering
Jekyll.logger.debug LOG_TOPIC, "Rendering matched block - #{match}"
KATEX.call('katex.renderToString', Regexp.last_match(2), rendering_options)
end
# KaTeX should fix escaped `$` within fenced blocks, this addresses instances outside of math mode
rendered_str.to_s.gsub(/\\[$]/, '$').to_s
end
def fix_code(input)
updated = false
html = Nokogiri::HTML.fragment(input)
Jekyll.logger.debug LOG_TOPIC, "Fixing - #{input}"
html.css("code, code span").each do |c|
if c.css('*').empty? && c.content['$']
updated = true
Jekyll.logger.debug LOG_TOPIC, "current tag - #{c}"
content = c.content
content['$'] = '\$'
c.content = content
Jekyll.logger.debug LOG_TOPIC, "current tag now - #{c}/#{content}"
end
end
output = html.to_s
Jekyll.logger.debug LOG_TOPIC, "Fixed - #{output}"
if updated then html.to_s else input end
end
end
end
end
Liquid::Template.register_tag('katexmmx', Jekyll::Tags::KatexMathModeFixed)
It's installable in _plugins
directory.
Thing is, this is still buggy - by default kramdown still tries to use mathjax engine and generates <script type="math/tex">
, so that had to be changed. When I was investigating how, I found out that kramdown supports math_engine: katex
as well - with that I only had to add fonts and CSS, and jekyll-katex
become completely obsolete (as well as my workaround, which I'll leave here if anyone is curious).