I make Manim to support for the Typst typesetting language but It seem like horizontal line in between numerator and denominator such as (z^2)/(w^2) is not appear. Seem Demo(scene). from manim import * import os import shutil import subprocess # Define a temporary directory for Typst output TYPST_TEMP_DIR = "typst_temp_files" def str_to_valid_filename(s: str) -> str: """Converts a string to a valid filename.""" return "".join((c for c in s if c.isalnum() or c in "._-")).strip() def common_str_to_typst_str(common_str: str, font_size_pt: int = 50, color_hex: str = "#FFFFFF") -> str: """ Generates a Typst string with specified font size and color. The font_size_pt here is the actual font size Typst will render at. """ # Ensure background is transparent and text color/size are set template = rf"""#set page(fill: none) #set text(fill: rgb("{color_hex}"), size: {font_size_pt}pt) // Set page width to auto to prevent excessive padding, allowing Typst to fit content #set page(width: auto, height: auto) {common_str}""" return template def math_str_to_common_str(math_str: str) -> str: """Wraps a math string in Typst's inline math delimiters.""" return f"$ {math_str} $" def typst_str_to_typ(typst_str: str, typst_file_name: str, folder: str = TYPST_TEMP_DIR) -> str: """Writes the Typst content to a .typ file.""" os.makedirs(folder, exist_ok=True) # Use exist_ok=True to avoid error if folder exists file_path = os.path.join(folder, typst_file_name) with open(file_path, "w", encoding="utf-8") as f: f.write(typst_str) return file_path def typ_to_svg(typ_file_path: str) -> str: """Compiles a .typ file to an SVG file using the Typst CLI.""" svg_file_path = typ_file_path.replace(".typ", ".svg") if os.path.exists(svg_file_path): return svg_file_path # Return existing SVG for caching, avoiding re-compilation command = [ 'typst', 'compile', '--format', 'svg', typ_file_path, svg_file_path ] try: # Use subprocess.run for better error handling and simplicity result = subprocess.run(command, check=True, capture_output=True, text=True, encoding="utf-8") if result.stdout: print(f"Typst stdout: {result.stdout}") if result.stderr: print(f"Typst stderr: {result.stderr}") except subprocess.CalledProcessError as e: print(f"Error compiling Typst file: {typ_file_path}") print(f"Typst stderr: {e.stderr}") raise RuntimeError(f"Typst compilation failed: {e.stderr}") from e except FileNotFoundError: raise RuntimeError("Typst command not found. Please ensure Typst is installed and in your system's PATH.") if not os.path.exists(svg_file_path): raise RuntimeError(f"SVG file not generated at expected path: {svg_file_path}") return svg_file_path def common_str_to_svg(common_str: str, font_size_pt: int = 50, color_hex: str = "#FFFFFF", folder: str = TYPST_TEMP_DIR) -> str: """ Converts a common string to an SVG file path by rendering it with Typst. This function will generate temporary .typ and .svg files. """ # Create a unique filename for the Typst file to avoid conflicts unique_filename = f"{str_to_valid_filename(common_str)}_{hash(common_str) % 100000}.typ" typst_str = common_str_to_typst_str(common_str, font_size_pt, color_hex) typ_file_path = typst_str_to_typ(typst_str, unique_filename, folder) svg_file_path = typ_to_svg(typ_file_path) return svg_file_path class Typst(SVGMobject): """ A Manim Mobject for rendering Typst content. Parameters ---------- common_str The string content to be rendered by Typst. font_size The desired visual font size in Manim units (similar to Text/MathTex). color The color of the text, as a ManimColor or hex string. folder The directory to store temporary Typst files. kwargs Additional keyword arguments passed to SVGMobject. """ def __init__(self, common_str: str, font_size: float = 0.7, color: ManimColor | str = WHITE, folder: str = TYPST_TEMP_DIR, **kwargs): TYPST_RENDER_FONT_SIZE_PT = 200 # Render Typst at a large, fixed point size for quality # Ensure color is a hex string for Typst if isinstance(color, ManimColor): color_hex = color.to_hex() # <-- FIX IS HERE: changed .hex to .to_hex() else: color_hex = color self.svg_file_path = common_str_to_svg(common_str, TYPST_RENDER_FONT_SIZE_PT, color_hex, folder) self._common_str = common_str self._font_size = font_size self._color = color self._folder = folder self._kwargs = kwargs if "stroke_width" not in kwargs: kwargs["stroke_width"] = 0 super().__init__(self.svg_file_path, **kwargs) if self.height > 1e-9: self.scale(font_size / self.height) else: self.set_height(font_size) self.set_color(self._color) @property def font_size(self) -> float: return self._font_size @font_size.setter def font_size(self, new_font_size: float): if new_font_size <= 0: raise ValueError("Font size must be greater than 0.") self.set_height(new_font_size) self._font_size = new_font_size def set_content(self, common_str: str, font_size: float = None, color: ManimColor | str = None, folder: str = None): """ Updates the content of the Typst mobject. Removes the old SVG file and generates a new one. """ if os.path.exists(self.svg_file_path): os.remove(self.svg_file_path) current_font_size = font_size if font_size is not None else self._font_size current_color = color if color is not None else self._color current_folder = folder if folder is not None else self._folder if isinstance(current_color, ManimColor): color_hex = current_color.to_hex() # <-- FIX IS HERE: changed .hex to .to_hex() else: color_hex = current_color TYPST_RENDER_FONT_SIZE_PT = 200 new_svg_file_path = common_str_to_svg(common_str, TYPST_RENDER_FONT_SIZE_PT, color_hex, current_folder) temp_kwargs = self._kwargs.copy() new_mob = SVGMobject(new_svg_file_path, **temp_kwargs) if new_mob.height > 1e-9: new_mob.scale(current_font_size / new_mob.height) else: new_mob.set_height(current_font_size) new_mob.set_color(current_color) self.become(new_mob) self.svg_file_path = new_svg_file_path self._common_str = common_str self._font_size = current_font_size self._color = current_color self._folder = current_folder return self class MathTypst(Typst): """ A Manim Mobject for rendering Typst mathematical content. Parameters ---------- math_str The mathematical string content to be rendered by Typst. font_size The desired visual font size in Manim units. color The color of the text, as a ManimColor or hex string. folder The directory to store temporary Typst files. kwargs Additional keyword arguments passed to SVGMobject. """ def __init__(self, math_str: str, font_size: float = 0.7, color: ManimColor | str = WHITE, folder: str = TYPST_TEMP_DIR, **kwargs): super().__init__(math_str_to_common_str(math_str), font_size, color, folder, **kwargs) def set_content(self, math_str: str, font_size: float = None, color: ManimColor | str = None, folder: str = None): """ Updates the content of the MathTypst mobject with new math string. """ super().set_content(math_str_to_common_str(math_str), font_size, color, folder) return self # You might want to add a cleanup function for temporary files def cleanup_typst_temp_files(folder: str = TYPST_TEMP_DIR): """Removes the temporary Typst files folder.""" if os.path.exists(folder): print(f"Cleaning up temporary Typst files in: {folder}") shutil.rmtree(folder) # Clean up # cleanup_typst_temp_files() class Demo(Scene): def construct(self): tex = Typst("Hello Typst!", color=YELLOW) math = Typst(r"$x^2 + y^2 = (z^2)/(w^2)$", color=BLUE) mathlatex = Tex(r"$x^2+y^2 = \frac{z2}{w^2}$") group = VGroup(tex, math, mathlatax).arrange(DOWN, buff=1) self.play(Write(group)) # Clean up cleanup_typst_temp_files() Continue reading...