2024-05-23 21:03:03 -07:00
|
|
|
require "execjs"
|
|
|
|
require "nokogiri"
|
|
|
|
require "net/http"
|
|
|
|
require "json"
|
|
|
|
require "cgi"
|
2024-05-23 21:14:49 -07:00
|
|
|
require "optparse"
|
2024-05-23 21:03:03 -07:00
|
|
|
|
|
|
|
class KatexRenderer
|
|
|
|
def initialize(source)
|
|
|
|
@context = ExecJS.compile(source)
|
|
|
|
@inline_cache = {}
|
|
|
|
@display_cache = {}
|
|
|
|
end
|
|
|
|
|
|
|
|
def render(display, string)
|
|
|
|
cache = display ? @display_cache : @inline_cache
|
|
|
|
comment = display ? "display" : string
|
|
|
|
string = CGI.unescapeHTML string
|
|
|
|
|
|
|
|
cache.fetch(string) do
|
|
|
|
puts " Rendering #{comment}"
|
|
|
|
options = { "throwOnError" => false, "displayMode" => display }
|
|
|
|
cache[string] = @context.call("katex.renderToString", string, options)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def substitute(content)
|
|
|
|
rendered = content.gsub /\\\(((?:[^\\]|\\[^\)])*)\\\)/ do |match|
|
|
|
|
render(false, $~[1])
|
|
|
|
end
|
2024-08-18 17:16:42 -07:00
|
|
|
rendered = rendered.gsub /\$\$((?:[^\$]|$[^\$])*)\$\$/ do |match|
|
2024-05-23 21:03:03 -07:00
|
|
|
render(true, $~[1])
|
|
|
|
end
|
|
|
|
return rendered
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-05-23 21:14:49 -07:00
|
|
|
# Provided via this project's Gemfile
|
2024-05-23 21:03:03 -07:00
|
|
|
ExecJS.runtime = ExecJS::Runtimes::Duktape
|
|
|
|
|
2024-05-23 21:14:49 -07:00
|
|
|
katex = nil
|
|
|
|
OptionParser.new do |opts|
|
|
|
|
opts.banner = "Usage: convert.rb [options]"
|
|
|
|
|
|
|
|
opts.on("--katex-js-file=FILE", "Use the given KaTeX JS file to process LaTeX") do |f|
|
|
|
|
katex = f
|
|
|
|
end
|
|
|
|
end.parse!
|
|
|
|
files = ARGV
|
|
|
|
|
|
|
|
if katex
|
|
|
|
katex = File.read(katex)
|
|
|
|
else
|
|
|
|
katex = Net::HTTP.get(URI("https://static.danilafe.com/katex/katex.min.js"))
|
|
|
|
end
|
|
|
|
|
|
|
|
renderer = KatexRenderer.new(katex)
|
2024-05-23 21:03:03 -07:00
|
|
|
files.each do |file|
|
|
|
|
puts "Rendering file: #{file}"
|
|
|
|
document = Nokogiri::HTML.parse(File.open(file))
|
|
|
|
document.search('//*[not(ancestor-or-self::code or ancestor-or-self::script)]/text()').each do |t|
|
|
|
|
t.replace(renderer.substitute(t.content))
|
|
|
|
end
|
|
|
|
File.write(file, document.to_html(encoding: 'UTF-8'))
|
|
|
|
end
|