Browse Source

Finished KaTeX math rendering.

pull/28/head
Jan Černohorský 2 years ago
parent
commit
be2d8ed723
  1. 6
      formatitko.py
  2. 45
      html.py
  3. 33
      katex.py
  4. 10
      test.md

6
formatitko.py

@ -23,7 +23,5 @@ doc = doc.walk(transform, context)
print("---------------------") print("---------------------")
#print(show(doc)) #print(show(doc))
#print(convert_text(doc, input_format="panflute", output_format="markdown")) #print(convert_text(doc, input_format="panflute", output_format="markdown"))
#open("output.html", "w").write("<head> <meta charset='utf-8'> </head>" + html(doc)) katexClient = KatexClient()
k = KatexClient() open("output.html", "w").write("<head> <meta charset='utf-8'> <link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css' integrity='sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0' crossorigin='anonymous'> </head>" + html(doc, katexClient))
input()
print(k)

45
html.py

@ -1,11 +1,12 @@
from panflute import * from panflute import *
from whitespace import NBSP from whitespace import NBSP
from transform import FQuoted from transform import FQuoted
from katex import KatexClient
def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str: def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") -> str:
if isinstance(e, ListContainer): if isinstance(e, ListContainer):
return ''.join([html(child, indent_level, indent_str) for child in e]) return ''.join([html(child, k, indent_level, indent_str) for child in e])
tag = e.tag.lower() tag = e.tag.lower()
attributes = "" attributes = ""
@ -67,14 +68,14 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
tag = "pre" tag = "pre"
if isinstance(e, Figure): if isinstance(e, Figure):
content_foot = html(e.caption, indent_level+1, indent_str) content_foot = html(e.caption, k, indent_level+1, indent_str)
if isinstance(e, Caption): if isinstance(e, Caption):
tag = "figcaption" tag = "figcaption"
if isinstance(e, Image): if isinstance(e, Image):
# TODO: Image processing # TODO: Image processing
return f'<img src="{e.url}" alt="{e.title or html(e.content, 0, "")}">' return f'<img src="{e.url}" alt="{e.title or html(e.content, k, 0, "")}">'
if isinstance(e, Header): if isinstance(e, Header):
tag = "h"+str(e.level) tag = "h"+str(e.level)
@ -86,13 +87,13 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
attributes += f' title="{e.title}"' attributes += f' title="{e.title}"'
if isinstance(e, LineItem): if isinstance(e, LineItem):
return indent_level*indent_str + html(e.content) + "<br>\n" return indent_level*indent_str + html(e.content, k) + "<br>\n"
if isinstance(e, Note): if isinstance(e, Note):
content_head = "(" content_head = "("
content_foot = ")" content_foot = ")"
if len(e.content) == 1 and isinstance(e.content[0], Para): if len(e.content) == 1 and isinstance(e.content[0], Para):
return f' <note>({html(e.content[0].content, 0, "")})</note>' return f' <note>({html(e.content[0].content, k, 0, "")})</note>'
if isinstance(e, OrderedList): if isinstance(e, OrderedList):
tag = "ol" tag = "ol"
@ -110,8 +111,8 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
# FIXME: Delimeter styles # FIXME: Delimeter styles
if isinstance(e, Table): if isinstance(e, Table):
content_head = html(e.head, indent_level+1, indent_str) content_head = html(e.head, k, indent_level+1, indent_str)
content_foot = html(e.foot, indent_level+1, indent_str) content_foot = html(e.foot, k, indent_level+1, indent_str)
# FIXME: Fancy pandoc tables, using colspec # FIXME: Fancy pandoc tables, using colspec
if isinstance(e, TableCell): if isinstance(e, TableCell):
@ -131,28 +132,34 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
if isinstance(e, FQuoted): if isinstance(e, FQuoted):
if e.style == "cs": if e.style == "cs":
if e.quote_type == "SingleQuote": if e.quote_type == "SingleQuote":
return f'{html(e.content, 0, "")}' return f'{html(e.content, k, 0, "")}'
elif e.quote_type == "DoubleQuote": elif e.quote_type == "DoubleQuote":
return f'{html(e.content, 0, "")}' return f'{html(e.content, k, 0, "")}'
elif e.style == "en": elif e.style == "en":
if e.quote_type == "SingleQuote": if e.quote_type == "SingleQuote":
return f'{html(e.content, 0, "")}' return f'{html(e.content, k, 0, "")}'
elif e.quote_type == "DoubleQuote": elif e.quote_type == "DoubleQuote":
return f'{html(e.content, 0, "")}' return f'{html(e.content, k, 0, "")}'
else: else:
if e.quote_type == "SingleQuote": if e.quote_type == "SingleQuote":
return f'\'{html(e.content, 0, "")}\'' return f'\'{html(e.content, k, 0, "")}\''
elif e.quote_type == "DoubleQuote": elif e.quote_type == "DoubleQuote":
return f'"{html(e.content, 0, "")}"' return f'"{html(e.content, k, 0, "")}"'
else: else:
return f'"{html(e.content, 0, "")}"' return f'"{html(e.content, k, 0, "")}"'
if isinstance(e, Str): if isinstance(e, Str):
return e.text.replace(" ", "&nbsp;") return e.text.replace(" ", "&nbsp;")
if isinstance(e, Math): if isinstance(e, Math):
# TODO formats = {
return "TODO: MATH" "DisplayMath": True,
"InlineMath": False
}
# FIXME: Currently, all bits of math are isolated from each other, this
# means that \defs and and alike work only inside a single math block
# and are forgotten in the next one.
return indent_level*indent_str + k.render(e.text, {"displayMode": formats[e.format]})
if isinstance(e, RawInline) and e.format == "html": if isinstance(e, RawInline) and e.format == "html":
return e.text return e.text
@ -161,7 +168,7 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
return f'{e.text}\n' return f'{e.text}\n'
if isinstance(e, Inline): if isinstance(e, Inline):
return f"<{tag}{attributes}>{content_head}{html(e.content, 0, '')}{content_foot}</{tag}>" return f"<{tag}{attributes}>{content_head}{html(e.content, k, 0, '')}{content_foot}</{tag}>"
out_str = "" out_str = ""
if not isinstance(e, Plain): if not isinstance(e, Plain):
@ -170,7 +177,7 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
if hasattr(e, "_content"): if hasattr(e, "_content"):
if len(e.content) > 0 and isinstance(e.content[0], Inline): if len(e.content) > 0 and isinstance(e.content[0], Inline):
out_str += (indent_level+1)*indent_str out_str += (indent_level+1)*indent_str
out_str += html(e.content, indent_level+1, indent_str) out_str += html(e.content, k, indent_level+1, indent_str)
if hasattr(e, "text"): if hasattr(e, "text"):
out_str += e.text out_str += e.text
out_str += f"{content_foot}\n" out_str += f"{content_foot}\n"

33
katex.py

@ -1,12 +1,39 @@
import socket import socket
import subprocess import subprocess
import tempfile import tempfile
import json
import os
from typing import Dict
class KatexError(Exception):
pass
class KatexClient: class KatexClient:
def __init__(self): def __init__(self):
self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self._client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko') self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko')
socket_file = self._temp_dir.name + "/katex-socket" self._socket_file = self._temp_dir.name + "/katex-socket"
self._client.bind(socket_file) self._server_process = subprocess.Popen(["node", "./katex-server/index.mjs", self._socket_file])
self._server = subprocess.Popen(["node", "./katex-server/index.mjs", socket_file]) while not os.path.exists(self._socket_file):
pass
while True:
try:
self._client.connect(self._socket_file)
except ConnectionRefusedError:
continue
break
def render(self, tex: str, options: Dict={}):
self._client.sendall((json.dumps({"formulas":[{"tex":tex, "options": options}]})+"\n").encode("utf-8"))
data = self._client.recv(128)
while data[-1] != 0x0a:
data += self._client.recv(128)
response = json.loads(data)
if "error" in response:
raise Exception(response["error"])
if "error" in response["results"][0]:
raise KatexError(response["results"][0]["error"])
else:
return response["results"][0]["html"]

10
test.md

@ -55,7 +55,15 @@ A non-breakable&nbsp;space bro
A lot of spaces A lot of spaces
A text with some $math$. A text with some inline math: $\sum_{i=1}^nn^2$. Plus some display math:
$$
\def\eqalign#1{\begin{align*}#1\end{align*}}
\eqalign{
2 x_2 + 6 x_3 &= 14 \cr
x_1 - 3 x_2 + 2 x_3 &= 5 \cr
-x_1 + 4 x_2 + \phantom{1} x_3 &= 2
}
$$
This should be seen by all^[This is a footnote]. This should be seen by all^[This is a footnote].

Loading…
Cancel
Save