From 7bfdc63512bf76072b10d0a60c6461f15fb0bb33 Mon Sep 17 00:00:00 2001 From: Folkert Kevelam Date: Sun, 31 Aug 2025 21:23:34 +0200 Subject: [PATCH] Initial commit --- Server/MarkdownPreviewer/admonition.py | 153 +++++++++++++++++++++++++ Server/MarkdownPreviewer/image.py | 31 +++++ Server/MarkdownPreviewer/penrose.py | 54 +++++++++ 3 files changed, 238 insertions(+) create mode 100644 Server/MarkdownPreviewer/admonition.py create mode 100644 Server/MarkdownPreviewer/image.py create mode 100644 Server/MarkdownPreviewer/penrose.py diff --git a/Server/MarkdownPreviewer/admonition.py b/Server/MarkdownPreviewer/admonition.py new file mode 100644 index 0000000..9ca0064 --- /dev/null +++ b/Server/MarkdownPreviewer/admonition.py @@ -0,0 +1,153 @@ +import re + +from .render_pipeline import CallbackClass +from .pandoc import Div, Span, Header, Attr, Plain, PString, Inline, Image + +blockquote_re = re.compile('!\\[([a-z]+)\\]') + +panel_content = Attr("", classes=["panel-content"]) +header_attr = Attr("", classes=["panel-title"]) +warning_icon = Attr("", classes=["fas", "fa-exclamation-triangle", "panel-icon"]) +information_icon = Attr("", classes=["fas", "fa-info-circle", "panel-icon"]) +header_title_attr = Attr("", classes=["panel-header"]) +outer_attr = Attr("", classes=["panel", "panel-warning"]) +outer_info_attr = Attr("", classes=["panel", "panel-info"]) + +def blockquote_filter(block): + + data = block['c'] + + if data[0]['t'] != 'Para': + return None + + paragraph_content = data[0]['c'] + if paragraph_content[0]['t'] != 'Str': + return None + + string_content = paragraph_content[0]['c'] + + m = blockquote_re.match(string_content) + + if m is None: + return None + + return m.group(1) + +def create_warning(content): + + internal_content = content['c'] + + internal_content[0]['c'].pop(0) + internal_content[0]['c'].pop(0) + + content_div = Div(panel_content, internal_content).toJson() + label = PString("Warning").toJson() + header = Header(header_attr, 3, [label]).toJson() + icon = Plain(Span(warning_icon, []).toJson()).toJson() + + header_div = Div(header_title_attr, + [ + icon, + header + ]).toJson() + + outer_div = Div(outer_attr, + [ + header_div, + content_div + ]).toJson() + + return outer_div + +def create_information(content): + + internal_content = content['c'] + + internal_content[0]['c'].pop(0) + internal_content[0]['c'].pop(0) + + content_div = Div(panel_content, internal_content).toJson() + label = PString("Information").toJson() + header = Header(header_attr, 3, [label]).toJson() + icon = Plain(Span(information_icon, []).toJson()).toJson() + + header_div = Div(header_title_attr, + [ + icon, + header + ]).toJson() + + outer_div = Div(outer_info_attr, + [ + header_div, + content_div + ]).toJson() + + return outer_div + +class Theorem(CallbackClass): + def __init__(self): + self.counter = 1 + + def __call__(self, content): + + internal_content = content['c'] + + internal_content[0]['c'].pop(0) + internal_content[0]['c'].pop(0) + + outer_div_attr = Attr("", classes=["theorem"]) + inner_div_attr = Attr("", classes=["theorem-content"]) + bold_attr = Attr("", classes=["theorem-title"]) + + span_attr = Attr("") + + theorem_string = "Theorem {}. ".format(self.counter) + + title_content = Inline("Emph", [PString(theorem_string).toJson()]).toJson() + title = Span(span_attr, [title_content]).toJson() + + internal_content[0]['c'].insert(0, title) + + content_div = Div(inner_div_attr, internal_content).toJson() + outer_div = Div(outer_div_attr, [content_div]).toJson() + + self.counter += 1 + + return outer_div + + def clear(self): + self.counter = 1 + +class Proof(CallbackClass): + def __init__(self): + self.counter = 1 + + def clear(self): + self.counter = 1 + + def __call__(self, content): + internal_content = content['c'] + + internal_content[0]['c'].pop(0) + internal_content[0]['c'].pop(0) + + outer_div_attr = Attr("", classes=["proof"]) + inner_div_attr = Attr("", classes=["proof-content"]) + span_attr = Attr("", classes=["proof-title"]) + qed_attr = Attr("", classes=["proof-qed"]) + + proof_string = "Proof {}. ".format(self.counter) + + title_content = Span(span_attr, [PString(proof_string).toJson()]).toJson() + title = Inline("Emph", [title_content]).toJson() + + internal_content[0]['c'].insert(0, title) + qed_string = Plain(PString("◻").toJson()).toJson() + qed = Div(qed_attr, [qed_string]).toJson() + + inner_div = Div(inner_div_attr, internal_content).toJson() + + outer_div = Div(outer_div_attr, [inner_div, qed]).toJson() + + return outer_div diff --git a/Server/MarkdownPreviewer/image.py b/Server/MarkdownPreviewer/image.py new file mode 100644 index 0000000..3252835 --- /dev/null +++ b/Server/MarkdownPreviewer/image.py @@ -0,0 +1,31 @@ +from pathlib import Path + +class ImageFilter: + def __init__(self, base_path=None): + self.base_path = "" + if base_path is not None: + self.base_path = Path(base_path) + if not self.base_path.is_dir(): + self.base_path = self.base_path.parent + + def set_base_path(self, base_path): + if isinstance(base_path, str): + self.base_path = Path(base_path) + elif isinstance(base_path, Path): + self.base_path = base_path + + if not self.base_path.is_dir(): + self.base_path = self.base_path.parent + + def __call__(self, content): + path = Path(content['c'][2][0]) + if path.is_absolute(): + return content + + new_path = (self.base_path / path).resolve() + + print(new_path) + + content['c'][2][0] = str(new_path) + + return content diff --git a/Server/MarkdownPreviewer/penrose.py b/Server/MarkdownPreviewer/penrose.py new file mode 100644 index 0000000..c40929e --- /dev/null +++ b/Server/MarkdownPreviewer/penrose.py @@ -0,0 +1,54 @@ +from .render_pipeline import CallbackClass +from .pandoc import Attr, Image, Plain +import hashlib +import subprocess +import tempfile +import os +import re + +class Penrose(CallbackClass): + def __init__(self, base_path): + self.data_path = base_path + "/data/penrose/" + self.image_cache = dict() + + def run(self, input_file_name, domain, style): + + domain_path = self.data_path + domain + ".domain" + style_path = self.data_path + domain + ".style" + + return subprocess.run( + ["roger", "trio", input_file_name, domain_path, style_path, '--path', "/"], + 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] + + handle, file_path = tempfile.mkstemp(suffix=".substance", text=True) + text = content['c'][1] + + with os.fdopen(handle, 'w') as f: + f.write(text) + + data = self.run(file_path, "set", "set") + + handle, file_path = tempfile.mkstemp(suffix=".svg", text=True) + + with os.fdopen(handle, 'w') as f: + f.write(data.stdout) + + img_attr = Attr("") + + new_content = Image(img_attr, [{'t' : 'Str', 'c' : 'Penrose'}], "/generated/{}".format(file_path[5:])).toJson() + wrapper = Plain(new_content).toJson() + + self.image_cache[digest] = wrapper + + return wrapper