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.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.
Defined Under Namespace
Modules: Utils
Constant Summary collapse
- 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 (
true
for block, defaultfalse
for 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 subdirectoryjs
in 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::Runtime
subclass 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.js
as 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.
252 253 254 255 |
# File 'lib/sskatex.rb', line 252 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.
200 201 202 |
# File 'lib/sskatex.rb', line 200 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.
101 102 103 |
# File 'lib/sskatex.rb', line 101 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=.
225 226 227 |
# File 'lib/sskatex.rb', line 225 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.
235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/sskatex.rb', line 235 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_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
.
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=.
270 271 272 273 274 275 276 277 278 279 280 281 |
# File 'lib/sskatex.rb', line 270 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).
284 285 286 287 288 |
# File 'lib/sskatex.rb', line 284 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) ⇒ 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 |