From ba456a9b8b1e92b94176a48c7eda83cc4925a77a Mon Sep 17 00:00:00 2001 From: Folkert Kevelam Date: Fri, 12 Sep 2025 17:21:54 +0200 Subject: [PATCH] Add tikz pipeline --- Server/MarkdownPreviewer/tikz.py | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 Server/MarkdownPreviewer/tikz.py diff --git a/Server/MarkdownPreviewer/tikz.py b/Server/MarkdownPreviewer/tikz.py new file mode 100644 index 0000000..16a42d2 --- /dev/null +++ b/Server/MarkdownPreviewer/tikz.py @@ -0,0 +1,114 @@ +from .render_pipeline import CallbackClass +from .pandoc import Attr, Image, Plain +import hashlib +import subprocess +import tempfile +import os +import re +from pathlib import Path + +class Tikz(CallbackClass): + def __init__(self, base_path): + self.data_path = base_path + "/data/tikz/" + self.script_path = base_path + "/scripts/tex/tex2svg.sh" + self.image_cache = dict() + + def run(self, output_dir, input_dir, name): + return subprocess.run( + [self.script_path, output_dir, input_dir, name], + text=True, + capture_output=True) + + def __call__(self, content): + hashinput = re.sub(r"\s+", "", content['c'][1], flags=re.UNICODE).encode("utf-8") + hash = hashlib.md5() + hash.update(hashinput) + + digest = hash.hexdigest() + + if digest in self.image_cache: + return self.image_cache[digest] + + preamble = """\\documentclass[class=minimal,crop=true,out=\\jobname.svg]{standalone} + \\usepackage{tikz} + \\begin{document} + \\begin{tikzpicture}[scale=2]""" + + postamble = """\\end{tikzpicture} + \\end{document}""" + + handle, file_path = tempfile.mkstemp(suffix=".tex", text=True) + text = content['c'][1] + + with os.fdopen(handle, 'w') as f: + f.write(preamble) + f.write("\n") + f.write(text) + f.write("\n") + f.write(postamble) + + stem = Path(file_path).stem + + data = self.run("/tmp", "/tmp", stem) + + img_attr = Attr("") + + new_content = Image(img_attr, [{'t' : 'Str', 'c' : 'Tikz'}], "/generated/{}.svg".format(stem)).toJson() + wrapper = Plain(new_content).toJson() + + self.image_cache[digest] = wrapper + + return wrapper + +class Circuitikz(CallbackClass): + def __init__(self, base_path): + self.data_path = base_path + "/data/tikz/" + self.script_path = base_path + "/scripts/tex/tex2svg.sh" + self.image_cache = dict() + + def run(self, output_dir, input_dir, name): + return subprocess.run( + [self.script_path, output_dir, input_dir, name], + text=True, + capture_output=True) + + def __call__(self, content): + hashinput = re.sub(r"\s+", "", content['c'][1], flags=re.UNICODE).encode("utf-8") + hash = hashlib.md5() + hash.update(hashinput) + + digest = hash.hexdigest() + + if digest in self.image_cache: + return self.image_cache[digest] + + preamble = """\\documentclass[class=minimal,crop=true,out=\\jobname.svg]{standalone} + \\usepackage{circuitikz} + \\begin{document} + \\begin{circuitikz}[scale=2, transform shape, line width=1pt]""" + + postamble = """\\end{circuitikz} + \\end{document}""" + + handle, file_path = tempfile.mkstemp(suffix=".tex", text=True) + text = content['c'][1] + + with os.fdopen(handle, 'w') as f: + f.write(preamble) + f.write("\n") + f.write(text) + f.write("\n") + f.write(postamble) + + stem = Path(file_path).stem + + data = self.run("/tmp", "/tmp", stem) + + img_attr = Attr("") + + new_content = Image(img_attr, [{'t' : 'Str', 'c' : 'Circuitikz'}], "/generated/{}.svg".format(stem)).toJson() + wrapper = Plain(new_content).toJson() + + self.image_cache[digest] = wrapper + + return wrapper