Class: SsKaTeX
- Inherits:
-
Object
- Object
- SsKaTeX
- Defined in:
- lib/sskatex.rb
Overview
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.jsfrom 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.
Defined Under Namespace
Modules: Utils
Constant Summary collapse
- ENV_EXECJS_RUNTIME =
Original value of the
EXECJS_RUNTIMEenvironment variable, if any. Used when deferring ExecJS’s engine auto-selection. ENV['EXECJS_RUNTIME']
- DATADIR =
Root directory for auxiliary files of this gem
File.(File.join(File.dirname(__FILE__), '..', 'data', 'sskatex'))
- DEFAULT_JS_DIR =
The default for the #js_dir configuration option. Path of a directory with Javascript helper files.
File.join(DATADIR, 'js')
- 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. File.join('katex', 'katex.min.js')
- 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.
['escape_nonascii_html.js', 'tex_to_html.js']
Instance Attribute Summary collapse
-
#logger ⇒ Object
This can be used for monitoring or debugging.
Class Method Summary collapse
-
.warn_logger(level = :verbose) ⇒ Object
Given a desired log level, this returns an object useful for #logger.
Instance Method Summary collapse
-
#call(tex, display_mode = false, &logger) ⇒ Object
Given a TeX math fragment tex and a boolean display_mode (
truefor block, defaultfalsefor inline), run the JS engine (using #js_context) and let KaTeX compile the math fragment. -
#config ⇒ Object
A dictionary with the used configuration options.
-
#config=(cfg) ⇒ Object
Reconfigure the conversion engine by passing in a dictionary, without affecting the #logger setting.
-
#initialize(cfg = {}, &logger) ⇒ SsKaTeX
constructor
Create a new instance configured with keyword arguments and optional logger.
-
#js_context(&logger) ⇒ Object
The JS engine context obtained by letting the #js_runtime(&logger) compile the #js_source(&logger).
-
#js_dir ⇒ Object
The path to a directory with Javascript helper files as specified by #config[
:js_dir], or its default which is the subdirectoryjsin the data directory of SsKaTeX. -
#js_libs ⇒ Object
A list of UTF-8-encoded Javascript helper files to load.
-
#js_run(&logger) ⇒ Object
Identifies the Javascript engine to be used.
-
#js_runtime(&logger) ⇒ Object
The
ExecJS::Runtimesubclass corresponding to #js_run(&logger). -
#js_source(&logger) ⇒ Object
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.
-
#katex_js ⇒ Object
The path to your copy of
katex.min.jsas specified by #config[:katex_js] or its default'katex/katex.min.js'. -
#katex_opts ⇒ Object
A dictionary filled with the contents of #config[
:katex_opts] if given.
Constructor Details
#initialize(cfg = {}, &logger) ⇒ SsKaTeX
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.
254 255 256 257 |
# File 'lib/sskatex.rb', line 254 def initialize(cfg = {}, &logger) @logger = logger self.config = cfg end |
Instance Attribute Details
#logger ⇒ Object
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.
202 203 204 |
# File 'lib/sskatex.rb', line 202 def logger @logger end |
Class Method Details
.warn_logger(level = :verbose) ⇒ Object
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.
107 108 109 |
# File 'lib/sskatex.rb', line 107 def self.warn_logger(level = :verbose) LOGGERS[level] end |
Instance Method Details
#call(tex, display_mode = false, &logger) ⇒ Object
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=.
384 385 386 387 388 389 390 391 392 393 394 |
# File 'lib/sskatex.rb', line 384 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 ⇒ Object
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=.
227 228 229 |
# File 'lib/sskatex.rb', line 227 def config @config end |
#config=(cfg) ⇒ Object
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.
237 238 239 240 241 242 243 244 245 246 247 |
# File 'lib/sskatex.rb', line 237 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) ⇒ Object
The JS engine context obtained by letting the #js_runtime(&logger) compile the #js_source(&logger). Created at first use e.g. by #call.
375 376 377 |
# File 'lib/sskatex.rb', line 375 def js_context(&logger) @js_context ||= js_runtime(&logger).compile(js_source(&logger)) end |
#js_dir ⇒ Object
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.
294 295 296 |
# File 'lib/sskatex.rb', line 294 def js_dir @js_dir ||= @config[:js_dir] || DEFAULT_JS_DIR end |
#js_libs ⇒ Object
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_htmlthat 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 (truefor block display,falsefor 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_htmlto each math fragment encountered. The implementation given here useskatex.renderToStringand postprocesses the output withescape_nonascii_html.
325 326 327 |
# File 'lib/sskatex.rb', line 325 def js_libs @js_libs ||= @config[:js_libs] || DEFAULT_JS_LIBS end |
#js_run(&logger) ⇒ Object
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=.
272 273 274 275 276 277 278 279 280 281 282 283 |
# File 'lib/sskatex.rb', line 272 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) ⇒ Object
The ExecJS::Runtime subclass corresponding to #js_run(&logger).
286 287 288 |
# File 'lib/sskatex.rb', line 286 def js_runtime(&logger) @js_runtime ||= Utils::JSRUN_FROMSYM[js_run(&logger)] end |
#js_source(&logger) ⇒ Object
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=.
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/sskatex.rb', line 354 def js_source(&logger) @js_source ||= begin logger ||= @logger js = '' js_libs.each do |libfile| absfile = File.(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 ⇒ Object
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.
332 333 334 |
# File 'lib/sskatex.rb', line 332 def katex_js @katex_js ||= @config[:katex_js] || DEFAULT_KATEX_JS end |
#katex_opts ⇒ Object
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.
345 346 347 |
# File 'lib/sskatex.rb', line 345 def katex_opts @katex_opts ||= Utils.dedup_keys(@config[:katex_opts] || {}) end |