diff --git a/formatitko.py b/formatitko.py
index 62c4f33..4293929 100755
--- a/formatitko.py
+++ b/formatitko.py
@@ -10,6 +10,7 @@ from transform import transform
 from util import *
 from context import Context
 from html import html
+from katex import KatexClient
 
 from mj_show import show
 
@@ -22,5 +23,7 @@ doc = doc.walk(transform, context)
 print("---------------------")
 #print(show(doc))
 #print(convert_text(doc, input_format="panflute", output_format="markdown"))
-open("output.html", "w").write("<head> <meta charset='utf-8'> </head>" + html(doc))
-
+#open("output.html", "w").write("<head> <meta charset='utf-8'> </head>" + html(doc))
+k = KatexClient()
+input()
+print(k)
diff --git a/katex-server/.gitignore b/katex-server/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/katex-server/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/katex-server/README.md b/katex-server/README.md
new file mode 100644
index 0000000..082cd55
--- /dev/null
+++ b/katex-server/README.md
@@ -0,0 +1 @@
+This was made by Standa Lukeš @exyi
diff --git a/katex-server/index.js b/katex-server/index.js
new file mode 100644
index 0000000..d6754c8
--- /dev/null
+++ b/katex-server/index.js
@@ -0,0 +1 @@
+console.log(require('katex').renderToString('\\frac{2a}{b}'))
diff --git a/katex-server/index.mjs b/katex-server/index.mjs
new file mode 100644
index 0000000..9987351
--- /dev/null
+++ b/katex-server/index.mjs
@@ -0,0 +1,105 @@
+// 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 })
+
+    for await (const line of rl) {
+        try {
+            const query = JSON.parse(line)
+
+            const results = []
+            for (const input of query.formulas) {
+                const options = input.options ?? query.options ?? defaultOptions
+                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')
+        }
+    }
+}
+
diff --git a/katex-server/package-lock.json b/katex-server/package-lock.json
new file mode 100644
index 0000000..9307bf4
--- /dev/null
+++ b/katex-server/package-lock.json
@@ -0,0 +1,21 @@
+{
+  "name": "ksp-katex-server",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "commander": {
+      "version": "8.3.0",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
+      "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="
+    },
+    "katex": {
+      "version": "0.16.3",
+      "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.3.tgz",
+      "integrity": "sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==",
+      "requires": {
+        "commander": "^8.0.0"
+      }
+    }
+  }
+}
diff --git a/katex-server/package.json b/katex-server/package.json
new file mode 100644
index 0000000..3d68121
--- /dev/null
+++ b/katex-server/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "ksp-katex-server",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.mjs",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "katex": "^0.16.3"
+  }
+}
diff --git a/katex.py b/katex.py
new file mode 100644
index 0000000..dc54da9
--- /dev/null
+++ b/katex.py
@@ -0,0 +1,12 @@
+import socket
+import subprocess
+import tempfile
+
+
+class KatexClient:
+	def __init__(self):
+		self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+		self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko')
+		socket_file = self._temp_dir.name + "/katex-socket"
+		self._client.bind(socket_file)
+		self._server = subprocess.Popen(["node", "./katex-server/index.mjs", socket_file])