Main file cleanup, image processing.
This commit is contained in:
		
							parent
							
								
									1e2b306b15
								
							
						
					
					
						commit
						303dcfaa1f
					
				
					 14 changed files with 362 additions and 57 deletions
				
			
		
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -1,2 +1,11 @@ | ||||||
| **/__pycache__ | **/__pycache__ | ||||||
| output.* | output.* | ||||||
|  | *.log | ||||||
|  | *.aux | ||||||
|  | test/test.pdf | ||||||
|  | test/test.tex | ||||||
|  | public/ | ||||||
|  | *.png | ||||||
|  | *.pdf | ||||||
|  | *.jpeg | ||||||
|  | *.svg | ||||||
|  |  | ||||||
|  | @ -1,9 +1,10 @@ | ||||||
| #!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||||
| 
 | 
 | ||||||
| 
 | import argparse | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
| from typing import List | from typing import List | ||||||
|  | import os | ||||||
| 
 | 
 | ||||||
| # Import local files | # Import local files | ||||||
| from transform import transform | from transform import transform | ||||||
|  | @ -13,21 +14,30 @@ from group import Group | ||||||
| from katex import KatexClient | from katex import KatexClient | ||||||
| from html import html | from html import html | ||||||
| from tex import tex | from tex import tex | ||||||
|  | from images import ImageProcessor | ||||||
| 
 | 
 | ||||||
| from mj_show import show | from mj_show import show | ||||||
| 
 | 
 | ||||||
| doc = import_md(open(sys.argv[1], "r").read()) | parser = argparse.ArgumentParser() | ||||||
|  | parser.add_argument("-l", "--img-lookup-dirs", help="Image lookup directories. When processing images, the program will try to find the image in them first. By default contains the directory of the MarkDown file.", nargs="+", default=[]) | ||||||
|  | parser.add_argument("-p", "--img-public-dir", help="Directory to put processed images into. The program will not overwrite existing images.", nargs=1, default="public") | ||||||
|  | parser.add_argument("-w", "--output-html", help="The HTML file (for Web) to write into.", nargs=1, default="output.html") | ||||||
|  | parser.add_argument("-t", "--output-tex", help="The TEX file to write into.", nargs=1, default="output.tex") | ||||||
|  | parser.add_argument("input_filename", help="The MarkDown file to process.") | ||||||
|  | args = parser.parse_args() | ||||||
|  | 
 | ||||||
|  | doc = import_md(open(args.input_filename, "r").read()) | ||||||
|  | 
 | ||||||
| language = doc.get_metadata("language", None, True) | language = doc.get_metadata("language", None, True) | ||||||
| print(show(doc)) |  | ||||||
| context = Context(doc, sys.argv[1]) | context = Context(doc, sys.argv[1]) | ||||||
|  | 
 | ||||||
| doc = doc.walk(transform, context) | doc = doc.walk(transform, context) | ||||||
|  | 
 | ||||||
| doc.content = [Group(*doc.content, metadata={"language":language})] | doc.content = [Group(*doc.content, metadata={"language":language})] | ||||||
| #print("---------------------") | 
 | ||||||
| #print(show(doc)) |  | ||||||
| #print(convert_text(doc, input_format="panflute", output_format="markdown")) |  | ||||||
| katexClient = KatexClient() | katexClient = KatexClient() | ||||||
| #print(katexClient.render("\\def\\Bruh{K^A\\TeX}")) | doc_dir = os.path.dirname(args.input_filename) if os.path.dirname(args.input_filename) != "" else "." | ||||||
| #print(katexClient.render("\\Bruh")) | imageProcessor = ImageProcessor(args.img_public_dir, doc_dir, *args.img_lookup_dirs) | ||||||
| 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)) | 
 | ||||||
| open("output.tex", "w").write("\input formatitko.tex\n" + tex(doc)) | open(args.output_html, "w").write(html(doc, katexClient, imageProcessor)) | ||||||
| #print(tex(doc)) | open(args.output_tex, "w").write(tex(doc, imageProcessor)) | ||||||
|  |  | ||||||
							
								
								
									
										50
									
								
								html.py
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								html.py
									
									
									
									
									
								
							|  | @ -3,20 +3,22 @@ from pygments import highlight | ||||||
| from pygments.lexers import get_lexer_by_name | from pygments.lexers import get_lexer_by_name | ||||||
| from pygments.formatters import HtmlFormatter | from pygments.formatters import HtmlFormatter | ||||||
| from pygments.util import ClassNotFound | from pygments.util import ClassNotFound | ||||||
|  | import os | ||||||
| 
 | 
 | ||||||
| from whitespace import NBSP | from whitespace import NBSP | ||||||
| from transform import FQuoted | from transform import FQuoted | ||||||
| from katex import KatexClient | from katex import KatexClient | ||||||
| from util import inlinify | from util import inlinify | ||||||
| from group import Group | from group import Group | ||||||
|  | from images import ImageProcessor | ||||||
| 
 | 
 | ||||||
| def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") -> str: | def html(e: Element, k: KatexClient, i: ImageProcessor, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 	 | 	 | ||||||
| 	if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "html": | 	if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "html": | ||||||
| 		return "" | 		return "" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, ListContainer): | 	if isinstance(e, ListContainer): | ||||||
| 		return ''.join([html(child, k, indent_level, indent_str) for child in e]) | 		return ''.join([html(child, k, i, indent_level, indent_str) for child in e]) | ||||||
| 
 | 
 | ||||||
| 	tag = e.tag.lower() | 	tag = e.tag.lower() | ||||||
| 	attributes = "" | 	attributes = "" | ||||||
|  | @ -95,14 +97,24 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 			return f'<pre>{e.text}</pre>' | 			return f'<pre>{e.text}</pre>' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Figure): | 	if isinstance(e, Figure): | ||||||
| 		content_foot = html(e.caption, k, indent_level+1, indent_str) | 		content_foot = html(e.caption, k, i, 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 | 		url = e.url | ||||||
| 		return f'<img src="{e.url}" alt="{e.title or html(e.content, k, 0, "")}">' | 		_, ext = os.path.splitext(url) | ||||||
|  | 		ext = ext[1:] | ||||||
|  | 		if ext in ["svg", "png", "jpeg", "gif"]: | ||||||
|  | 			url = i.process_image(url, ext) | ||||||
|  | 		elif ext in ["pdf", "epdf"]: | ||||||
|  | 			url = i.process_image(url, "png", dpi=300) | ||||||
|  | 		elif ext in ["jpg"]: | ||||||
|  | 			url = i.process_image(url, "jpeg") | ||||||
|  | 		else: | ||||||
|  | 			url = i.process_image(url, ".png") | ||||||
|  | 		return f'<img src="{url}" {"style=width:"+e.attributes["width"] if "width" in e.attributes else ""} alt="{e.title or html(e.content, k, i, 0, "")}">' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Header): | 	if isinstance(e, Header): | ||||||
| 		tag = "h"+str(e.level) | 		tag = "h"+str(e.level) | ||||||
|  | @ -114,13 +126,13 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 			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, k) + "<br>\n" | 		return indent_level*indent_str + html(e.content, k, i) + "<br>\n" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Note): | 	if isinstance(e, Note): | ||||||
| 		content_head = "(" | 		content_head = "(" | ||||||
| 		content_foot = ")" | 		content_foot = ")" | ||||||
| 		if inlinify(e) is not None: | 		if inlinify(e) is not None: | ||||||
| 			return f' <note>({html(inlinify(e), k, 0, "")})</note>' | 			return f' <note>({html(inlinify(e), k, i, 0, "")})</note>' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, OrderedList): | 	if isinstance(e, OrderedList): | ||||||
| 		tag = "ol" | 		tag = "ol" | ||||||
|  | @ -138,8 +150,8 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 		# FIXME: Delimeter styles | 		# FIXME: Delimeter styles | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Table): | 	if isinstance(e, Table): | ||||||
| 		content_head = html(e.head, k, indent_level+1, indent_str) | 		content_head = html(e.head, k, i, indent_level+1, indent_str) | ||||||
| 		content_foot = html(e.foot, k, indent_level+1, indent_str) | 		content_foot = html(e.foot, k, i, 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): | ||||||
|  | @ -159,25 +171,25 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 	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, k, 0, "")}‘' | 				return f'‚{html(e.content, k, i, 0, "")}‘' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'„{html(e.content, k, 0, "")}“' | 				return f'„{html(e.content, k, i, 0, "")}“' | ||||||
| 		elif e.style == "en": | 		elif e.style == "en": | ||||||
| 			if e.quote_type == "SingleQuote": | 			if e.quote_type == "SingleQuote": | ||||||
| 				return f'‘{html(e.content, k, 0, "")}’' | 				return f'‘{html(e.content, k, i, 0, "")}’' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'“{html(e.content, k, 0, "")}”' | 				return f'“{html(e.content, k, i, 0, "")}”' | ||||||
| 		else: | 		else: | ||||||
| 			if e.quote_type == "SingleQuote": | 			if e.quote_type == "SingleQuote": | ||||||
| 				return f'\'{html(e.content, k, 0, "")}\'' | 				return f'\'{html(e.content, k, i, 0, "")}\'' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'"{html(e.content, k, 0, "")}"' | 				return f'"{html(e.content, k, i, 0, "")}"' | ||||||
| 			else: | 			else: | ||||||
| 				return f'"{html(e.content, k, 0, "")}"' | 				return f'"{html(e.content, k, i, 0, "")}"' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Group): | 	if isinstance(e, Group): | ||||||
| 		k.begingroup() | 		k.begingroup() | ||||||
| 		ret = html(e.content, k, indent_level, indent_str) | 		ret = html(e.content, k, i, indent_level, indent_str) | ||||||
| 		k.endgroup() | 		k.endgroup() | ||||||
| 		return ret | 		return ret | ||||||
| 
 | 
 | ||||||
|  | @ -204,7 +216,7 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 			return "" | 			return "" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Inline): | 	if isinstance(e, Inline): | ||||||
| 		return f'<{tag}{attributes}>{content_head}{html(e.content, k, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}</{tag}>' | 		return f'<{tag}{attributes}>{content_head}{html(e.content, k, i, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}</{tag}>' | ||||||
| 
 | 
 | ||||||
| 	out_str = "" | 	out_str = "" | ||||||
| 	if not isinstance(e, Plain): | 	if not isinstance(e, Plain): | ||||||
|  | @ -213,7 +225,7 @@ def html(e: Element, k: KatexClient, indent_level: int=0, indent_str: str="\t") | ||||||
| 	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, k, indent_level+1, indent_str) | 		out_str += html(e.content, k, i, 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" | ||||||
|  |  | ||||||
							
								
								
									
										65
									
								
								images.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								images.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | ||||||
|  | from typing import List | ||||||
|  | import os | ||||||
|  | import shutil | ||||||
|  | import subprocess | ||||||
|  | 
 | ||||||
|  | class ImageProcessor: | ||||||
|  | 	def __init__(self, public_dir: str, *lookup_dirs: List[str]): | ||||||
|  | 		self.public_dir = public_dir | ||||||
|  | 		self.lookup_dirs = lookup_dirs | ||||||
|  | 		if not os.path.exists(self.public_dir): | ||||||
|  | 			os.mkdir(self.public_dir) | ||||||
|  | 
 | ||||||
|  | 	def process_image(self, input_filename: str, format: str, relative=True, width: int=None, height:int=None, quality: int=None, dpi: int=None) -> str: | ||||||
|  | 		name = os.path.basename(input_filename) | ||||||
|  | 		base, ext = os.path.splitext(name) | ||||||
|  | 		ext = ext[1:] | ||||||
|  | 		full_path = self.find_image(input_filename) | ||||||
|  | 		if full_path is None: | ||||||
|  | 			raise FileNotFoundError(f'Image {input_filename} not found.') | ||||||
|  | 		 | ||||||
|  | 		suffix = "" | ||||||
|  | 		geometry = None | ||||||
|  | 		if width is not None or height is not None: | ||||||
|  | 			geometry = f'{width if width is not None else ""}x{height if height is not None else ""}' | ||||||
|  | 			suffix += "_"+geometry | ||||||
|  | 		if quality is not None: | ||||||
|  | 			suffix += f'_q{quality}' | ||||||
|  | 		if quality is not None: | ||||||
|  | 			suffix += f'_d{dpi}' | ||||||
|  | 		target_name = base+suffix+"."+format | ||||||
|  | 		target_path = self.public_dir + "/" + target_name | ||||||
|  | 		 | ||||||
|  | 		if not os.path.isfile(target_path): | ||||||
|  | 			if (((ext == format and width) | ||||||
|  | 		     or (ext == "epdf" and format == "pdf") | ||||||
|  | 			 or (ext == "jpg" and format == "jpeg")) | ||||||
|  | 			 and width is None and height is None and quality is None and dpi is None): | ||||||
|  | 				shutil.copyfile(full_path, target_path) | ||||||
|  | 
 | ||||||
|  | 			elif self.find_image(target_name): | ||||||
|  | 				shutil.copyfile(self.find_image(target_name), target_path) | ||||||
|  | 
 | ||||||
|  | 			elif ext == "svg": | ||||||
|  | 				width_arg = ['--export-width', str(width)] if width is not None else [] | ||||||
|  | 				height_arg = ['--export-height', str(height)] if height is not None else [] | ||||||
|  | 				dpi_arg = ['--export-dpi', str(dpi)] if dpi is not None else [] | ||||||
|  | 				if subprocess.run(['inkscape', full_path, '-o', target_path, *width_arg, *height_arg, *dpi_arg]).returncode != 0: | ||||||
|  | 					raise Exception(f"Could not convert '{full_path}' to '{format}'") | ||||||
|  | 
 | ||||||
|  | 			else: | ||||||
|  | 				resize_arg = ['-resize', str(geometry)] if geometry is not None else [] | ||||||
|  | 				density_arg = ['-density', str(dpi)] if dpi is not None else [] | ||||||
|  | 				quality_arg = ['-quality', str(quality)] if quality is not None else [] | ||||||
|  | 				if subprocess.run(['convert', full_path, *resize_arg, *density_arg, *quality_arg, target_path]).returncode != 0: | ||||||
|  | 					raise Exception(f"Could not convert '{full_path}' to '{format}'") | ||||||
|  | 
 | ||||||
|  | 		return target_name if relative else target_path | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 			 | ||||||
|  | 
 | ||||||
|  | 	def find_image(self, input_filename) -> str: | ||||||
|  | 		for dir in self.lookup_dirs: | ||||||
|  | 			if os.path.isfile(dir + "/" + input_filename): | ||||||
|  | 				return dir + "/" + input_filename | ||||||
							
								
								
									
										2
									
								
								katex.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								katex.py
									
									
									
									
									
								
							|  | @ -14,7 +14,7 @@ class KatexClient: | ||||||
| 		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') | ||||||
| 		self._socket_file = self._temp_dir.name + "/katex-socket" | 		self._socket_file = self._temp_dir.name + "/katex-socket" | ||||||
| 		self._server_process = subprocess.Popen(["node", "./katex-server/index.mjs", self._socket_file]) | 		self._server_process = subprocess.Popen(["node", os.path.dirname(os.path.realpath(__file__)) + "/katex-server/index.mjs", self._socket_file]) | ||||||
| 		while not os.path.exists(self._socket_file): | 		while not os.path.exists(self._socket_file): | ||||||
| 			pass | 			pass | ||||||
| 		while True: | 		while True: | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								test.md
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								test.md
									
									
									
									
									
								
							|  | @ -47,7 +47,7 @@ This should only be shown to cats the second time | ||||||
| 
 | 
 | ||||||
| # [$are_we_there_yet]{} | # [$are_we_there_yet]{} | ||||||
| 
 | 
 | ||||||
| {width=10em} |  | ||||||
| 
 | 
 | ||||||
| {width=10em} | {width=10em} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										13
									
								
								test/Makefile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								test/Makefile
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | ||||||
|  | all: test.pdf public/test.html | ||||||
|  | 
 | ||||||
|  | output.tex output.html: | ||||||
|  | 	../formatitko.py test.md | ||||||
|  | 
 | ||||||
|  | public/test.html: output.html | ||||||
|  | 	cat test-top.html output.html > public/test.html | ||||||
|  | 
 | ||||||
|  | test.tex: output.tex | ||||||
|  | 	cat test-top.tex output.tex > test.tex | ||||||
|  | 
 | ||||||
|  | test.pdf: test.tex | ||||||
|  | 	TEXINPUTS=.:../ucwmac:${TEXINPUTS} luatex -halt-on-error -interaction nonstopmode test.tex | ||||||
							
								
								
									
										
											BIN
										
									
								
								test/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								test/logo.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 17 KiB | 
							
								
								
									
										8
									
								
								test/test-top.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								test/test-top.html
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,8 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html> | ||||||
|  | <head> | ||||||
|  | 	<meta name="viewport" content="width=device-width, initial-scale=1"> | ||||||
|  | 	<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> | ||||||
|  | <body> | ||||||
							
								
								
									
										1
									
								
								test/test-top.tex
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								test/test-top.tex
									
									
									
									
									
										Symbolic link
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | ../formatitko.tex | ||||||
							
								
								
									
										166
									
								
								test/test.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										166
									
								
								test/test.md
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,166 @@ | ||||||
|  | --- | ||||||
|  | title: 'Wooooo a title' | ||||||
|  | subtitle: 'A subtitle' | ||||||
|  | are_we_there_yet: False | ||||||
|  | language: "en" | ||||||
|  | --- | ||||||
|  | [#test-import.md]{} | ||||||
|  | 
 | ||||||
|  | # Hello world! | ||||||
|  | 
 | ||||||
|  | This is an *example* **yay**! | ||||||
|  | 
 | ||||||
|  | This is *very **strongly** emphasised* | ||||||
|  | 
 | ||||||
|  | Příliš žluťoučký kůň pěl dábelské ódy. *Příliš žluťoučký kůň pěl dábelské ódy.* **Příliš žluťoučký kůň pěl dábelské ódy.** ***Příliš žluťoučký kůň pěl dábelské ódy.*** | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | :::{partial=test-partial.md} | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | :::{if=cat} | ||||||
|  | This should only be shown to cats | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | ```python {.run} | ||||||
|  | ctx.set_flag("cat", True) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```python {.run} | ||||||
|  | println(f"The main document's title is '{ctx.get_metadata('title')}'") | ||||||
|  | ctx.set_metadata("a", {}) | ||||||
|  | ctx.set_metadata("a.b", {}) | ||||||
|  | ctx.set_metadata("a.b.c", "Bruh **bruh** bruh") | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | ```python {style=native} | ||||||
|  | def bruh(no): | ||||||
|  |     wat | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Inline `code` | ||||||
|  | 
 | ||||||
|  | ::::{if=cat} | ||||||
|  | This should only be shown to cats the second time | ||||||
|  | :::: | ||||||
|  | 
 | ||||||
|  | # [$are_we_there_yet]{} | ||||||
|  | 
 | ||||||
|  | {width=50%} | ||||||
|  | 
 | ||||||
|  | {width=50%} | ||||||
|  | 
 | ||||||
|  | {width=50%} | ||||||
|  | 
 | ||||||
|  | {width=10em} | ||||||
|  | 
 | ||||||
|  | {width=10em} | ||||||
|  | 
 | ||||||
|  | {width=10em} | ||||||
|  | 
 | ||||||
|  | ```python {.run} | ||||||
|  | ctx.set_metadata("language", "cs") | ||||||
|  | ``` | ||||||
|  | [!opendatatask]{} | ||||||
|  | ```python {.run} | ||||||
|  | ctx.set_metadata("language","en") | ||||||
|  | ``` | ||||||
|  | [This too!]{if=cat} | ||||||
|  | 
 | ||||||
|  | [What]{.co} | ||||||
|  | 
 | ||||||
|  | [An inline command with contents and **bold** and another [!nop]{} inside!]{c=nop} | ||||||
|  | 
 | ||||||
|  | [!nop]{a=b}<!-- A special command! WOW --> | ||||||
|  | 
 | ||||||
|  | > OOO a blockquote mate init | ||||||
|  | > | ||||||
|  | >> Nesting?? | ||||||
|  | >> Woah | ||||||
|  | 
 | ||||||
|  | A non-breakable space bro | ||||||
|  | 
 | ||||||
|  | A lot              of spaces | ||||||
|  | 
 | ||||||
|  | A text with some inline math: $\sum_{i=1}^nn^2$. Plus some display math: | ||||||
|  | 
 | ||||||
|  | A link with the link in the link: <https://bruh.com> | ||||||
|  | 
 | ||||||
|  | H~2~O is a liquid.  2^10^ is 1024. | ||||||
|  | 
 | ||||||
|  | [Underline]{.underline} | ||||||
|  | 
 | ||||||
|  | :::{only=html} | ||||||
|  | $$ | ||||||
|  | \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 | ||||||
|  | } | ||||||
|  | $$ | ||||||
|  | 
 | ||||||
|  | :::{partial=test-partial.md} | ||||||
|  | ::: | ||||||
|  | 
 | ||||||
|  | --- | ||||||
|  | 
 | ||||||
|  | This should be seen by all.^[This is a footnote] | ||||||
|  | 
 | ||||||
|  | | Matematicko-fyzikální fakulta University Karlovy | ||||||
|  | | Malostranské nám. 2/25 | ||||||
|  | | 118 00 Praha 1 | ||||||
|  | 
 | ||||||
|  | More footnotes.^[I am a foot] | ||||||
|  | 
 | ||||||
|  | To Do: | ||||||
|  | 
 | ||||||
|  | - buy eggs | ||||||
|  | - buy milk | ||||||
|  | - ??? | ||||||
|  | - profit | ||||||
|  |     - also create sublists preferrably | ||||||
|  | 
 | ||||||
|  | 1. Woah | ||||||
|  | 2. Wooo | ||||||
|  | 3. no | ||||||
|  | 
 | ||||||
|  | 4) WOO | ||||||
|  | 
 | ||||||
|  | ``` {=html} | ||||||
|  | <figure> | ||||||
|  |     <video src="woah.mp4" autoplay></video> | ||||||
|  |     <figcaption> This is indeed a video </figcaption> | ||||||
|  | </figure> | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | #. brum | ||||||
|  | #. BRUHHH | ||||||
|  | #. woah | ||||||
|  | 
 | ||||||
|  | i. bro | ||||||
|  | ii. wym bro | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | +---------------------+-----------------------+ | ||||||
|  | | Location            | Temperature 1961-1990 | | ||||||
|  | |                     | in degree Celsius     | | ||||||
|  | +---------------------+-------+-------+-------+ | ||||||
|  | |                     | min   | mean  | max   | | ||||||
|  | +=====================+=======+=======+======:+ | ||||||
|  | | Antarctica          | -89.2 | N/A   | 19.8  | | ||||||
|  | +---------------------+-------+-------+-------+ | ||||||
|  | | Earth               | -89.2 | 14    | 56.7  | | ||||||
|  | +---------------------+-------+-------+-------+ | ||||||
|  | 
 | ||||||
|  | -------     ------ ----------   ------- | ||||||
|  |      12     12        12             12 | ||||||
|  |     123     123       123           123 | ||||||
|  |       1     1          1        1 | ||||||
|  | -------     ------ ----------   ------- | ||||||
|  | 
 | ||||||
							
								
								
									
										71
									
								
								tex.py
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								tex.py
									
									
									
									
									
								
							|  | @ -1,18 +1,20 @@ | ||||||
| from panflute import * | from panflute import * | ||||||
|  | import os | ||||||
| 
 | 
 | ||||||
| from whitespace import NBSP | from whitespace import NBSP | ||||||
| from transform import FQuoted | from transform import FQuoted | ||||||
| from util import inlinify | from util import inlinify | ||||||
| from group import Group | from group import Group | ||||||
|  | from images import ImageProcessor | ||||||
| 
 | 
 | ||||||
| # Heavily inspired by: git://git.ucw.cz/labsconf2022.git | # Heavily inspired by: git://git.ucw.cz/labsconf2022.git | ||||||
| def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | def tex(e: Element, i: ImageProcessor, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 
 | 
 | ||||||
| 	if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex": | 	if hasattr(e, "attributes") and "only" in e.attributes and e.attributes["only"] != "tex": | ||||||
| 		return "" | 		return "" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, ListContainer): | 	if isinstance(e, ListContainer): | ||||||
| 		return ''.join([tex(child, indent_level, indent_str) for child in e]) | 		return ''.join([tex(child, i, indent_level, indent_str) for child in e]) | ||||||
| 
 | 
 | ||||||
| 	content_foot = "" | 	content_foot = "" | ||||||
| 	content_head = "" | 	content_head = "" | ||||||
|  | @ -55,26 +57,26 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 		return e.text.replace(" ", "~").replace(" ", "~") | 		return e.text.replace(" ", "~").replace(" ", "~") | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Para): | 	if isinstance(e, Para): | ||||||
| 		return tex(e.content, 0, "")+"\n\n" | 		return tex(e.content, i, 0, "")+"\n\n" | ||||||
| 
 | 
 | ||||||
| 	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'‚{tex(e.content, 0, "")}‘' | 				return f'‚{tex(e.content, i, 0, "")}‘' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'„{tex(e.content, 0, "")}“' | 				return f'„{tex(e.content, i, 0, "")}“' | ||||||
| 		elif e.style == "en": | 		elif e.style == "en": | ||||||
| 			if e.quote_type == "SingleQuote": | 			if e.quote_type == "SingleQuote": | ||||||
| 				return f'‘{tex(e.content, 0, "")}’' | 				return f'‘{tex(e.content, i, 0, "")}’' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'“{tex(e.content, 0, "")}”' | 				return f'“{tex(e.content, i, 0, "")}”' | ||||||
| 		else: | 		else: | ||||||
| 			if e.quote_type == "SingleQuote": | 			if e.quote_type == "SingleQuote": | ||||||
| 				return f'\'{tex(e.content, 0, "")}\'' | 				return f'\'{tex(e.content, i, 0, "")}\'' | ||||||
| 			elif e.quote_type == "DoubleQuote": | 			elif e.quote_type == "DoubleQuote": | ||||||
| 				return f'"{tex(e.content, 0, "")}"' | 				return f'"{tex(e.content, i, 0, "")}"' | ||||||
| 			else: | 			else: | ||||||
| 				return f'"{tex(e.content, 0, "")}"' | 				return f'"{tex(e.content, i, 0, "")}"' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, BulletList): | 	if isinstance(e, BulletList): | ||||||
| 		tag = "list" | 		tag = "list" | ||||||
|  | @ -106,14 +108,33 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 		# FIXME: Starting number of list | 		# FIXME: Starting number of list | ||||||
| 	 | 	 | ||||||
| 	if isinstance(e, Image): | 	if isinstance(e, Image): | ||||||
| 		return f'\\image{{width {e.attributes["width"] if "width" in e.attributes else ""}}}{{{e.url}}}' | 		url = e.url | ||||||
|  | 		_, ext = os.path.splitext(url) | ||||||
|  | 		ext = ext[1:] | ||||||
|  | 		if ext in ["pdf", "png", "jpeg"]: | ||||||
|  | 			url = i.process_image(url, ext, relative=False) | ||||||
|  | 		elif ext in ["svg"]: | ||||||
|  | 			url = i.process_image(url, "pdf", relative=False) | ||||||
|  | 		elif ext in ["epdf"]: | ||||||
|  | 			url = i.process_image(url, "pdf", relative=False) | ||||||
|  | 		elif ext in ["jpg"]: | ||||||
|  | 			url = i.process_image(url, "jpeg", relative=False) | ||||||
|  | 		else: | ||||||
|  | 			url = i.process_image(url, "pdf", relative=False) | ||||||
|  | 		width = "" | ||||||
|  | 		if "width" in e.attributes: | ||||||
|  | 			width = e.attributes["width"] | ||||||
|  | 			if e.attributes["width"][-1] == "%": | ||||||
|  | 				width = str(int(e.attributes["width"][:-1])/100) + "\\hsize" | ||||||
|  | 			width = "width " + width | ||||||
|  | 		return f'\\image{{{width}}}{{{url}}}' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Figure): | 	if isinstance(e, Figure): | ||||||
| 		return f'\\figure{{{tex(e.content, indent_level+1, indent_str)}}}{{{tex(e.caption, indent_level+1, indent_str)}}}\n\n' | 		return f'\\figure{{{tex(e.content, i, indent_level+1, indent_str)}}}{{{tex(e.caption, i, indent_level+1, indent_str)}}}\n\n' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Caption): | 	if isinstance(e, Caption): | ||||||
| 		if inlinify(e) is not None: | 		if inlinify(e) is not None: | ||||||
| 			return f'\\caption{{{tex(e.content, 0, "")}}}' | 			return f'\\caption{{{tex(e.content, i, 0, "")}}}' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, ListItem): | 	if isinstance(e, ListItem): | ||||||
| 		tag = ":" | 		tag = ":" | ||||||
|  | @ -134,7 +155,7 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 	if isinstance(e, Note): | 	if isinstance(e, Note): | ||||||
| 		tag = "fn" | 		tag = "fn" | ||||||
| 		if inlinify(e) is not None: | 		if inlinify(e) is not None: | ||||||
| 			return f'\\fn{{{tex(inlinify(e), 0, "")}}}' | 			return f'\\fn{{{tex(inlinify(e), i, 0, "")}}}' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Table): | 	if isinstance(e, Table): | ||||||
| 		aligns = { | 		aligns = { | ||||||
|  | @ -144,16 +165,16 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 			"AlignDefault": "\\quad#\\quad\\hfil" | 			"AlignDefault": "\\quad#\\quad\\hfil" | ||||||
| 		} | 		} | ||||||
| 		text = "\strut"+"&".join([aligns[col[0]] for col in e.colspec])+"\cr\n" | 		text = "\strut"+"&".join([aligns[col[0]] for col in e.colspec])+"\cr\n" | ||||||
| 		text += tex(e.head.content, 0, "") | 		text += tex(e.head.content, i, 0, "") | ||||||
| 		text += "\\noalign{\\hrule}\n" | 		text += "\\noalign{\\hrule}\n" | ||||||
| 		text += tex(e.content[0].content, 0, "") | 		text += tex(e.content[0].content, i, 0, "") | ||||||
| 		text += "\\noalign{\\hrule}\n" | 		text += "\\noalign{\\hrule}\n" | ||||||
| 		text += tex(e.foot.content, 0, "") | 		text += tex(e.foot.content, i, 0, "") | ||||||
| 		return "\\vskip1em\n\\halign{"+text+"}\n\\vskip1em\n" | 		return "\\vskip1em\n\\halign{"+text+"}\n\\vskip1em\n" | ||||||
| 		# FIXME: Implement rowspan | 		# FIXME: Implement rowspan | ||||||
| 	 | 	 | ||||||
| 	if isinstance(e, TableRow): | 	if isinstance(e, TableRow): | ||||||
| 		return "&".join([("\\multispan"+str(cell.colspan)+" " if cell.colspan > 1 else "")+tex(cell.content, 0, "") for cell in e.content])+"\cr\n" | 		return "&".join([("\\multispan"+str(cell.colspan)+" " if cell.colspan > 1 else "")+tex(cell.content, i, 0, "") for cell in e.content])+"\cr\n" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, RawInline): | 	if isinstance(e, RawInline): | ||||||
| 		if e.format == "tex": | 		if e.format == "tex": | ||||||
|  | @ -168,13 +189,13 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 			return "" | 			return "" | ||||||
| 	 | 	 | ||||||
| 	if isinstance(e, Span) or isinstance(e, Plain): | 	if isinstance(e, Span) or isinstance(e, Plain): | ||||||
| 		return tex(e.content, 0, "") | 		return tex(e.content, i, 0, "") | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, LineItem): | 	if isinstance(e, LineItem): | ||||||
| 		return tex(e.content, 0, "") + ("\\\\\n" if e.next else "\n") | 		return tex(e.content, i, 0, "") + ("\\\\\n" if e.next else "\n") | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, LineBlock): | 	if isinstance(e, LineBlock): | ||||||
| 		return f'{tex(e.content, indent_level+1, indent_str)}\n' | 		return f'{tex(e.content, i, indent_level+1, indent_str)}\n' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Group): | 	if isinstance(e, Group): | ||||||
| 		tag = "begingroup" | 		tag = "begingroup" | ||||||
|  | @ -184,19 +205,19 @@ def tex(e, indent_level: int=0, indent_str: str="\t") -> str: | ||||||
| 		close = "\\endgroup" | 		close = "\\endgroup" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Div): | 	if isinstance(e, Div): | ||||||
| 		return f'{tex(e.content, indent_level+1, indent_str)}' | 		return f'{tex(e.content, i, indent_level+1, indent_str)}' | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Doc): | 	if isinstance(e, Doc): | ||||||
| 		return tex(e.content, indent_level, indent_str)+"\n\\bye" | 		return tex(e.content, i, indent_level, indent_str)+"\n\\bye" | ||||||
| 
 | 
 | ||||||
| 	if isinstance(e, Inline): | 	if isinstance(e, Inline): | ||||||
| 		return f'\\{tag}{arguments}{open}{content_head}{tex(e.content, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}{close}' | 		return f'\\{tag}{arguments}{open}{content_head}{tex(e.content, i, 0, "") if hasattr(e, "_content") else ""}{e.text if hasattr(e, "text") else ""}{content_foot}{close}' | ||||||
| 
 | 
 | ||||||
| 	out_str = "" | 	out_str = "" | ||||||
| 	out_str = f"\\{tag}{arguments}{open}\n" | 	out_str = f"\\{tag}{arguments}{open}\n" | ||||||
| 	out_str += content_head | 	out_str += content_head | ||||||
| 	if hasattr(e, "_content"): | 	if hasattr(e, "_content"): | ||||||
| 		out_str += tex(e.content, indent_level+1, indent_str) | 		out_str += tex(e.content, i, 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{close}\n\n" | 	out_str += f"{content_foot}\n{close}\n\n" | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue