From 93511d9a442d85bcc373bc2e1fecfce3ff4c14f5 Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Thu, 4 Dec 2025 22:06:35 +0100 Subject: [PATCH 1/4] Typst helper --- nelsie/python/nelsie/helpers/typst.py | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 nelsie/python/nelsie/helpers/typst.py diff --git a/nelsie/python/nelsie/helpers/typst.py b/nelsie/python/nelsie/helpers/typst.py new file mode 100644 index 00000000..c39b1278 --- /dev/null +++ b/nelsie/python/nelsie/helpers/typst.py @@ -0,0 +1,40 @@ +import subprocess +import hashlib +import os + + +class Typst: + + def __init__(self, typst_path="typst", cache_path="./typst_cache"): + cache_path = os.path.abspath(cache_path) + if not os.path.isdir(cache_path): + os.makedirs(cache_path) + self.typst_path = typst_path + self.cache_path = cache_path + + self.default_header = "#set page(width: auto, height: auto, margin: (x: 0pt, y: 0pt))" + self.version = self._call_typst(["--version"]) + hasher = hashlib.sha1() + hasher.update(self.version) + self.hasher = hasher + + def _call_typst(self, args: list[str]): + r = subprocess.run([self.typst_path, *args], check=True, stdout=subprocess.PIPE) + return r.stdout + + def get_path(self, text: str, use_header: bool = True): + if use_header: + text = f"{self.default_header}\n{text}" + hasher = self.hasher.copy() + hasher.update(text.encode()) + output_path = os.path.join(self.cache_path, f"{hasher.digest().hex()}.svg") + if os.path.isfile(output_path): + return output_path + input_path = os.path.join(self.cache_path, f"{hasher.digest().hex()}.typ") + with open(input_path, "w") as f: + f.write(text) + self._call_typst(["compile", "--format=svg", input_path]) + return output_path + + def render(self, slide, text: str, use_header: bool = True, **kwargs): + slide.image(self.get_path(text, use_header), **kwargs) From cde59f1dcede0aa6e8fb0baab40e9df7d91d4eaf Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Sat, 6 Dec 2025 22:03:45 +0100 Subject: [PATCH 2/4] Custom header --- nelsie/python/nelsie/helpers/typst.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nelsie/python/nelsie/helpers/typst.py b/nelsie/python/nelsie/helpers/typst.py index c39b1278..672cac4b 100644 --- a/nelsie/python/nelsie/helpers/typst.py +++ b/nelsie/python/nelsie/helpers/typst.py @@ -12,7 +12,7 @@ def __init__(self, typst_path="typst", cache_path="./typst_cache"): self.typst_path = typst_path self.cache_path = cache_path - self.default_header = "#set page(width: auto, height: auto, margin: (x: 0pt, y: 0pt))" + self.default_header = self.get_header() self.version = self._call_typst(["--version"]) hasher = hashlib.sha1() hasher.update(self.version) @@ -22,6 +22,9 @@ def _call_typst(self, args: list[str]): r = subprocess.run([self.typst_path, *args], check=True, stdout=subprocess.PIPE) return r.stdout + def get_header(self, width="auto", height="auto", margin_x="0pt", margin_y="0pt"): + return f"#set page(width: {width}, height: {height}, margin: (x: {margin_x}, y: {margin_y}))\n" + def get_path(self, text: str, use_header: bool = True): if use_header: text = f"{self.default_header}\n{text}" From aa0e62e91a1e688ef5967b174e610c423ad8555b Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Fri, 12 Dec 2025 19:44:19 +0100 Subject: [PATCH 3/4] Cleaning typst cache --- nelsie/python/nelsie/helpers/typst.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/nelsie/python/nelsie/helpers/typst.py b/nelsie/python/nelsie/helpers/typst.py index 672cac4b..e261dff8 100644 --- a/nelsie/python/nelsie/helpers/typst.py +++ b/nelsie/python/nelsie/helpers/typst.py @@ -17,6 +17,7 @@ def __init__(self, typst_path="typst", cache_path="./typst_cache"): hasher = hashlib.sha1() hasher.update(self.version) self.hasher = hasher + self.generated = set() def _call_typst(self, args: list[str]): r = subprocess.run([self.typst_path, *args], check=True, stdout=subprocess.PIPE) @@ -30,10 +31,12 @@ def get_path(self, text: str, use_header: bool = True): text = f"{self.default_header}\n{text}" hasher = self.hasher.copy() hasher.update(text.encode()) - output_path = os.path.join(self.cache_path, f"{hasher.digest().hex()}.svg") + hex = hasher.digest().hex() + self.generated.add(hex) + output_path = os.path.join(self.cache_path, f"{hex}.svg") if os.path.isfile(output_path): return output_path - input_path = os.path.join(self.cache_path, f"{hasher.digest().hex()}.typ") + input_path = os.path.join(self.cache_path, f"{hex}.typ") with open(input_path, "w") as f: f.write(text) self._call_typst(["compile", "--format=svg", input_path]) @@ -41,3 +44,11 @@ def get_path(self, text: str, use_header: bool = True): def render(self, slide, text: str, use_header: bool = True, **kwargs): slide.image(self.get_path(text, use_header), **kwargs) + + def clean_cache(self): + for path in os.listdir(self.cache_path): + if not path.endswith(".typ") and not path.endswith(".svg"): + continue + base = path[:-4] + if base not in self.generated: + os.unlink(os.path.join(self.cache_path, path)) From 9a3a02025ba3b171278b56527b3ef58845c9331b Mon Sep 17 00:00:00 2001 From: Ada Bohm Date: Fri, 23 Jan 2026 20:53:07 +0100 Subject: [PATCH 4/4] Disable watching generated typst files --- CHANGELOG.md | 1 + nelsie/python/nelsie/box.py | 7 ++++++- nelsie/python/nelsie/helpers/typst.py | 2 +- nelsie/python/nelsie/image.py | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98c0450..6cf2034f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased * When strip=True in code/text, then text steps line are rstripped +* "typst" extension for writing mathematical expressions # v0.21.2 diff --git a/nelsie/python/nelsie/box.py b/nelsie/python/nelsie/box.py index 872e56bf..07140c30 100644 --- a/nelsie/python/nelsie/box.py +++ b/nelsie/python/nelsie/box.py @@ -6,6 +6,7 @@ ImageContent, check_image_path_or_data, normalize_and_watch_image_path, + normalize_image_path, ) from .steps import ( Sn, @@ -228,12 +229,16 @@ def image( *, enable_steps: Sv[bool] = True, shift_steps: int = 0, + watch=True, **box_args, ): sn_check(path_or_data, check_image_path_or_data) sv_check(enable_steps, check_is_bool) sv_check(shift_steps, check_is_int) - path_or_data = sn_map(path_or_data, normalize_and_watch_image_path) + if watch: + path_or_data = sn_map(path_or_data, normalize_and_watch_image_path) + else: + path_or_data = sn_map(path_or_data, normalize_image_path) box = self.box(**box_args) box._content = ImageContent(path_or_data, enable_steps, shift_steps) return box diff --git a/nelsie/python/nelsie/helpers/typst.py b/nelsie/python/nelsie/helpers/typst.py index e261dff8..1e44962d 100644 --- a/nelsie/python/nelsie/helpers/typst.py +++ b/nelsie/python/nelsie/helpers/typst.py @@ -4,7 +4,6 @@ class Typst: - def __init__(self, typst_path="typst", cache_path="./typst_cache"): cache_path = os.path.abspath(cache_path) if not os.path.isdir(cache_path): @@ -43,6 +42,7 @@ def get_path(self, text: str, use_header: bool = True): return output_path def render(self, slide, text: str, use_header: bool = True, **kwargs): + kwargs.setdefault("watch", False) slide.image(self.get_path(text, use_header), **kwargs) def clean_cache(self): diff --git a/nelsie/python/nelsie/image.py b/nelsie/python/nelsie/image.py index 05ca02a9..8d11ae1b 100644 --- a/nelsie/python/nelsie/image.py +++ b/nelsie/python/nelsie/image.py @@ -48,6 +48,12 @@ def normalize_and_watch_image_path(path): return path +def normalize_image_path(path): + if isinstance(path, str): + path = os.path.abspath(path) + return path + + def _put_into_shared_data( path_or_data: PathOrImageData | None, content, shared_data, steps ):