class SsKaTeX
This is a TeX-to-HTML+MathML+CSS converter class using the Javascript-based KaTeX, interpreted by one of the Javascript engines supported by ExecJS. The intended purpose is to eliminate the need for math-rendering Javascript in the client's HTML browser. Therefore the name: SsKaTeX
means server-side KaTeX.
Javascript execution context initialization can be done once and then reused for formula renderings with the same general configuration. As a result, the performance is reasonable. Consider this a fast and lightweight alternative to mathjax-node-cli.
Requirements for using SsKaTeX:
-
Ruby gem ExecJS,
-
A Javascript engine supported by ExecJS, e.g. via one of
-
Ruby gem therubyracer,
-
Ruby gem therubyrhino,
-
Ruby gem duktape.rb,
-
-
katex.min.js
from KaTeX.
Although the converter only needs katex.min.js
, you may need to serve the rest of the KaTeX package, that is, CSS and fonts, as resources to the targeted web browsers. The upside is that your HTML templates need no longer include Javascripts for Math (neither katex.js
nor any search-and-replace script). Your HTML templates should continue referencing the KaTeX CSS. If you host your own copy of the CSS, also keep hosting the fonts.
Minimal usage example:
tex_to_html = SsKaTeX.new(katex_js: 'path-to-katex/katex.min.js') # Here you could verify contents of tex_to_html.js_source for security... body_html = '<p>By Pythagoras, %s. Furthermore:</p>' % tex_to_html.call('a^2 + b^2 = c^2', false) # inline math body_html << # block display tex_to_html.call('\frac{1}{2} + \frac{1}{3} + \frac{1}{6} = 1', true) # etc, etc.
More configuration options are described in the Rdoc. Most options, with the notable exception of katex_opts
, do not affect usage nor output, but may be needed to make SsKaTeX
work with all the external parts (JS engine and KaTeX). Since KaTeX is distributed separately from the SsKaTeX
gem, configuration of the latter must support the specification of Javascript file locations. This implies that execution of arbitrary Javascript code is possible. Specifically, options with js
in their names should be accepted from trusted sources only. Applications using SsKaTeX
need to check this.
Constants
- DATADIR
Root directory for auxiliary files of this gem
- DEBUG_LOGGER
A logger that outputs every message via
warn
- DEFAULT_JS_DIR
The default for the
js_dir
configuration option. Path of a directory with Javascript helper files.- DEFAULT_JS_LIBS
The default for the
js_libs
configuration option. A list of UTF-8-encoded Javascript helper files to load. Relative paths are interpreted relative tojs_dir
.- DEFAULT_KATEX_JS
The default path to
katex.js
, cf. thekatex_js
configuration option. For a relative path, the starting point is the current working directory.- LOGGERS
A dictionary with simple loggers
- VERBOSE_LOGGER
A logger that outputs only verbose-level messages via
warn
Attributes
This can be used for monitoring or debugging. Must be either nil
or a
proc {|level, &block| ...}
where the block is used for on-demand construction of the log message. level is one of:
:verbose
-
For information about the effective engine configuration. Issued on first use of a changed configuration option.
:debug
-
For the Javascript expressions used when converting TeX. Issued once per TeX snippet.
For example, to trace :verbose
but not :debug
messages, set logger
to
lambda {|level, &block| warn(block.call) if level == :verbose}
or, equivalently, to the output of ::warn_logger
.
If logger
is nil
, no logging will be done.
Public Class Methods
Create a new instance configured with keyword arguments and optional logger. The arguments are just stored by reference; no further action is taken until parts of the configuration are actually needed in other method calls. The dictionary with the keyword arguments can be accessed as config
. The logger can be accessed as logger
.
# File lib/sskatex.rb, line 251 def initialize(cfg = {}, &logger) @logger = logger self.config = cfg end
Given a desired log level, this returns an object useful for logger
. That logger object simply outputs messages via warn
. If level is :debug
, all messages are output. If level is :verbose
, only verbose-level messages are output. level can be given as a symbol or as its JSON-equivalent string. If level is anything else, nil
will be returned, thus disabling logging.
# File lib/sskatex.rb, line 100 def self.warn_logger(level = :verbose) LOGGERS[level] end
Public Instance Methods
Given a TeX math fragment tex and a boolean display_mode (true
for block, default false
for inline), run the JS engine (using js_context
) and let KaTeX compile the math fragment. Return the resulting HTML string. Can raise errors if something in the process fails. If a block is given, it is used instead of the logger set with logger=
.
# File lib/sskatex.rb, line 383 def call(tex, display_mode = false, &logger) logger ||= @logger ctx = js_context(&logger) js = "tex_to_html(#{Utils.js_quote(tex)}, #{display_mode.to_json}, katex_opts)" logd(logger) {"JS eval: #{js}"} ans = ctx.eval(js) unless ans && ans.start_with?('<') && ans.end_with?('>') raise "KaTeX conversion failed!\nInput:\n#{tex}\nOutput:\n#{ans}" end ans end
Reconfigure the conversion engine by passing in a dictionary, without affecting the logger
setting. Changes become effective on first use.
Note: The dict will be shared by reference. Its deep object tree should remain unchanged at least until js_context
or call
has been invoked. Thereafter changes do not matter until config=
is assigned again.
# File lib/sskatex.rb, line 234 def config=(cfg) @js_context = nil @js_source = nil @katex_opts = nil @katex_js = nil @js_libs = nil @js_dir = nil @js_runtime = nil @js_run = nil @config = cfg end
The JS engine context obtained by letting the js_runtime
(&logger) compile the js_source
(&logger). Created at first use e.g. by call
.
# File lib/sskatex.rb, line 374 def js_context(&logger) @js_context ||= js_runtime(&logger).compile(js_source(&logger)) end
The path to a directory with Javascript helper files as specified by config
[ :js_dir
], or its default which is the subdirectory js
in the data directory of SsKaTeX
. There is no need to change that setting unless you want to experiment with Javascript details.
# File lib/sskatex.rb, line 293 def js_dir @js_dir ||= @config[:js_dir] || DEFAULT_JS_DIR end
A list of UTF-8-encoded Javascript helper files to load. Can be overridden with config
[ :js_libs
]. Relative paths are interpreted relative to js_dir
. The default setting (in YAML notation) is
js_libs: - escape_nonascii_html.js - tex_to_html.js
And there is no need to change that unless you want to experiment with Javascript details.
Files available in the default js_dir
are:
escape_nonascii_html.js
-
defines a function
escape_nonascii_html
that converts non-ASCII characters to HTML numeric character references. Intended as postprocessing filter. tex_to_html.js
-
defines a function
tex_to_html
(tex, display_mode, katex_opts) that takes a LaTeX math string, a boolean display mode (true
for block display,false
for inline), and a dict with general KaTeX options, and returns a string with corresponding HTML+MathML output. The implementation is allowed to setkatex_opts.displayMode
.SsKaTeX
appliestex_to_html
to each math fragment encountered. The implementation given here useskatex.renderToString
and postprocesses the output withescape_nonascii_html
.
# File lib/sskatex.rb, line 324 def js_libs @js_libs ||= @config[:js_libs] || DEFAULT_JS_LIBS end
Identifies the Javascript engine to be used. Defined identifiers include: :RubyRacer
, :RubyRhino
, :Duktape
, :MiniRacer
, :Node
, :JavaScriptCore
, :Spidermonkey
, :JScript
, :V8
, and :Disabled
; that last one would raise an error on first run (by js_context
). Which engines are actually available depends on your installation.
js_run
is determined on first demand as follows, then logged and cached for reuse. If config
[ :js_run
] is not defined, the contents of the environment variable EXECJS_RUNTIME
will be considered instead; and if that is not defined, an automatic choice will be made. For more information, use the logger to show :verbose
messages and consult the documentation of ExecJS. If a block is given, it is used instead of the logger set with logger=
.
# File lib/sskatex.rb, line 269 def js_run(&logger) @js_run ||= begin logger ||= @logger logv(logger) {"Available JS runtimes: #{Utils.js_runtimes.join(', ')}"} jsrun = (@config[:js_run] || ENV['EXECJS_RUNTIME'] || Utils::JSRUN_TOSYM[ExecJS::Runtimes.best_available] || 'Disabled').to_s.to_sym logv(logger) {"Selected JS runtime: #{jsrun}"} jsrun end end
The ExecJS::Runtime
subclass corresponding to js_run
(&logger).
# File lib/sskatex.rb, line 283 def js_runtime(&logger) @js_runtime ||= Utils::JSRUN_FROMSYM[js_run(&logger)].tap do |runtime| runtime.available? # trigger necessary initializations end end
The concatenation of the contents of the files in js_libs
, in katex_js
, and a JS variable definition for katex_opts
, each item followed by a newline. Created at first use, with filenames and katex_opts
logged. Can be used to validate JS contents before a js_context
is created. If a block is given, it is used instead of the logger set with logger=
.
# File lib/sskatex.rb, line 353 def js_source(&logger) @js_source ||= begin logger ||= @logger js = '' js_libs.each do |libfile| absfile = File.expand_path(libfile, js_dir) logv(logger) {"Loading JS file: #{absfile}"} js << IO.read(absfile, external_encoding: Encoding::UTF_8) << "\n" end logv(logger) {"Loading KaTeX JS file: #{katex_js}"} js << IO.read(katex_js, external_encoding: Encoding::UTF_8) << "\n" js_katex_opts = "var katex_opts = #{katex_opts.to_json}" logv(logger) {"JS eval: #{js_katex_opts}"} js << js_katex_opts << "\n" end end
The path to your copy of katex.min.js
as specified by config
[ :katex_js
] or its default 'katex/katex.min.js'
. For a relative path, the starting point is the current working directory.
# File lib/sskatex.rb, line 331 def katex_js @katex_js ||= @config[:katex_js] || DEFAULT_KATEX_JS end
A dictionary filled with the contents of config
[ :katex_opts
] if given. These are general KaTeX options such as throwOnError
, errorColor
, colorIsTextColor
, and macros
. See the KaTeX documentation for details. Use throwOnError: false
if you want parse errors highlighted in the HTML output rather than raised as exceptions when compiling. Note that displayMode
is computed dynamically and should not be specified here. Keys can be symbols or strings; if a key is given in both forms, the symbol will be ignored.
# File lib/sskatex.rb, line 344 def katex_opts @katex_opts ||= Utils.dedup_keys(@config[:katex_opts] || {}) end
Protected Instance Methods
Generic shortcut for logging
# File lib/sskatex.rb, line 204 def log(logger = @logger, level, &msg) logger.call(level, &msg) if logger end
Shortcut for logging debug-level messages
# File lib/sskatex.rb, line 209 def logd(logger = @logger, &msg) log(logger, :debug, &msg) end
Shortcut for logging verbose-level messages
# File lib/sskatex.rb, line 214 def logv(logger = @logger, &msg) log(logger, :verbose, &msg) end