Finished KaTeX math rendering.
This commit is contained in:
parent
f71eea3c06
commit
be2d8ed723
4 changed files with 67 additions and 27 deletions
|
@ -23,7 +23,5 @@ 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))
|
||||
k = KatexClient()
|
||||
input()
|
||||
print(k)
|
||||
katexClient = 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))
|
||||
|
|
45
html.py
45
html.py
|
@ -1,11 +1,12 @@
|
|||
from panflute import *
|
||||
from whitespace import NBSP
|
||||
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):
|
||||
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()
|
||||
attributes = ""
|
||||
|
@ -67,14 +68,14 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
|
|||
tag = "pre"
|
||||
|
||||
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):
|
||||
tag = "figcaption"
|
||||
|
||||
if isinstance(e, Image):
|
||||
# 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):
|
||||
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}"'
|
||||
|
||||
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):
|
||||
content_head = "("
|
||||
content_foot = ")"
|
||||
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):
|
||||
tag = "ol"
|
||||
|
@ -110,8 +111,8 @@ def html(e: Element, indent_level: int=0, indent_str: str="\t") -> str:
|
|||
# FIXME: Delimeter styles
|
||||
|
||||
if isinstance(e, Table):
|
||||
content_head = html(e.head, indent_level+1, indent_str)
|
||||
content_foot = html(e.foot, indent_level+1, indent_str)
|
||||
content_head = html(e.head, k, indent_level+1, indent_str)
|
||||
content_foot = html(e.foot, k, indent_level+1, indent_str)
|
||||
# FIXME: Fancy pandoc tables, using colspec
|
||||
|
||||
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 e.style == "cs":
|
||||
if e.quote_type == "SingleQuote":
|
||||
return f'‚{html(e.content, 0, "")}‘'
|
||||
return f'‚{html(e.content, k, 0, "")}‘'
|
||||
elif e.quote_type == "DoubleQuote":
|
||||
return f'„{html(e.content, 0, "")}“'
|
||||
return f'„{html(e.content, k, 0, "")}“'
|
||||
elif e.style == "en":
|
||||
if e.quote_type == "SingleQuote":
|
||||
return f'‘{html(e.content, 0, "")}’'
|
||||
return f'‘{html(e.content, k, 0, "")}’'
|
||||
elif e.quote_type == "DoubleQuote":
|
||||
return f'“{html(e.content, 0, "")}”'
|
||||
return f'“{html(e.content, k, 0, "")}”'
|
||||
else:
|
||||
if e.quote_type == "SingleQuote":
|
||||
return f'\'{html(e.content, 0, "")}\''
|
||||
return f'\'{html(e.content, k, 0, "")}\''
|
||||
elif e.quote_type == "DoubleQuote":
|
||||
return f'"{html(e.content, 0, "")}"'
|
||||
return f'"{html(e.content, k, 0, "")}"'
|
||||
else:
|
||||
return f'"{html(e.content, 0, "")}"'
|
||||
return f'"{html(e.content, k, 0, "")}"'
|
||||
|
||||
if isinstance(e, Str):
|
||||
return e.text.replace(" ", " ")
|
||||
|
||||
if isinstance(e, Math):
|
||||
# TODO
|
||||
return "TODO: MATH"
|
||||
formats = {
|
||||
"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":
|
||||
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'
|
||||
|
||||
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 = ""
|
||||
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 len(e.content) > 0 and isinstance(e.content[0], Inline):
|
||||
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"):
|
||||
out_str += e.text
|
||||
out_str += f"{content_foot}\n"
|
||||
|
|
33
katex.py
33
katex.py
|
@ -1,12 +1,39 @@
|
|||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import json
|
||||
import os
|
||||
from typing import Dict
|
||||
|
||||
|
||||
class KatexError(Exception):
|
||||
pass
|
||||
|
||||
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])
|
||||
self._socket_file = self._temp_dir.name + "/katex-socket"
|
||||
self._server_process = subprocess.Popen(["node", "./katex-server/index.mjs", self._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
10
test.md
|
@ -55,7 +55,15 @@ A non-breakable space bro
|
|||
|
||||
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].
|
||||
|
||||
|
|
Loading…
Reference in a new issue