Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/jekyll-katex.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
require 'jekyll'
require 'jekyll/tags/katex'
require 'jekyll/tags/katex_math_mode'
require 'jekyll-katex/cache'

Liquid::Template.register_tag('katex', Jekyll::Tags::Katex)
Liquid::Template.register_tag('katexmm', Jekyll::Tags::KatexMathMode)

Jekyll::Hooks.register :site, :after_init do |_site|
Jekyll::Katex::Cache.load_cache
end

Jekyll::Hooks.register :site, :post_write do |_site|
Jekyll::Katex::Cache.save_cache
end
66 changes: 66 additions & 0 deletions lib/jekyll-katex/cache.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# frozen_string_literal: true

require 'digest'
require 'fileutils'
require 'jekyll-katex/katex_js'

module Jekyll
module Katex
# Disk-based render cache for KaTeX expressions.
#
# Caches the HTML output of katex.renderToString calls in a Marshal file so
# that expressions are only rendered once. Subsequent builds serve cached
# HTML instantly instead of calling ExecJS/Node.js for every expression.
#
# Cache lives in .jekyll-cache/katex-cache.marshal (already gitignored).
# Run `jekyll clean` to clear it.
module Cache
CACHE_DIR = '.jekyll-cache'
CACHE_FILE = File.join(CACHE_DIR, 'katex-cache.marshal')

@store = {}
@dirty = false
@hits = 0
@misses = 0

class << self
def load_cache
if File.exist?(CACHE_FILE)
File.open(CACHE_FILE, 'rb') { |f| @store = Marshal.load(f) }
Jekyll.logger.info 'KaTeX Cache:', "Loaded #{@store.size} cached expressions"
else
@store = {}
Jekyll.logger.info 'KaTeX Cache:', 'No cache file found, starting fresh'
end
@dirty = false
@hits = 0
@misses = 0
end

def save_cache
if @dirty
FileUtils.mkdir_p(CACHE_DIR)
File.open(CACHE_FILE, 'wb') { |f| Marshal.dump(@store, f) }
Jekyll.logger.info 'KaTeX Cache:', "Saved #{@store.size} expressions (#{@hits} hits, #{@misses} misses)"
else
Jekyll.logger.info 'KaTeX Cache:', "No new expressions, skipping write (#{@hits} hits)"
end
end

def render(latex_source, rendering_options)
key = Digest::SHA256.hexdigest(latex_source + rendering_options.to_s)
if @store.key?(key)
@hits += 1
@store[key]
else
@misses += 1
result = KATEX_JS.call('katex.renderToString', latex_source, rendering_options)
@store[key] = result
@dirty = true
result
end
end
end
end
end
end
4 changes: 1 addition & 3 deletions lib/jekyll/tags/katex.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

require 'jekyll-katex/configuration'
require 'jekyll-katex/katex_js'

module Jekyll
module Tags
Expand All @@ -11,7 +10,6 @@ module Tags
# {% endkatex %}
class Katex < Liquid::Block
LOG_TOPIC = 'Katex Block:'
KATEX ||= Jekyll::Katex::KATEX_JS

def initialize(tag_name, markup, tokens)
super
Expand All @@ -23,7 +21,7 @@ def initialize(tag_name, markup, tokens)
def render(context)
latex_source = super
rendering_options = Jekyll::Katex::Configuration.global_rendering_options.merge(displayMode: @display)
KATEX.call('katex.renderToString', latex_source, rendering_options)
Jekyll::Katex::Cache.render(latex_source, rendering_options)
end
end
end
Expand Down
4 changes: 1 addition & 3 deletions lib/jekyll/tags/katex_math_mode.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

require 'jekyll-katex/configuration'
require 'jekyll-katex/katex_js'

module Jekyll
module Tags
Expand All @@ -16,7 +15,6 @@ module Tags
# {% endkatexmm %}
class KatexMathMode < Liquid::Block
LOG_TOPIC = 'KatexMathMode:'
KATEX ||= Jekyll::Katex::KATEX_JS
LATEX_TOKEN_PATTERN = /(?<!\\)([$]{2}|[$]{1})(.+?)(?<!\\)\1/m.freeze

def initialize(tag_name, markup, tokens)
Expand All @@ -33,7 +31,7 @@ def render(context)
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)
Jekyll::Katex::Cache.render(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
Expand Down