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