You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Manim集成Typst排版时分式水平线不显示问题求助

Manim集成Typst排版时分式水平线不显示问题求助

大家好,我正在给Manim开发Typst排版的支持模块,目前基本功能能跑起来,但遇到一个头疼的问题——像(z^2)/(w^2)这样的分式,分子和分母之间的水平线完全不显示,试了好几种方式都没解决。下面是我写的完整代码,麻烦各位帮忙看看哪里出问题了?

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.
    """
    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:
        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.
    """
    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):
    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)
        # 后续更新逻辑待补充

我自己排查了几个方向,但都没找到原因:

  • 检查过Typst的模板设置,页面是透明背景,文字颜色设为白色,应该不会和背景重合
  • 尝试过直接用Typst CLI渲染单独的分式,水平线是正常显示的,但通过代码生成的SVG导入Manim后就没了
  • 也调整过渲染的字号大小,还是看不到水平线

有没有大佬遇到过类似的问题,或者能帮我指出代码里的疏漏?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 07:28:04