展示 HN:Tylax – 一个用 Rust 编写的 LaTeX 到 Typst 双向转换器
Show HN: Tylax – A bidirectional LaTeX to Typst converter in Rust

原始链接: https://github.com/scipenai/tylax

## Tylax:双向 LaTeX & Typst 转换器 Tylax 是一个基于 Rust 的工具,用于在 LaTeX 和 Typst 之间进行转换,支持双向转换。它利用 AST(抽象语法树)解析实现高精度转换,超越了简单的基于正则表达式的方法。 **主要特性:** * **双向转换:** 无缝地将 LaTeX 转换为 Typst,反之亦然。 * **文档支持:** 处理完整文档,包括标题、列表、表格、图形和代码块。 * **数学公式:** 转换包含 700 多个符号映射的复杂数学表达式。 * **表格处理:** 支持 `\multirow` 和 `\multicolumn`,包括嵌套表格。 * **图形:** 提供 TikZ 和 CeTZ 绘图命令之间的基本转换。 * **宏展开:** 支持 LaTeX 的 `\def` 和 `\newcommand`,以及 Typst 的 `#let`。 * **WebAssembly 支持:** 可以编译为 WASM 用于浏览器使用。 * **CLI 工具:** 提供命令行界面用于批量处理。 Tylax 的特点在于具有状态感知的表格处理、多坐标系统 TikZ 解析器以及深度限制的宏展开。虽然功能强大,但复杂的宏和样式可能需要在转换后进行手动调整。 **资源:** [https://github.com/scipenai/tylax](https://github.com/scipenai/tylax)

## Tylax:LaTeX 到 Typst 转换器 一个名为 Tylax 的新型 Rust 工具旨在可靠地将 LaTeX 文档转换为 Typst,解决了基于脆弱的正则表达式转换脚本的局限性。由 democat (scipenai) 开发,Tylax 利用 `mitex-parser` 生成的抽象语法树 (AST) 来处理复杂的 LaTeX 结构——包括完整的文档格式和复杂的数学公式,如矩阵和积分。 值得注意的是,Tylax 包含实验性的 TikZ 图形转换为 CeTZ 的支持。它提供了一个在线演示 ([https://convert.silkyai.cn/](https://convert.silkyai.cn/)) 和 GitHub 上的源代码 ([https://github.com/scipenai/tylax](https://github.com/scipenai/tylax))。 早期的反馈表明,递归宏定义可能存在问题,Tylax 目前会简化而不是正确处理它们。作者指出,Pandoc 对于完整转换而言是不够的,尤其是在图形转换方面。
相关文章

原文

Crates.io Documentation License CI

Bidirectional, AST-based LaTeX ↔ Typst Converter

A tool written in Rust that converts mathematical formulas and full documents between LaTeX and Typst formats.

English | 中文

$ t2l input.tex -o output.typ
✓ Converted 1250 lines

$ cat math.tex
\frac{1}{2} + \int_0^\infty e^{-x} dx

$ t2l math.tex
1/2 + integral_(0)^(infinity) e^(- x) d x

  • Bidirectional Conversion - Supports both LaTeX → Typst and Typst → LaTeX
  • Document Support - Handles headings, lists, tables, figures, and code blocks
  • Math Formulas - Converts mathematical expressions with 700+ symbol mappings
  • Table Support - Handles \multirow, \multicolumn, and nested tables
  • TikZ/CeTZ Graphics - Basic conversion between TikZ and CeTZ drawing commands
  • Macro Expansion - Supports \def, \newcommand in LaTeX and #let in Typst
  • WebAssembly - Can be compiled to WASM for browser usage
  • CLI Tool - Command-line interface for batch processing
Feature Tylax Pandoc Regex Scripts
Architecture AST (Rust) AST (Haskell) Regex
Speed Native Binary Haskell Runtime Varies
Bidirectional ✅ Yes ✅ Yes ❌ Usually One-way
WASM Support ✅ Yes ❌ Large Binary ✅ Yes
TikZ Support ✅ Partial (to CeTZ) ❌ No ❌ No
Math Precision ✅ AST Preserved ✅ AST Preserved ⚠️ String Replacement
Category Features
Math Environments equation, align, gather, multline, matrix, pmatrix, bmatrix, cases, etc.
Document Structure \section, \subsection, \title, \author, \tableofcontents, \appendix
Lists itemize, enumerate, description (with 3+ level nesting)
Tables tabular, longtable, tabularx, \multicolumn, \multirow, booktabs
References \cite, \citep, \citet, \label, \ref, \eqref, \autoref
Figures \includegraphics, figure environment, \caption
Code verbatim, lstlisting, minted
Graphics TikZ (lines, circles, rectangles, nodes, arrows, bezier curves, arcs, grids, \foreach, fill patterns)
Theorems theorem, lemma, corollary, proof (with QED symbol), definition, remark
Extensions siunitx (\SI, \si), color commands, \footnote

Supported TikZ/CeTZ Features

TikZ Feature CeTZ Equivalent Status
\draw (a) -- (b) line((a), (b))
\draw (a) circle (r) circle((a), radius: r)
\draw (a) rectangle (b) rect((a), (b))
\draw (a) arc (s:e:r) arc((a), start: s, stop: e, radius: r)
\node at (a) {text} content((a), [text])
\draw[->] arrows mark: (end: ">")
\draw[dashed] dash: "dashed"
\draw[thick] stroke: (thickness: 0.8pt)
\draw[blue] colors stroke: blue
\fill[red!20] fill: red.lighten(80%)
.. controls (c) .. bezier bezier(...)
\foreach \x in {...} for x in (...) {...} ⚠️ Partial
node[right] anchors anchor: "west"
\clip regions Comment only ⚠️ Partial

While Tylax uses advanced AST-based parsing, please note:

  • Complex Macros: Highly dynamic TeX macros or complex packages (like tcolorbox) may require manual adjustment.
  • Styling: Typst's styling system differs significantly from LaTeX; some visual adjustments are expected after conversion.
  • Compilation: This tool converts syntax, it does not execute the full TeX engine (e.g., \newcounter, complex \if).

This project uses AST-based parsing for high-precision bidirectional conversion:

  • LaTeX parsing: MiTeX - High-performance LaTeX parser with Rowan AST
  • Typst parsing: typst-syntax - Official Typst syntax parser
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#4a90d9', 'primaryTextColor': '#fff', 'primaryBorderColor': '#2d6cb5', 'lineColor': '#5c6bc0', 'secondaryColor': '#81c784', 'tertiaryColor': '#fff3e0'}}}%%

flowchart LR
    subgraph INPUT ["📄 Input"]
        direction TB
        LaTeX["LaTeX\n.tex"]
        Typst["Typst\n.typ"]
    end

    subgraph CORE ["⚙️ Core Engine"]
        direction TB
        
        subgraph L2T ["LaTeX → Typst"]
            direction LR
            MP[["🔍 MiTeX\nParser"]]
            LA[("AST")]
            LC{{"Converter"}}
            MP --> LA --> LC
        end
        
        subgraph T2L ["Typst → LaTeX"]
            direction LR
            TP[["🔍 typst-syntax\nParser"]]
            TA[("AST")]
            TC{{"Converter"}}
            TP --> TA --> TC
        end
        
        subgraph FEATURES ["📦 Features"]
            direction TB
            F1["Tables\n(Coverage Tracking)"]
            F2["TikZ/CeTZ\n(Coord Parser)"]
            F3["Macros\n(Recursive Expander)"]
            F4["References"]
        end
    end

    subgraph OUTPUT ["📄 Output"]
        direction TB
        TypstOut["Typst\n.typ"]
        LaTeXOut["LaTeX\n.tex"]
    end

    LaTeX --> MP
    LC --> TypstOut
    
    Typst --> TP
    TC --> LaTeXOut
    
    LC -.- FEATURES
    TC -.- FEATURES

    style INPUT fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
    style CORE fill:#fff8e1,stroke:#ff8f00,stroke-width:2px
    style OUTPUT fill:#e8f5e9,stroke:#388e3c,stroke-width:2px
    style L2T fill:#e1f5fe,stroke:#0288d1
    style T2L fill:#fce4ec,stroke:#c2185b
    style FEATURES fill:#f3e5f5,stroke:#7b1fa2
    
    style MP fill:#bbdefb,stroke:#1976d2
    style TP fill:#f8bbd0,stroke:#c2185b
    style LA fill:#fff9c4,stroke:#fbc02d
    style TA fill:#fff9c4,stroke:#fbc02d
    style LC fill:#c8e6c9,stroke:#388e3c
    style TC fill:#c8e6c9,stroke:#388e3c
Loading

Key technical details distinguishing this project from regex-based converters

📊 State-aware Table Handling — coverage tracking for multirow/multicolumn
  • Core idea: maintain a col_coverage[] array while streaming cells; skip “phantom” placeholders created by row/col spans.
  • Why it matters: \multirow/\multicolumn spans won’t shift later cells (supports nested spans).
  • Where in code: src/core/latex2typst/table/parser.rs, src/features/tables.rs, plus table markers in src/core/latex2typst/markup.rs.
🎨 TikZ → CeTZ — multi-coordinate-system parser
  • Core idea: parse multiple TikZ coordinate systems (absolute / relative / polar / named / calc) and normalize for CeTZ output.
  • Why it matters: real TikZ documents mix coordinate styles; a 1:1 regex mapping breaks quickly.
  • Where in code: src/features/tikz.rs (Coordinate parsing + conversion).
🔄 Depth-limited Macro Expansion — recursive expander with limit
  • Core idea: expand \newcommand / \def / \DeclareMathOperator before parsing, with a recursion depth limit.
  • Why it matters: prevents infinite loops for self-referential macros, while still supporting common macro-based writing styles.
  • Where in code: src/features/macros.rs (e.g., MAX_EXPANSION_DEPTH).
🧠 AST-Driven Conversion — context-aware conversion (math vs text)
  • Core idea: parse to AST first, then convert with context/state (math mode, environments, labels, etc.).
  • Why it matters: handles deeply nested structures (e.g., \frac{\frac{a}{b}}{c}) and avoids regex corner cases.
  • Where in code: src/core/latex2typst/*, src/core/typst2latex/*.
Show module layout
src/
├── core/
│   ├── latex2typst/     # LaTeX → Typst conversion
│   │   ├── context.rs   # Conversion state & options
│   │   ├── environment.rs # Environment handling
│   │   ├── math.rs      # Math formula conversion
│   │   ├── markup.rs    # Document structure (2000+ lines)
│   │   └── table/       # Smart table parser with coverage tracking
│   └── typst2latex/     # Typst → LaTeX conversion
│       ├── context.rs   # Conversion state
│       ├── math.rs      # Math conversion
│       ├── markup.rs    # Markup conversion
│       └── table/       # Table handling with hline support
├── features/
│   ├── tikz.rs          # TikZ ↔ CeTZ with 5 coordinate systems
│   ├── tables.rs        # Complex table support
│   ├── refs.rs          # Citations & references
│   ├── macros.rs        # Depth-limited macro expansion
│   └── bibtex.rs        # Bibliography parsing
├── data/
│   ├── maps.rs          # 700+ symbol mappings
│   ├── symbols.rs       # Greek, operators, arrows
│   └── siunitx.rs       # SI units support
└── utils/
    ├── diagnostics.rs   # Error reporting
    └── files.rs         # Multi-file support

git clone https://github.com/scipenai/tylax.git
cd tylax
cargo build --release

Add to your Cargo.toml:

[dependencies]
tylax = "0.1.0"

# Math formula conversion
t2l input.tex -d l2t              # LaTeX → Typst
t2l input.typ -d t2l              # Typst → LaTeX

# Full document conversion
t2l input.tex -d l2t -f           # LaTeX document → Typst
t2l input.typ -d t2l -f           # Typst document → LaTeX

# Auto-detect format
t2l input.tex                     # Detect by extension
t2l input.tex -o output.typ       # Specify output file

# From stdin
echo '\frac{1}{2}' | t2l -d l2t

# TikZ/CeTZ conversion
t2l tikz input.tex -o output.typ  # TikZ → CeTZ
t2l tikz input.typ -o output.tex  # CeTZ → TikZ (auto-detect)

# Batch conversion
t2l batch ./latex_files -o ./typst_output -d l2t -f

# Check LaTeX for issues
t2l check input.tex

# Show version and features
t2l info
use tylax::{latex_to_typst, typst_to_latex};

// LaTeX → Typst
let typst = latex_to_typst(r"\frac{1}{2} + \alpha^2");
// Output: "1/2 + alpha^(2)"

// Typst → LaTeX
let latex = typst_to_latex("frac(1, 2) + alpha^2");
// Output: "\frac{1}{2} + \alpha^{2}"
use tylax::{latex_document_to_typst, typst_document_to_latex};

let latex_doc = r#"
\documentclass{article}
\title{My Paper}
\begin{document}
\section{Introduction}
Hello, world!
\end{document}
"#;

let typst = latex_document_to_typst(latex_doc);

TikZ/CeTZ Graphics Conversion

use tylax::tikz::{convert_tikz_to_cetz, convert_cetz_to_tikz};

// TikZ → CeTZ
let tikz = r"\begin{tikzpicture}\draw (0,0) -- (1,1);\end{tikzpicture}";
let cetz = convert_tikz_to_cetz(tikz);

// CeTZ → TikZ
let cetz = "line((0, 0), (1, 1))";
let tikz = convert_cetz_to_tikz(cetz);

Math Formulas (LaTeX → Typst)

LaTeX Typst
\frac{1}{2} 1/2
\sqrt{x^2 + y^2} sqrt(x^(2) + y^(2))
\sum_{i=1}^{n} i^2 sum_(i = 1)^(n) i^(2)
\int_0^\infty e^{-x} dx integral_(0)^(infinity) e^(- x) d x
\lim_{n \to \infty} lim_(n -> infinity)
\alpha + \beta = \gamma alpha + beta = gamma
\begin{pmatrix} a & b \\ c & d \end{pmatrix} mat(delim: "(", a, b ; c, d)
\mathbb{R} RR
\vec{v} arrow(v)
\nabla \times \vec{E} nabla times arrow(E)
\underbrace{x+y}_{text} underbrace(x + y)_(t e x t)
\begin{cases} x \\ y \end{cases} cases(x, y)
\oint_{\partial S} integral.cont_(partial S)

Document Structure (Typst → LaTeX)

Typst LaTeX
= Title \section{ Title}
== Subtitle \subsection{ Subtitle}
*bold* \textbf{bold}
_italic_ \textit{italic}
`code` \texttt{code}
@label \ref{label}
$x + y$ $x + y$
$ x + y $ \[ x + y \]
TikZ CeTZ
\draw (0,0) -- (1,1); line((0, 0), (1, 1))
\draw (0,0) circle (1); circle((0, 0), radius: 1)
\node at (0,0) {text}; content((0, 0), [text])
\draw[->] (0,0) -- (1,0); line((0, 0), (1, 0), mark: (end: ">"))
\draw[<->] (0,0) -- (1,0); line((0, 0), (1, 0), mark: (start: ">", end: ">"))
\draw (0,0) rectangle (1,1); rect((0, 0), (1, 1))
\draw[blue, thick] ... stroke: (paint: blue, thickness: 0.8pt)
\draw[dashed] ... stroke: (dash: "dashed")
\draw[fill=green!20] ... fill: green.lighten(80%)
\node[right] at (0,0) {x}; content((0, 0), [x], anchor: "west")
\draw .. controls (a) and (b) .. bezier(..., (a), (b), ...)
\draw (0,0) arc (0:90:1); arc((0, 0), start: 0deg, stop: 90deg, radius: 1)

Input (TikZ):

\begin{tikzpicture}
  \draw[thick, ->] (0,0) -- (4,0) node[right] {$x$};
  \draw[thick, ->] (0,0) -- (0,3) node[above] {$y$};
  \draw[blue, thick] (0,0) -- (3,2);
  \draw[red, dashed] (0,2) -- (3,2) -- (3,0);
  \draw[green, fill=green!20] (2,1.5) circle (0.5);
  \node at (3,2) [above right] {$(3,2)$};
\end{tikzpicture}

Output (CeTZ):

#import "@preview/cetz:0.2.2": canvas, draw

#canvas({
  import draw: *

  content((4, 0), [\$x\$], anchor: "west")
  line((0, 0), (4, 0), stroke: (thickness: 0.8pt), mark: (end: ">"))
  content((0, 3), [\$y\$], anchor: "south")
  line((0, 0), (0, 3), stroke: (thickness: 0.8pt), mark: (end: ">"))
  line((0, 0), (3, 2), stroke: (paint: blue, thickness: 0.8pt))
  line((0, 2), (3, 2), (3, 0), stroke: (paint: red, dash: "dashed"))
  circle((2, 1.5), radius: 0.5, stroke: green, fill: green.lighten(80%))
  content((3, 2), [\$(3,2)\$], anchor: "south-west")
})

# Install wasm-pack
cargo install wasm-pack

# Build
wasm-pack build --target web --out-dir web/src/pkg --features wasm --no-default-features
import init, { 
  latexToTypst, 
  typstToLatex, 
  latexDocumentToTypst,
  typstDocumentToLatex,
  tikzToCetz, 
  cetzToTikz,
  checkLatex,
  detectFormat,
  getVersion 
} from './pkg/tylax.js';

await init();

// Math conversion
const typst = latexToTypst("\\frac{1}{2}");
console.log(typst); // "1/2"

// Document conversion
const typstDoc = latexDocumentToTypst("\\documentclass{article}...");

// Graphics conversion
const cetz = tikzToCetz("\\begin{tikzpicture}\\draw (0,0) -- (1,1);\\end{tikzpicture}");

// Format detection
const format = detectFormat("$x + y$"); // "typst" or "latex"

// Check LaTeX for issues
const issues = checkLatex("\\frac{1}");
if (issues.has_errors) {
  console.error("Errors:", issues.errors);
}

// Version info
console.log("Version:", getVersion());

tylax/
├── src/
│   ├── lib.rs              # Library entry point
│   ├── core/
│   │   ├── latex2typst/    # LaTeX → Typst conversion (AST-based)
│   │   └── typst2latex/    # Typst → LaTeX conversion (modular)
│   ├── data/
│   │   ├── maps.rs         # Symbol mappings (700+ entries)
│   │   ├── constants.rs    # Static constants & theorem types
│   │   └── symbols.rs      # Symbol helpers & delimiters
│   ├── features/
│   │   ├── tikz.rs         # TikZ ↔ CeTZ bidirectional conversion
│   │   ├── bibtex.rs       # Bibliography handling
│   │   └── templates.rs    # Document templates
│   ├── utils/
│   │   ├── diagnostics.rs  # LaTeX checking & warnings
│   │   └── error.rs        # Structured error types
│   ├── wasm.rs             # WebAssembly bindings
│   └── bin/
│       └── t2l.rs          # CLI application
├── web/                    # Web frontend
├── tests/                  # Integration tests
└── tools/                  # Build & code generation tools

# Run all tests
cargo test --release

# Run specific module tests
cargo test latex2typst      # LaTeX → Typst tests
cargo test typst2latex      # Typst → LaTeX tests
cargo test tikz             # TikZ/CeTZ tests
cargo test integration      # Integration tests

# With output
cargo test -- --nocapture
Category Test Cases Status
Math formulas (L→T) Basic, complex, matrices, cases
Math formulas (T→L) Basic, complex, matrices, cases
Documents (L→T) Sections, lists, tables, theorems, bibliography
Documents (T→L) Headings, formatting, math
TikZ → CeTZ Lines, shapes, arrows, bezier, nodes, fills
CeTZ → TikZ Lines, shapes, content, basic styles
Integration Full document conversion, auto-detection, roundtrip

Join the conversation!

Contributions are welcome! Please feel free to submit issues and pull requests.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request
  • Follow Rust coding conventions
  • Add tests for new features
  • Update documentation as needed
  • Run cargo fmt and cargo clippy before committing

This project is licensed under the Apache-2.0 License - see the LICENSE file for details.


This project builds upon the following excellent projects:

  • MiTeX - High-performance LaTeX parser
  • tex2typst - Symbol mapping reference
  • typst-syntax - Official Typst syntax parser
  • Pandoc - Document structure conversion reference
联系我们 contact @ memedata.com