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:

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 to js_dir.

DEFAULT_KATEX_JS

The default path to katex.js, cf. the katex_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

logger[RW]

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

new(cfg = {}, &logger) click to toggle source

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
warn_logger(level = :verbose) click to toggle source

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

call(tex, display_mode = false, &logger) click to toggle source

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
config() click to toggle source

A dictionary with the used configuration options. The resulting effective option values can be read from the same-named attributes katex_js, katex_opts, js_dir, js_libs, js_run. See also config=.

# File lib/sskatex.rb, line 224
def config
  @config
end
config=(cfg) click to toggle source

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
js_context(&logger) click to toggle source

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
js_dir() click to toggle source

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
js_libs() click to toggle source

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 set katex_opts.displayMode. SsKaTeX applies tex_to_html to each math fragment encountered. The implementation given here uses katex.renderToString and postprocesses the output with escape_nonascii_html.

# File lib/sskatex.rb, line 324
def js_libs
  @js_libs ||= @config[:js_libs] || DEFAULT_JS_LIBS
end
js_run(&logger) click to toggle source

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
js_runtime(&logger) click to toggle source

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
js_source(&logger) click to toggle source

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
katex_js() click to toggle source

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
katex_opts() click to toggle source

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

log(logger = @logger, level, &msg) click to toggle source

Generic shortcut for logging

# File lib/sskatex.rb, line 204
def log(logger = @logger, level, &msg)
  logger.call(level, &msg) if logger
end
logd(logger = @logger, &msg) click to toggle source

Shortcut for logging debug-level messages

# File lib/sskatex.rb, line 209
def logd(logger = @logger, &msg)
  log(logger, :debug, &msg)
end
logv(logger = @logger, &msg) click to toggle source

Shortcut for logging verbose-level messages

# File lib/sskatex.rb, line 214
def logv(logger = @logger, &msg)
  log(logger, :verbose, &msg)
end