|
|
|
// KaTeX rendering server
|
|
|
|
// Listens on unix socket, path is provided as first argument
|
|
|
|
// Expects JSON lines, each line is a query with the following schema:
|
|
|
|
// {
|
|
|
|
// formulas: [
|
|
|
|
// {
|
|
|
|
// tex: string,
|
|
|
|
// options?: object
|
|
|
|
// }
|
|
|
|
// ],
|
|
|
|
// options?: object
|
|
|
|
// }
|
|
|
|
|
|
|
|
// see https://katex.org/docs/options.html for list of available options
|
|
|
|
// If options formulas[].options field is used, the global options field is ignored.
|
|
|
|
|
|
|
|
// For each line, returns one JSON line with the following schema:
|
|
|
|
// {
|
|
|
|
// results: [
|
|
|
|
// { html?: string } | { error?: string }
|
|
|
|
// ]
|
|
|
|
// } | { error?: string }
|
|
|
|
|
|
|
|
|
|
|
|
// If one formula is invalid, the error in results is used
|
|
|
|
// If the entire query is invalid (couldn't parse JSON, for example), the outer error field is used
|
|
|
|
|
|
|
|
|
|
|
|
import katex from 'katex'
|
|
|
|
import net from 'net'
|
|
|
|
import * as readline from 'readline'
|
|
|
|
|
|
|
|
const myArgs = process.argv.slice(2)
|
|
|
|
|
|
|
|
const unixSocketPath = myArgs[0]
|
|
|
|
if (!unixSocketPath) {
|
|
|
|
console.error('you must specify socket path')
|
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This server listens on a Unix socket at /var/run/mysocket
|
|
|
|
var unixServer = net.createServer(handleClient);
|
|
|
|
unixServer.listen(unixSocketPath);
|
|
|
|
|
|
|
|
function handleExit(signal) {
|
|
|
|
// unixServer.emit('close')
|
|
|
|
unixServer.close(function () {
|
|
|
|
|
|
|
|
});
|
|
|
|
process.exit(0); // put this into the callback to avoid closing open connections
|
|
|
|
}
|
|
|
|
process.on('SIGINT', handleExit);
|
|
|
|
process.on('SIGQUIT', handleExit);
|
|
|
|
process.on('SIGTERM', handleExit);
|
|
|
|
process.on('exit', handleExit);
|
|
|
|
|
|
|
|
const defaultOptions = {}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {net.Socket} socket
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* */
|
|
|
|
function socketWrite(socket, data) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
socket.write(data, (err) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {net.Socket} client
|
|
|
|
* */
|
|
|
|
async function handleClient(client) {
|
|
|
|
const rl = readline.createInterface({ input: client })
|
|
|
|
|
|
|
|
/* Added by GS: A stack of katex's `macros` objects, each group inherits
|
|
|
|
* the one from the parent group and can add its own stuff without
|
|
|
|
* affecting the parent.
|
|
|
|
*/
|
|
|
|
const macroStack = [{}]
|
|
|
|
for await (const line of rl) {
|
|
|
|
try {
|
|
|
|
// The custom commands for pushing and popping the macro stack.
|
|
|
|
if (line === "begingroup") {
|
|
|
|
// Copy the current state of macros and push it onto the stack.
|
|
|
|
macroStack.push({...macroStack.slice(-1)[0]})
|
|
|
|
continue
|
|
|
|
} else if (line === "endgroup") {
|
|
|
|
macroStack.pop()
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
const query = JSON.parse(line)
|
|
|
|
const results = []
|
|
|
|
for (const input of query.formulas) {
|
|
|
|
const options = input.options ?? query.options ?? defaultOptions
|
|
|
|
// Add macros from the macros option
|
|
|
|
if (options.macros) {
|
|
|
|
for (const macro of Object.keys(options.macros)) {
|
|
|
|
macroStack.slice(-1)[macro] = options.macros[macro]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
options.macros = macroStack.slice(-1)[0]
|
|
|
|
// Enforce globalGroup option, katex then saves created macros
|
|
|
|
// into the options.macros object.
|
|
|
|
options.globalGroup = true
|
|
|
|
try {
|
|
|
|
const html = katex.renderToString(input.tex, options)
|
|
|
|
results.push({ html })
|
|
|
|
} catch (e) {
|
|
|
|
results.push({ error: String(e) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await socketWrite(client, JSON.stringify({ results }, null, query.debug ? ' ' : undefined))
|
|
|
|
await socketWrite(client, '\n')
|
|
|
|
} catch (e) {
|
|
|
|
console.error(e)
|
|
|
|
await socketWrite(client, JSON.stringify({ error: String(e) }))
|
|
|
|
await socketWrite(client, '\n')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|