Repository: lzanini/mdbook-katex
Branch: master
Commit: fc5bab43e77f
Files: 22
Total size: 74.8 KB
Directory structure:
gitextract_9swmyqjt/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── deploy.yml
│ ├── release-plz.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
├── cfg.rs
├── escape.rs
├── lib.rs
├── main.rs
├── preprocess.rs
├── render/
│ ├── cfg.rs
│ └── preprocess.rs
├── render.rs
├── scan.rs
└── tests/
├── escape.rs
├── mod.rs
├── render/
│ └── not_duktape.rs
└── render.rs
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/dependabot.yml
================================================
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
================================================
FILE: .github/workflows/deploy.yml
================================================
name: deploy
on:
push:
tags:
- "v*.*.*"
release:
types: [published]
workflow_dispatch:
# Manual trigger.
jobs:
msvc-windows-binary:
runs-on: windows-latest
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
steps:
- uses: actions/checkout@v3
- name: Install stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-pc-windows-msvc
override: true
- uses: Swatinem/rust-cache@v2
- name: Build mdbook-katex
run: |
cargo build --no-default-features --features duktape --release
- name: Get the version
shell: bash
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create zip
run: |
$ZIP_PREFIX = "mdbook-katex-v${{ steps.tagName.outputs.version }}"
7z a "$ZIP_PREFIX-x86_64-pc-windows-msvc.zip" `
"./target/release/mdbook-katex.exe"
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: x86_64-pc-windows
path: mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-pc-windows-msvc.zip
gnu-windows-binary:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install x86_64-pc-windows-gnu
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-pc-windows-gnu
override: true
- uses: Swatinem/rust-cache@v2
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: |
--release
--target x86_64-pc-windows-gnu
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create tar
run: |
mv target/x86_64-pc-windows-gnu/release/mdbook-katex.exe mdbook-katex.exe
TAR_FILE=mdbook-katex-v${{ steps.tagName.outputs.version }}
zip $TAR_FILE-x86_64-pc-windows-gnu.zip \
mdbook-katex.exe
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: x86_64-pc-windows-gnu
path: |
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-pc-windows-gnu.zip
gnu-linux-binary:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install x86_64-unknown-linux-gnu target
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-unknown-linux-gnu
override: true
- uses: Swatinem/rust-cache@v2
- name: Build mbdook-katex
run: |
cargo build --release
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create tar
run: |
mv target/release/mdbook-katex mdbook-katex
TAR_FILE=mdbook-katex-v${{ steps.tagName.outputs.version }}
tar -czvf $TAR_FILE-x86_64-unknown-linux-gnu.tar.gz \
mdbook-katex
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: x86_64-unknown-linux-gnu
path: |
Cargo.lock
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz
musl-linux-binary:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Install x86_64-unknown-linux-musl
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-unknown-linux-musl
override: true
- uses: Swatinem/rust-cache@v2
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: build
args: |
--release
--target x86_64-unknown-linux-musl
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create tar
run: |
mv target/x86_64-unknown-linux-musl/release/mdbook-katex mdbook-katex
TAR_FILE=mdbook-katex-v${{ steps.tagName.outputs.version }}
tar -czvf $TAR_FILE-x86_64-unknown-linux-musl.tar.gz \
mdbook-katex
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: x86_64-unknown-linux-musl
path: |
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-unknown-linux-musl.tar.gz
x86_64-macos-binary:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install stable-x86_64-apple-darwin
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-apple-darwin
override: true
- uses: Swatinem/rust-cache@v2
- name: Build mdbook-katex for x86_64-apple-darwin
run: |
cargo build --release
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create tar for x86_64-apple-darwin
run: |
mv target/release/mdbook-katex mdbook-katex
TAR_PREFIX=mdbook-katex-v${{ steps.tagName.outputs.version }}
tar -czvf $TAR_PREFIX-x86_64-apple-darwin.tar.gz \
mdbook-katex
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: x86_64-apple-darwin
path: |
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-apple-darwin.tar.gz
aarch64-macos-binary:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install stable-aarch64-apple-darwin
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: aarch64-apple-darwin
override: true
- uses: Swatinem/rust-cache@v2
- name: Cross build mdbook-katex for aarch64-apple-darwin
run: |
cargo build --release --target=aarch64-apple-darwin
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create tar for aarch64-apple-darwin
run: |
mv target/aarch64-apple-darwin/release/mdbook-katex mdbook-katex
TAR_PREFIX=mdbook-katex-v${{ steps.tagName.outputs.version }}
tar -czvf $TAR_PREFIX-aarch64-apple-darwin.tar.gz \
mdbook-katex
- name: Upload binary artifact
uses: actions/upload-artifact@v4
with:
name: aarch64-apple-darwin
path: |
mdbook-katex-v${{ steps.tagName.outputs.version }}-aarch64-apple-darwin.tar.gz
deploy:
needs:
[
msvc-windows-binary,
gnu-windows-binary,
gnu-linux-binary,
musl-linux-binary,
x86_64-macos-binary,
aarch64-macos-binary,
]
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- name: Download artifacts
uses: actions/download-artifact@v4
with:
pattern: "*"
merge-multiple: true
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- name: Get the version
id: tagName
run: |
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
echo "::set-output name=version::$VERSION"
- name: Create a release
uses: softprops/action-gh-release@v1
with:
name: v${{ steps.tagName.outputs.version }}-binaries
files: |
Cargo.lock
mdbook-katex-v${{ steps.tagName.outputs.version }}-aarch64-apple-darwin.tar.gz
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-apple-darwin.tar.gz
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-pc-windows-msvc.zip
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-pc-windows-gnu.zip
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-unknown-linux-gnu.tar.gz
mdbook-katex-v${{ steps.tagName.outputs.version }}-x86_64-unknown-linux-musl.tar.gz
tag_name: ${{ steps.tagName.outputs.version }}-binaries
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .github/workflows/release-plz.yml
================================================
name: Release-plz
permissions:
pull-requests: write
contents: write
on:
push:
branches:
- master
paths:
- '.github/workflows/release-plz.yml'
- '**.rs'
- '**Cargo.toml'
- '**CHANGELOG.md'
jobs:
release-plz:
name: Release-plz
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Run release-plz
uses: MarcoIeni/release-plz-action@v0.5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_CREDENTIALS }}
with:
project_manifest: Cargo.toml
================================================
FILE: .github/workflows/test.yml
================================================
name: test-ci
on:
push:
pull_request:
jobs:
test-musl:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-unknown-linux-musl
override: true
- uses: Swatinem/rust-cache@v2
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: |
--target x86_64-unknown-linux-musl
test-ubuntu:
runs-on: ubuntu-22.04
strategy:
matrix:
rust:
- stable
- beta
- nightly
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.rust }}
override: true
- uses: Swatinem/rust-cache@v2
- run: cargo test
test-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- run: cargo test
test-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
target: x86_64-pc-windows-msvc
override: true
- uses: Swatinem/rust-cache@v2
- run: cargo test --no-default-features --features duktape
test-windows-from-ubuntu:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: x86_64-pc-windows-gnu
override: true
- uses: Swatinem/rust-cache@v2
- uses: actions-rs/cargo@v1
with:
use-cross: true
command: test
args: |
--target x86_64-pc-windows-gnu
--no-run
# Cannot run because not on Windows.
fmt-clippy:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: clippy, rustfmt
- uses: Swatinem/rust-cache@v2
- name: check formatting
run: cargo fmt -- --check
- name: clippy
run: RUSTFLAGS="-Dwarnings" cargo clippy
- name: check Cargo.lock
run: cargo update --locked
================================================
FILE: .gitignore
================================================
target/
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [0.10.0](https://github.com/lzanini/mdbook-katex/compare/v0.9.4...v0.10.0) - 2025-11-28
### Other
- update dependencies: toml to v0.9, rayon to 1.11
- use mdbook-preprocessor v0.5.1 instead of mdbook_fork4ls (a fork of mdbook v0.4, [#131](https://github.com/lzanini/mdbook-katex/pull/131))
- allow use of different js renderers of katex through features `quick-js` and `duktape`
- changed default features to `quick-js`
## [0.9.4](https://github.com/lzanini/mdbook-katex/compare/v0.9.3...v0.9.4) - 2025-05-01
### Other
- make clippy happy;update dependencies
- use mdbook_fork4ls v0.4.48
## [0.9.3](https://github.com/lzanini/mdbook-katex/compare/v0.9.2...v0.9.3) - 2025-02-18
### Other
- use mdbook_fork4ls v0.4.45 & its builtin parallelization
## [0.9.2](https://github.com/lzanini/mdbook-katex/compare/v0.9.1...v0.9.2) - 2024-12-07
### Other
- make new clippy happy
- update dependency mdbook to v0.4.43;update other dependencies
- attempt to fix upload-artifact not allowing reused name
- bump download-artifact as well
- bump upload-artifact to v4 so deployment runs
## [0.9.1](https://github.com/lzanini/mdbook-katex/compare/v0.9.0...v0.9.1) - 2024-11-07
### Fixed
- fix deploy CI artifact names
### Other
- make clippy happy
- switch to mdbook_fork4ls v0.4.41; update dependencies
- deploy CI explicit release tag
- different tag for deploy than release
- deploy CI does not publish on crates.io
- allow manually trigger deploy CI
## [0.9.0](https://github.com/lzanini/mdbook-katex/compare/v0.8.1...v0.9.0) - 2024-05-23
### Fixed
- fix&enhance tracing subscriber output
### Other
- update dependencies
- use tracing
- print render error&restore delimiter
- build release binary on release-plz
================================================
FILE: Cargo.toml
================================================
[package]
name = "mdbook-katex"
version = "0.10.0-alpha"
authors = [
"Lucas Zanini <zanini.lcs@gmail.com>",
"Steven Hé (Sīchàng) <stevensichanghe@gmail.com>",
]
edition = "2021"
description = "mdBook preprocessor rendering LaTeX equations to HTML."
license = "MIT"
readme = "README.md"
repository = "https://github.com/lzanini/mdbook-katex"
[dependencies]
clap = { version = "4.5.53", features = ["cargo"] }
mdbook-preprocessor = {version = "0.5.1"}
serde = "1.0.228"
serde_derive = "1.0"
serde_json = "1.0.145"
toml = "0.9.8"
tracing = { version = "0.1.41", default-features = false, features = [
"attributes",
] }
tracing-subscriber = { version = "0.3.20", default-features = false, features = [
"ansi",
"env-filter",
"fmt",
] }
rayon = "1.11"
katex = { version = "0.4.6", default-features = false, optional = true }
[features]
default = ["quick-js"]
pre-render = ["dep:katex"]
quick-js = ["pre-render", "katex/quick-js"]
duktape = ["pre-render", "katex/duktape"]
[profile.release]
opt-level = "z"
lto = true
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2022 Lucas Zanini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# mdBook-KaTeX
[](https://crates.io/crates/mdbook-katex)

mdBook-KaTeX is a preprocessor for [mdBook](https://github.com/rust-lang/mdBook), using KaTeX to render LaTeX math expressions.
There are two working modes:
- [Pre-render Mode](#pre-render-mode-default) (default): pre-renders math expressions at build time using KaTeX,
- no client-side JavaScript required,
- very fast page load,
- customizable macros and separators.
- [Escape mode](#escape-mode-experimental) (experimental): escapes math expressions to be rendered using either katex.js or MathJax in the browser. May be useful if having problems building mdBook-KaTeX with quickjs.
Pre-rendering uses [the katex crate](https://github.com/xu-cheng/katex-rs).
[List of LaTeX functions supported by KaTeX](https://katex.org/docs/supported.html).
<p align="center">
<img width="75%" height="75%" src="https://user-images.githubusercontent.com/71221149/107123378-84acbf80-689d-11eb-811d-26f20e32556c.gif">
</p>
## Getting Started
First, install mdBook-KaTeX
### **Non-Windows** users
```shell
cargo install mdbook-katex
```
### Windows users
The recommended way is to download the latest `x86_64-pc-windows-gnu.zip` from [Releases](https://github.com/lzanini/mdbook-katex/releases) for the full functionality.
Otherwise, building with the default feature may fail unless you have GCC, and you may only be able to [build with the `duktape` feature](#build-options-features) with limited features.
Another way is [Escape mode](#escape-mode-experimental).
### Basic setup
Then, add the following line to your `book.toml` file
```toml
[preprocessor.katex]
after = ["links"]
```
You can now use `$` and `$$` delimiters for inline and display math expressions within your `.md` files. If you need a regular dollar symbol, you need to escape delimiters with a backslash `\$`.
```markdown
# Chapter 1
Here is an inline example, $ \pi(\theta) $,
an equation,
$$ \nabla f(x) \in \mathbb{R}^n, $$
and a regular \$ symbol.
```
Math expressions will be rendered as HTML when running `mdbook build` or `mdbook serve` as usual.
## Pre-render mode (default)
Pre-rendering uses [the katex crate](https://github.com/xu-cheng/katex-rs).
[List of LaTeX functions supported by KaTeX](https://katex.org/docs/supported.html).
### KaTeX options
Most [KaTeX options](https://katex.org/docs/options.html) are supported via the `katex` crate.
Specify these options under `[preprocessor.katex]` in your `book.toml`:
| Argument | Type |
| :-------------------------------------------------------------------------------------------------- | :----------------------------------------- |
| [`output`](https://katex.org/docs/options.html#:~:text=default-,output,-string) | `"html"`, `"mathml"`, or `"htmlAndMathml"` |
| [`leqno`](https://katex.org/docs/options.html#:~:text=default-,leqno,-boolean) | `boolean` |
| [`fleqn`](https://katex.org/docs/options.html#:~:text=LaTeX-,fleqn,-boolean) | `boolean` |
| [`throw-on-error`](https://katex.org/docs/options.html#:~:text=package-,throwonerror,-boolean) | `boolean` |
| [`error-color`](https://katex.org/docs/options.html#:~:text=errorColor-,errorcolor,-string) | `string` |
| [`min-rule-thickness`](https://katex.org/docs/options.html#:~:text=state-,minrulethickness,-number) | `number` |
| [`max-size`](https://katex.org/docs/options.html#:~:text=true-,maxsize,-number) | `number` |
| [`max-expand`](https://katex.org/docs/options.html#:~:text=maxexpand) | `number` |
| [`trust`](https://katex.org/docs/options.html#:~:text=LaTeX-,trust,-boolean) | `boolean` |
There are also extra options to configure the behaviour of the preprocessor:
| Option | Description |
| :----------------- | :-------------------------------------------------------------------------------------------------------- |
| `no-css` | Do not inject KaTeX stylesheet link (See [Self-host KaTeX CSS and fonts](#self-host-katex-css-and-fonts)) |
| `macros` | Path to macros file (see [Custom macros](#custom-macros)) |
| `include-src` | Include math expressions source code (See [Including math Source](#including-math-source)) |
| `block-delimiter` | See [Custom delimiter](#custom-delimiter) |
| `inline-delimiter` | See [Custom delimiter](#custom-delimiter) |
| `pre-render` | See [Escape mode](#escape-mode-experimental) |
For example, the default configuration:
```toml
[preprocessor.katex]
after = ["links"]
# KaTeX options.
output = "html"
leqno = false
fleqn = false
throw-on-error = true
error-color = "#cc0000"
min-rule-thickness = -1.0
max-size = "Infinity"
max-expand = 1000
trust = false
# Extra options.
no-css = false
include-src = false
block-delimiter = { left = "$$", right = "$$" }
inline-delimiter = { left = "$", right = "$" }
pre-render = true
```
### Self-host KaTeX CSS and fonts
KaTeX requires a stylesheet and fonts to render correctly.
By default, mdBook-KaTeX injects a KaTeX stylesheet link pointing to a CDN.
If you want to self-host the CSS and fonts instead, you should specify in `book.toml`:
```toml
[preprocessor.katex]
no-css = true
```
and manually add the CSS and fonts to your mdBook project before building it.
See [mdBook-KaTeX Static CSS Example](https://github.com/SichangHe/mdbook_katex_static_css) for an automated example.
### Custom macros
Custom LaTeX macros must be defined in a `.txt` file, according to the following pattern
```txt
\grad:{\nabla}
\R:{\mathbb{R}^{#1 \times #2}}
```
You need to specify the path of this file in your `book.toml` as follows
```toml
[preprocessor.katex]
macros = "path/to/macros.txt"
```
These macros can then be used in your `.md` files
```markdown
# Chapter 1
$$ \grad f(x) \in \R{n}{p} $$
```
### Including math source
This option is added so users can have a convenient way to copy the source code of math expressions when they view the book.
When `include-src` is set to `true`, each math block is wrapped within a `<data>` tag with `class="katex-src"` with the included math source code being its `value` attribute.
For example, before being fed into mdBook,
```markdown
Define $f(x)$:
$$
f(x)=x^2\\
x\in\R
$$
```
is preprocessed into (the content of the `katex` `span`s are omitted and represented as `…`)
```markdown
Define <data class="katex-src" value="f(x)"><span class="katex">…</span></data>:
<data class="katex-src" value=" f(x)=x^2\\ x\in\R "><span class="katex-display"><span class="katex">…</span></span></data>
```
The math source code is included in a minimal fashion, and it is up to the users to write custom CSS and JavaScript to make use of it.
For more information about adding custom CSS and JavaScript in mdBook, see [additional-css and additional-js](https://rust-lang.github.io/mdBook/format/configuration/renderers.html#html-renderer-options).
If you need more information about this feature, please check the issues or file a new issue.
### Custom delimiter
To change the delimiters for math expressions, set the `block-delimiter` and `inline-delimiter` under `[preprocessor.katex]`.
For example, to use `\(`and `\)` for inline math and `\[` and `\]` for math block, set
```toml
[preprocessor.katex]
block-delimiter = { left = "\\[", right = "\\]" }
inline-delimiter = { left = "\\(", right = "\\)" }
```
Note that the double backslash above are just used to escape `\` in the TOML format.
### Caveats
`$\backslash$` does not work, but you can use `$\setminus$` instead.
Only the x86_64 Linux, Windows GNU, and macOS builds have full functionality (matrix, ...) , all other builds have compromised capabilities. See [#39](https://github.com/lzanini/mdbook-katex/issues/39) for the reasons.
### Build options (features)
Katex supports multiple js backends: `quick-js` (default), `duktape`, and `wasm-js`.
It is possible to build mdbook-katex with either `quick-js` (default) and `duktape`.
```shell
cargo install mdbook-katex --no-default-features --features duktape
```
Note that, for `duketape`, things such as matrices will not work. See [#67](https://github.com/lzanini/mdbook-katex/issues/67) for the reasons.
## Escape mode (experimental)
Escapes the string needed for a formula in advance so that it remains the original formula after the markdown processor.
Disable pre-render to use "Escape mode", and provide your client-side rendering library of choice. An example with `katex.js` included in `head.hbs` (see [index.hbs](https://rust-lang.github.io/mdBook/format/theme/index-hbs.html)) is provided below.
```toml
[preprocessor.katex]
after = ["links"]
pre-render = false
no-css = true
[output.html]
theme = "theme" # use theme/head.hbs
```
Note that the [KaTeX Options](#katex-options) are ignored in escape mode.
An example `head.hbs`:
```html
<link rel="stylesheet" href="https://unpkg.com/katex@latest/dist/katex.min.css">
<script defer src="https://unpkg.com/katex@latest/dist/katex.min.js"></script>
<script defer src="https://unpkg.com/katex@latest/dist/contrib/auto-render.min.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function () {
renderMathInElement(document.body, {
delimiters: [
{ left: '$$', right: '$$', display: true },
{ left: '$', right: '$', display: false },
],
});
});
</script>
```
================================================
FILE: src/cfg.rs
================================================
//! Configurations for preprocessing KaTeX.
use super::*;
/// Configuration for KaTeX preprocessor,
/// including options for `katex-rs` and feature options.
#[derive(Debug, Deserialize, Serialize)]
#[serde(default, rename_all = "kebab-case")]
pub struct KatexConfig {
// options for the katex-rust crate
/// KaTeX output type.
pub output: String,
/// Whether to have `\tags` rendered on the left instead of the right.
pub leqno: bool,
/// Whether to make display math flush left.
pub fleqn: bool,
/// Whether to let KaTeX throw a ParseError for invalid LaTeX.
pub throw_on_error: bool,
/// Color used for invalid LaTeX.
pub error_color: String,
/// Specifies a minimum thickness, in ems.
pub min_rule_thickness: f64,
/// Max size for user-specified sizes.
pub max_size: f64,
/// Limit the number of macro expansions to the specified number.
pub max_expand: i32,
/// Whether to trust users' input.
pub trust: bool,
// other options
/// Do not inject KaTeX CSS headers.
pub no_css: bool,
/// Include math source in rendered HTML.
pub include_src: bool,
/// Path to macro file.
pub macros: Option<String>,
/// Delimiter for math display block.
pub block_delimiter: Delimiter,
/// Delimiter for math inline block.
pub inline_delimiter: Delimiter,
/// Use katex.rs to pre-render math equations.
pub pre_render: bool,
}
impl Default for KatexConfig {
fn default() -> KatexConfig {
KatexConfig {
// default options for the katex-rust crate
// uses defaults specified in: https://katex.org/docs/options.html
output: "html".into(),
leqno: false,
fleqn: false,
throw_on_error: true,
error_color: String::from("#cc0000"),
min_rule_thickness: -1.0,
max_size: f64::INFINITY,
max_expand: 1000,
trust: false,
// other options
no_css: false,
include_src: false,
macros: None,
block_delimiter: Delimiter::same("$$".into()),
inline_delimiter: Delimiter::same("$".into()),
pre_render: true,
}
}
}
impl KatexConfig {
/// Generate extra options for the preprocessor.
pub fn build_extra_opts(&self) -> ExtraOpts {
ExtraOpts {
include_src: self.include_src,
block_delimiter: self.block_delimiter.clone(),
inline_delimiter: self.inline_delimiter.clone(),
}
}
}
/// Extract configuration for katex preprocessor from `book_cfg`.
pub fn get_config(
book_cfg: &mdbook_preprocessor::config::Config,
) -> Result<KatexConfig, toml::de::Error> {
let cfg = match book_cfg
.get::<toml::Value>("preprocessor.katex")
.unwrap_or_default()
{
Some(raw) => raw.clone().try_into(),
None => Ok(KatexConfig::default()),
};
cfg.or_else(|_| Ok(KatexConfig::default()))
}
================================================
FILE: src/escape.rs
================================================
//! Escaping math blocks to fix KaTeX rendering.
use super::*;
/// Escape a math block `item` into a delimited string.
/// Delimiter also need to be escaped, e.g. `\(,\)` and `\[,\]`.
pub fn escape_math_with_delimiter(item: &str, delimiter: &Delimiter) -> String {
let mut result = String::new();
escape_math(&delimiter.left, &mut result);
escape_math(item, &mut result);
escape_math(&delimiter.right, &mut result);
result
}
/// This is a amazing but useful little trick.
/// Mdbook's markdown engine will parse a part of KaTeX formula into HTML, e.g. `$[x^n](f + g)$`.
/// So if we escape the math formula in advance so that it passes through the markdown
/// engine as the original formula, it will be rendered correctly by katex.js.
pub fn escape_math(item: &str, result: &mut String) {
for c in item.chars() {
match c {
'_' => {
result.push_str("\\_");
}
'*' => {
result.push_str("\\*");
}
'\\' => {
result.push_str("\\\\");
}
_ => {
result.push(c);
}
}
}
}
================================================
FILE: src/lib.rs
================================================
#![deny(missing_docs)]
//! Preprocess math blocks using KaTeX for mdBook.
use std::{
borrow::Cow,
collections::HashMap,
collections::VecDeque,
fs::File,
io::{stderr, Read},
path::{Path, PathBuf},
};
use mdbook_preprocessor::{book::Book, errors::Result, Preprocessor, PreprocessorContext};
use rayon::iter::*;
use serde_derive::{Deserialize, Serialize};
use tracing::*;
use tracing_subscriber::EnvFilter;
use {
cfg::*,
escape::*,
preprocess::*,
scan::{Event, *},
};
pub mod cfg;
pub mod escape;
pub mod preprocess;
pub mod scan;
#[cfg(feature = "pre-render")]
pub mod render;
#[cfg(feature = "pre-render")]
pub use render::*;
#[cfg(test)]
mod tests;
#[doc(hidden)]
pub fn init_tracing() {
_ = tracing_subscriber::fmt()
.with_writer(stderr)
.with_ansi(true)
.with_env_filter(
EnvFilter::builder()
.with_default_directive(Level::INFO.into())
.from_env_lossy(),
)
.try_init();
}
================================================
FILE: src/main.rs
================================================
use clap::{crate_version, Arg, ArgMatches, Command};
use mdbook_katex::{init_tracing, preprocess::KatexProcessor};
use mdbook_preprocessor::errors::{Error, Result};
use mdbook_preprocessor::{parse_input, Preprocessor};
use std::io;
use tracing::*;
/// Parse CLI options.
pub fn make_app() -> Command {
Command::new("mdbook-katex")
.version(crate_version!())
.about("A preprocessor that renders KaTex equations to HTML.")
.subcommand(
Command::new("supports")
.arg(Arg::new("renderer").required(true))
.about("Check whether a renderer is supported by this preprocessor"),
)
}
/// Produce a warning on mdBook version mismatch.
fn check_mdbook_version(version: &str) {
if version != mdbook_preprocessor::MDBOOK_VERSION {
warn!(
"This mdbook-katex was built against mdbook v{}, \
but we are being called from mdbook v{version}. \
If you have any issue, this might be a reason.",
mdbook_preprocessor::MDBOOK_VERSION,
)
}
}
/// Tell mdBook if we support what it asks for.
fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> Result<()> {
let renderer = sub_args
.get_one::<String>("renderer")
.expect("Required argument");
let supported = pre.supports_renderer(renderer).unwrap_or(false);
if supported {
Ok(())
} else {
Err(Error::msg(format!(
"The katex preprocessor does not support the '{renderer}' renderer",
)))
}
}
/// Preprocess `book` using `pre` and print it out.
fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
let (ctx, book) = parse_input(io::stdin())?;
check_mdbook_version(&ctx.mdbook_version);
let processed_book = pre.run(&ctx, book)?;
serde_json::to_writer(io::stdout(), &processed_book)?;
Ok(())
}
fn main() -> Result<()> {
init_tracing();
// set up app
let matches = make_app().get_matches();
let pre = KatexProcessor;
// determine what behaviour has been requested
if let Some(sub_args) = matches.subcommand_matches("supports") {
// handle cmdline supports
handle_supports(&pre, sub_args)
} else {
// handle preprocessing
handle_preprocessing(&pre)
}
}
================================================
FILE: src/preprocess.rs
================================================
//! Preprocessing and escaping with KaTeX.
use super::*;
/// When `pre-render` is called but not enabled.
#[cfg(not(feature = "pre-render"))]
pub fn process_all_chapters_prerender(
_: &mut Book,
_: &KatexConfig,
_: &str,
_: &PreprocessorContext,
) -> Vec<String> {
panic!("Pre-render is unavailable because this `mdbook-katex` program does not have the `pre-render` feature enabled, only escaping mode is available, and you can set `pre-render = false` to enable it. If you do need `pre-render` mode, you need to add the `pre-render` feature and recompile. See the README at <https://github.com/lzanini/mdbook-katex/blob/master/README.md>.")
}
/// Header that points to CDN for the KaTeX stylesheet.
pub const KATEX_HEADER: &str = r#"<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css">
"#;
/// Extra options for the KaTeX preprocessor.
#[derive(Clone, Debug)]
pub struct ExtraOpts {
/// Path to macro file.
pub include_src: bool,
/// Delimiter for math display block.
pub block_delimiter: Delimiter,
/// Delimiter for math inline block.
pub inline_delimiter: Delimiter,
}
/// KaTeX `mdbook::preprocess::Proprecessor` for mdBook.
pub struct KatexProcessor;
// preprocessor to inject rendered katex blocks and stylesheet
impl Preprocessor for KatexProcessor {
fn name(&self) -> &str {
"katex"
}
fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
// parse TOML config
let cfg = get_config(&ctx.config)?;
let header = if cfg.no_css { "" } else { KATEX_HEADER }.to_owned();
if cfg.pre_render {
process_all_chapters_prerender(&mut book, &cfg, &header, ctx);
} else {
process_all_chapters_escape(&mut book, &cfg, &header, ctx);
}
Ok(book)
}
}
/// Escape all Katex equations.
pub fn process_all_chapters_escape(
book: &mut Book,
cfg: &KatexConfig,
stylesheet_header: &str,
_: &PreprocessorContext,
) {
let extra_opts = cfg.build_extra_opts();
book.for_each_chapter_mut(|chapter| {
chapter.content = process_chapter_escape(&chapter.content, &extra_opts, stylesheet_header);
});
}
/// Escape Katex equations.
pub fn process_chapter_escape(
raw_content: &str,
extra_opts: &ExtraOpts,
stylesheet_header: &str,
) -> String {
get_render_tasks(raw_content, stylesheet_header, extra_opts)
.into_par_iter()
.map(|rend| match rend {
Render::Text(t) => t.into(),
Render::InlineTask(item) => {
escape_math_with_delimiter(item, &extra_opts.inline_delimiter).into()
}
Render::DisplayTask(item) => {
escape_math_with_delimiter(item, &extra_opts.block_delimiter).into()
}
})
.collect::<Vec<Cow<_>>>()
.join("")
}
/// A render job for chapter processing.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Render<'a> {
/// No need to render.
Text(&'a str),
/// A render task for a math inline block.
InlineTask(&'a str),
/// A render task for a math display block.
DisplayTask(&'a str),
}
/// Find all the `Render` tasks in `raw_content`.
pub fn get_render_tasks<'a>(
raw_content: &'a str,
stylesheet_header: &'a str,
extra_opts: &ExtraOpts,
) -> Vec<Render<'a>> {
let scan = Scan::new(
raw_content,
&extra_opts.block_delimiter,
&extra_opts.inline_delimiter,
);
let mut rendering = Vec::new();
rendering.push(Render::Text(stylesheet_header));
let mut checkpoint = 0;
for event in scan {
match event {
Event::Begin(begin) => checkpoint = begin,
Event::TextEnd(end) => rendering.push(Render::Text(&raw_content[checkpoint..end])),
Event::InlineEnd(end) => {
rendering.push(Render::InlineTask(&raw_content[checkpoint..end]));
checkpoint = end;
}
Event::BlockEnd(end) => {
rendering.push(Render::DisplayTask(&raw_content[checkpoint..end]));
checkpoint = end;
}
}
}
if raw_content.len() > checkpoint {
rendering.push(Render::Text(&raw_content[checkpoint..raw_content.len()]));
}
rendering
}
================================================
FILE: src/render/cfg.rs
================================================
//! Extra configurations for pre-rendering KaTeX.
use super::*;
impl KatexConfig {
/// Configured output type.
/// Defaults to `Html`, can also be `Mathml` or `HtmlAndMathml`.
pub fn output_type(&self) -> katex::OutputType {
match self.output.as_str() {
"html" => katex::OutputType::Html,
"mathml" => katex::OutputType::Mathml,
"htmlAndMathml" => katex::OutputType::HtmlAndMathml,
other => {
error!(
"[preprocessor.katex]: `{other}` is not a valid choice for `output`! Please check your `book.toml`.
Defaulting to `html`. Other valid choices for output are `mathml` and `htmlAndMathml`."
);
katex::OutputType::Html
}
}
}
/// From `root`, load macros and generate configuration options
/// `(inline_opts, display_opts)`.
pub fn build_opts<P>(&self, root: P) -> (katex::Opts, katex::Opts)
where
P: AsRef<Path>,
{
// load macros as a HashMap
let macros = load_macros(root, &self.macros);
self.build_opts_from_macros(macros)
}
/// Given `macros`, generate `(inline_opts, display_opts)`.
pub fn build_opts_from_macros(
&self,
macros: HashMap<String, String>,
) -> (katex::Opts, katex::Opts) {
let mut configure_katex_opts = katex::Opts::builder();
configure_katex_opts
.output_type(self.output_type())
.leqno(self.leqno)
.fleqn(self.fleqn)
.throw_on_error(self.throw_on_error)
.error_color(self.error_color.clone())
.macros(macros)
.min_rule_thickness(self.min_rule_thickness)
.max_size(self.max_size)
.max_expand(self.max_expand)
.trust(self.trust);
// inline rendering options
let inline_opts = configure_katex_opts
.clone()
.display_mode(false)
.build()
.unwrap();
// display rendering options
let display_opts = configure_katex_opts.display_mode(true).build().unwrap();
(inline_opts, display_opts)
}
}
/// Load macros from `root`/`macros_path` into a `HashMap`.
fn load_macros<P>(root: P, macros_path: &Option<String>) -> HashMap<String, String>
where
P: AsRef<Path>,
{
// load macros as a HashMap
let mut map = HashMap::new();
if let Some(path) = get_macro_path(root, macros_path) {
let macro_str = load_as_string(&path);
for couple in macro_str.split('\n') {
// only consider lines starting with a backslash
if let Some('\\') = couple.chars().next() {
let couple: Vec<&str> = couple.splitn(2, ':').collect();
map.insert(String::from(couple[0]), String::from(couple[1]));
}
}
}
map
}
/// Absolute path of the macro file.
pub fn get_macro_path<P>(root: P, macros_path: &Option<String>) -> Option<PathBuf>
where
P: AsRef<Path>,
{
macros_path
.as_ref()
.map(|path| root.as_ref().join(PathBuf::from(path)))
}
/// Read file at `path`.
pub fn load_as_string(path: &Path) -> String {
let display = path.display();
let mut file = match File::open(path) {
Err(why) => panic!("couldn't open {display}: {why}"),
Ok(file) => file,
};
let mut string = String::new();
if let Err(why) = file.read_to_string(&mut string) {
panic!("couldn't read {display}: {why}")
};
string
}
================================================
FILE: src/render/preprocess.rs
================================================
//! Preprocessing and pre-rendering with KaTeX.
use katex::Opts;
use super::*;
/// Render all Katex equations.
pub fn process_all_chapters_prerender(
book: &mut Book,
cfg: &KatexConfig,
stylesheet_header: &str,
ctx: &PreprocessorContext,
) {
let extra_opts = cfg.build_extra_opts();
let (inline_opts, display_opts) = cfg.build_opts(&ctx.root);
book.for_each_chapter_mut(|chapter| {
chapter.content = process_chapter_prerender(
&chapter.content,
inline_opts.clone(),
display_opts.clone(),
stylesheet_header,
&extra_opts,
);
});
}
/// Render Katex equations in a `Chapter` as HTML, and add the Katex CSS.
pub fn process_chapter_prerender(
raw_content: &str,
inline_opts: Opts,
display_opts: Opts,
stylesheet_header: &str,
extra_opts: &ExtraOpts,
) -> String {
get_render_tasks(raw_content, stylesheet_header, extra_opts)
.into_par_iter()
.map(|rend| match rend {
Render::Text(t) => t.into(),
Render::InlineTask(item) => {
render(item, inline_opts.clone(), extra_opts.clone(), false).into()
}
Render::DisplayTask(item) => {
render(item, display_opts.clone(), extra_opts.clone(), true).into()
}
})
.collect::<Vec<Cow<_>>>()
.join("")
}
================================================
FILE: src/render.rs
================================================
//! Render KaTeX math block to HTML
use katex::{Error, Opts};
use super::*;
pub use {cfg::*, preprocess::*};
mod cfg;
mod preprocess;
/// Render a math block `item` into HTML following `opts`.
/// Wrap result in `<data>` tag if `extra_opts.include_src`.
#[instrument(skip(opts, extra_opts, display))]
pub fn render(item: &str, opts: Opts, extra_opts: ExtraOpts, display: bool) -> String {
let mut rendered_content = String::new();
// try to render equation
match katex::render_with_opts(item, opts) {
Ok(rendered) => {
let rendered = rendered.replace('\n', " ");
if extra_opts.include_src {
// Wrap around with `data.katex-src` tag.
rendered_content.push_str(r#"<data class="katex-src" value=""#);
rendered_content.push_str(&item.replace('"', r#"\""#).replace('\n', r" "));
rendered_content.push_str(r#"">"#);
rendered_content.push_str(&rendered);
rendered_content.push_str(r"</data>");
} else {
rendered_content.push_str(&rendered);
}
}
// if rendering fails, keep the unrendered equation
Err(why) => {
match why {
Error::JsExecError(why) => {
warn!("Rendering failed, keeping the original content: {why}")
}
_ => error!(
?why,
"Unexpected rendering failure, keeping the original content."
),
}
let delimiter = match display {
true => &extra_opts.block_delimiter,
false => &extra_opts.inline_delimiter,
};
rendered_content.push_str(&delimiter.left);
rendered_content.push_str(item);
rendered_content.push_str(&delimiter.right);
}
}
rendered_content
}
================================================
FILE: src/scan.rs
================================================
//! Scan Markdown text and identify math block events.
use super::*;
/// A pair of strings are delimiters.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Delimiter {
/// Left delimiter.
pub left: String,
/// Right delimiter.
pub right: String,
}
impl Delimiter {
/// Same left and right `delimiter`.
pub fn same(delimiter: String) -> Self {
Self {
left: delimiter.clone(),
right: delimiter,
}
}
/// The first byte of the left delimiter.
pub fn first(&self) -> u8 {
self.left.as_bytes()[0]
}
/// Whether `to_match` matches the left delimiter.
pub fn match_left(&self, to_match: &[u8]) -> bool {
if self.left.len() > to_match.len() {
return false;
}
for (we, they) in self.left.as_bytes().iter().zip(to_match) {
if we != they {
return false;
}
}
true
}
}
/// An event for parsing in a Markdown file.
#[derive(Debug)]
pub enum Event {
/// A beginning of text or math block.
Begin(usize),
/// An end of a text block.
TextEnd(usize),
/// An end of an inline math block.
InlineEnd(usize),
/// An end of a display math block.
BlockEnd(usize),
}
/// Scanner for text to identify block and inline math `Event`s.
#[derive(Debug)]
pub struct Scan<'a> {
string: &'a str,
bytes: &'a [u8],
index: usize,
/// Buffer for block and inline math `Event`s.
pub events: VecDeque<Event>,
block_delimiter: &'a Delimiter,
inline_delimiter: &'a Delimiter,
}
impl Iterator for Scan<'_> {
type Item = Event;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.events.pop_front() {
Some(item) => return Some(item),
None => self.process_byte().ok()?,
}
}
}
}
impl<'a> Scan<'a> {
/// Set up a `Scan` for `string` with given delimiters.
pub fn new(
string: &'a str,
block_delimiter: &'a Delimiter,
inline_delimiter: &'a Delimiter,
) -> Self {
Self {
string,
bytes: string.as_bytes(),
index: 0,
events: VecDeque::new(),
block_delimiter,
inline_delimiter,
}
}
/// Scan, identify and store all `Event`s in `self.events`.
pub fn run(&mut self) {
while let Ok(()) = self.process_byte() {}
}
/// Get byte currently pointed to. Returns `Err(())` if out of bound.
fn get_byte(&self) -> Result<u8, ()> {
self.bytes.get(self.index).map(|b| b.to_owned()).ok_or(())
}
/// Increment index.
fn inc(&mut self) {
self.index += 1;
}
/// Scan one byte, proceed process based on the byte.
/// - Start of delimiter => call `process_delimit`.
/// - `\` => skip one byte.
/// - `` ` `` => call `process_backtick`.
/// Return `Err(())` if no more bytes to process.
fn process_byte(&mut self) -> Result<(), ()> {
let byte = self.get_byte()?;
self.inc();
match byte {
b if b == self.block_delimiter.first()
&& self
.block_delimiter
.match_left(&self.bytes[(self.index - 1)..]) =>
{
self.index -= 1;
self.process_delimit(false)?;
}
b if b == self.inline_delimiter.first()
&& self
.inline_delimiter
.match_left(&self.bytes[(self.index - 1)..]) =>
{
self.index -= 1;
self.process_delimit(true)?;
}
b'\\' => {
self.inc();
}
b'`' => self.process_backtick()?,
_ => (),
}
Ok(())
}
/// Fully skip a backtick-delimited code block.
/// Guaranteed to match the number of backticks in delimiters.
/// Return `Err(())` if no more bytes to process.
fn process_backtick(&mut self) -> Result<(), ()> {
let mut n_back_ticks = 1;
loop {
let byte = self.get_byte()?;
if byte == b'`' {
self.inc();
n_back_ticks += 1;
} else {
break;
}
}
loop {
self.index += self.string[self.index..]
.find(&"`".repeat(n_back_ticks))
.ok_or(())?
+ n_back_ticks;
if self.get_byte()? == b'`' {
// Skip excessive backticks.
self.inc();
while let b'`' = self.get_byte()? {
self.inc();
}
} else {
break;
}
}
Ok(())
}
/// Skip a full math block.
/// Add `Event`s to mark the start and end of the math block and
/// surrounding text blocks.
/// Return `Err(())` if no more bytes to process.
fn process_delimit(&mut self, inline: bool) -> Result<(), ()> {
if self.index > 0 {
self.events.push_back(Event::TextEnd(self.index));
}
let delim = if inline {
self.inline_delimiter
} else {
self.block_delimiter
};
self.index += delim.left.len();
self.events.push_back(Event::Begin(self.index));
loop {
self.index += self.string[self.index..].find(&delim.right).ok_or(())?;
// Check `\`.
let mut escaped = false;
let mut checking = self.index;
loop {
checking -= 1;
if self.bytes.get(checking) == Some(&b'\\') {
escaped = !escaped;
} else {
break;
}
}
if !escaped {
let end_event = if inline {
Event::InlineEnd(self.index)
} else {
Event::BlockEnd(self.index)
};
self.events.push_back(end_event);
self.index += delim.right.len();
self.events.push_back(Event::Begin(self.index));
break;
} else {
self.index += delim.right.len();
}
}
Ok(())
}
}
================================================
FILE: src/tests/escape.rs
================================================
use super::*;
fn test_render(raw_content: &str) -> (String, String) {
let (stylesheet_header, mut rendered) =
test_render_with_cfg(&[raw_content], KatexConfig::default());
(stylesheet_header, rendered.pop().unwrap())
}
fn test_render_with_cfg(raw_contents: &[&str], cfg: KatexConfig) -> (String, Vec<String>) {
let extra_opts = cfg.build_extra_opts();
let stylesheet_header = KATEX_HEADER.to_owned();
let rendered = raw_contents
.iter()
.map(|raw_content| process_chapter_escape(raw_content, &extra_opts, &stylesheet_header))
.collect();
(stylesheet_header, rendered)
}
#[test]
fn test_escape_without_math() {
let raw_content = r"Some text, and more text.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_dollar_escape() {
let raw_content = r"Some text, \$\$ and more text.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_escape_with_math() {
let raw_content = r"A simple fomula, $\sum_{n=1}^\infty \frac{1}{n^2} = \frac{\pi^2}{6}$.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header
+ r"A simple fomula, $\\sum\_{n=1}^\\infty \\frac{1}{n^2} = \\frac{\\pi^2}{6}$.";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_escape_underscore() {
let raw_content = r"A simple `f_f_f`, f_f_f, f`f$f_$f_` fomula, $\sum_{n=1}^\infty\\$.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header
+ r"A simple `f_f_f`, f_f_f, f`f$f_$f_` fomula, $\\sum\_{n=1}^\\infty\\\\$.";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_escape_vmatrix() {
let raw_content = r"$$\begin{vmatrix}a&b\\c&d\end{vmatrix}$$";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + r"$$\\begin{vmatrix}a&b\\\\c&d\\end{vmatrix}$$";
debug_assert_eq!(expected_output, rendered_content);
}
================================================
FILE: src/tests/mod.rs
================================================
use super::*;
#[test]
fn test_name() {
let pre = KatexProcessor;
let preprocessor: &dyn Preprocessor = ⪯
assert_eq!(preprocessor.name(), "katex")
}
#[test]
fn test_support_html() {
let preprocessor = KatexProcessor;
assert!(preprocessor.supports_renderer("html").unwrap());
assert!(preprocessor.supports_renderer("other_renderer").unwrap())
}
mod escape;
#[cfg(feature = "pre-render")]
mod render;
================================================
FILE: src/tests/render/not_duktape.rs
================================================
use super::*;
#[test]
fn test_katex_rendering_vmatrix() {
use crate::cfg::KatexConfig;
let math_expr = r"\begin{vmatrix}a&b\\c&d\end{vmatrix}";
let cfg = KatexConfig::default();
let (_, display_opts) = cfg.build_opts_from_macros(HashMap::new());
let _ = katex::render_with_opts(math_expr, display_opts).unwrap();
}
#[test]
fn test_rendering_vmatrix() {
let raw_content = r"$$\begin{vmatrix}a&b\\c&d\end{vmatrix}$$";
let (stylesheet_header, rendered_content) = test_render(raw_content);
debug_assert_eq!(
stylesheet_header+
"<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:2.4em;vertical-align:-0.95em;\"></span><span class=\"minner\"><span class=\"mopen\"><span class=\"delimsizing mult\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.45em;\"><span style=\"top:-3.45em;\"><span class=\"pstrut\" style=\"height:4.4em;\"></span><span style=\"width:0.333em;height:2.400em;\"><svg xmlns=\"http://www.w3.org/2000/svg\" width='0.333em' height='2.400em' viewBox='0 0 333 2400'><path d='M145 15 v585 v1200 v585 c2.667,10,9.667,15,21,15 c10,0,16.667,-5,20,-15 v-585 v-1200 v-585 c-2.667,-10,-9.667,-15,-21,-15 c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v1200 v585 h43z'/></svg></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.95em;\"><span></span></span></span></span></span></span><span class=\"mord\"><span class=\"mtable\"><span class=\"col-align-c\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span class=\"pstrut\" style=\"height:3em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\">a</span></span></span><span style=\"top:-2.41em;\"><span class=\"pstrut\" style=\"height:3em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\">c</span></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.95em;\"><span></span></span></span></span></span><span class=\"arraycolsep\" style=\"width:0.5em;\"></span><span class=\"arraycolsep\" style=\"width:0.5em;\"></span><span class=\"col-align-c\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.45em;\"><span style=\"top:-3.61em;\"><span class=\"pstrut\" style=\"height:3em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\">b</span></span></span><span style=\"top:-2.41em;\"><span class=\"pstrut\" style=\"height:3em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\">d</span></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.95em;\"><span></span></span></span></span></span></span></span><span class=\"mclose\"><span class=\"delimsizing mult\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.45em;\"><span style=\"top:-3.45em;\"><span class=\"pstrut\" style=\"height:4.4em;\"></span><span style=\"width:0.333em;height:2.400em;\"><svg xmlns=\"http://www.w3.org/2000/svg\" width='0.333em' height='2.400em' viewBox='0 0 333 2400'><path d='M145 15 v585 v1200 v585 c2.667,10,9.667,15,21,15 c10,0,16.667,-5,20,-15 v-585 v-1200 v-585 c-2.667,-10,-9.667,-15,-21,-15 c-10,0,-16.667,5,-20,15z M188 15 H145 v585 v1200 v585 h43z'/></svg></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.95em;\"><span></span></span></span></span></span></span></span></span></span></span></span>",
rendered_content
);
}
================================================
FILE: src/tests/render.rs
================================================
use super::*;
fn test_render(raw_content: &str) -> (String, String) {
let (stylesheet_header, mut rendered) = test_render_with_macro(&[raw_content], HashMap::new());
(stylesheet_header, rendered.pop().unwrap())
}
fn test_render_with_macro(
raw_contents: &[&str],
macros: HashMap<String, String>,
) -> (String, Vec<String>) {
test_render_with_cfg(raw_contents, macros, KatexConfig::default())
}
fn test_render_with_cfg(
raw_contents: &[&str],
macros: HashMap<String, String>,
cfg: KatexConfig,
) -> (String, Vec<String>) {
let (inline_opts, display_opts) = cfg.build_opts_from_macros(macros);
let extra_opts = cfg.build_extra_opts();
let stylesheet_header = KATEX_HEADER.to_owned();
let rendered = raw_contents
.iter()
.map(|raw_content| {
process_chapter_prerender(
raw_content,
inline_opts.clone(),
display_opts.clone(),
&stylesheet_header,
&extra_opts,
)
})
.collect();
(stylesheet_header, rendered)
}
#[test]
fn test_rendering_without_math() {
let raw_content = r"Some text, and more text.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_dollar_escaping() {
let raw_content = r"Some text, \$\$ and more text.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_dollar_escaping_inside_expr() {
let raw_content = r"We randomly assign: $r \xleftarrow{\$} G $.";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header +
"We randomly assign: <span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:1.158em;vertical-align:-0.011em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.02778em;\">r</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel x-arrow\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.147em;\"><span style=\"top:-3.322em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight x-arrow-pad\"><span class=\"mord mtight\"><span class=\"mord mtight\">$</span></span></span></span><span class=\"svg-align\" style=\"top:-2.689em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"hide-tail\" style=\"height:0.522em;min-width:1.469em;\"><svg xmlns=\"http://www.w3.org/2000/svg\" width='400em' height='0.522em' viewBox='0 0 400000 522' preserveAspectRatio='xMinYMin slice'><path d='M400000 241H110l3-3c68.7-52.7 113.7-120 135-202 4-14.7 6-23 6-25 0-7.3-7-11-21-11-8 0-13.2.8-15.5 2.5-2.3 1.7-4.2 5.8 -5.5 12.5-1.3 4.7-2.7 10.3-4 17-12 48.7-34.8 92-68.5 130S65.3 228.3 18 247 c-10 4-16 7.7-18 11 0 8.7 6 14.3 18 17 47.3 18.7 87.8 47 121.5 85S196 441.3 208 490c.7 2 1.3 5 2 9s1.2 6.7 1.5 8c.3 1.3 1 3.3 2 6s2.2 4.5 3.5 5.5c1.3 1 3.3 1.8 6 2.5s6 1 10 1c14 0 21-3.7 21-11 0-2-2-10.3-6-25-20-79.3-65-146.7-135-202 l-3-3h399890zM100 241v40h399900v-40z'/></svg></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.011em;\"><span></span></span></span></span></span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6833em;\"></span><span class=\"mord mathnormal\">G</span></span></span></span>.";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_inline_rendering() {
let (stylesheet_header, rendered_content) =
test_render(r"Some text, $\nabla f(x) \in \mathbb{R}^n$, and more text.");
let expected_output=stylesheet_header+"Some text, <span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:1em;vertical-align:-0.25em;\"></span><span class=\"mord\">∇</span><span class=\"mord mathnormal\" style=\"margin-right:0.10764em;\">f</span><span class=\"mopen\">(</span><span class=\"mord mathnormal\">x</span><span class=\"mclose\">)</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">∈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6889em;\"></span><span class=\"mord\"><span class=\"mord mathbb\">R</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mathnormal mtight\">n</span></span></span></span></span></span></span></span></span></span></span>, and more text.";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_display_rendering() {
let (stylesheet_header, rendered_content) =
test_render(r"Some text, $\nabla f(x) \in \mathbb{R}^n$, and more text.");
let expected_output=stylesheet_header+"Some text, <span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:1em;vertical-align:-0.25em;\"></span><span class=\"mord\">∇</span><span class=\"mord mathnormal\" style=\"margin-right:0.10764em;\">f</span><span class=\"mopen\">(</span><span class=\"mord mathnormal\">x</span><span class=\"mclose\">)</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">∈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6889em;\"></span><span class=\"mord\"><span class=\"mord mathbb\">R</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.6644em;\"><span style=\"top:-3.063em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mathnormal mtight\">n</span></span></span></span></span></span></span></span></span></span></span>, and more text.";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_macros_without_argument() {
let mut macros = HashMap::new();
macros.insert(String::from(r"\grad"), String::from(r"\nabla"));
let raw_content_no_macro = r"Some text, $\nabla f(x) \in \mathbb{R}^n$, and more text.";
let raw_content_macro = r"Some text, $\grad f(x) \in \mathbb{R}^n$, and more text.";
let (_, rendered) = test_render_with_macro(&[raw_content_macro, raw_content_no_macro], macros);
debug_assert_eq!(rendered[0], rendered[1]);
}
#[test]
fn test_macros_with_argument() {
let mut macros = HashMap::new();
macros.insert(String::from(r"\R"), String::from(r"\mathbb{R}^#1"));
let raw_content_no_macro = r"Some text, $\nabla f(x) \in \mathbb{R}^1$, and more text.";
let raw_content_macro = r"Some text, $\nabla f(x) \in \R{1}$, and more text.";
let (_, rendered) = test_render_with_macro(&[raw_content_macro, raw_content_no_macro], macros);
debug_assert_eq!(rendered[0], rendered[1]);
}
#[test]
fn test_macro_file_loading() {
let cfg_str = r#"
[book]
src = "src"
[preprocessor.katex]
macros = "macros.txt"
"#;
let book_cfg = cfg_str.parse().unwrap();
let cfg = get_config(&book_cfg).unwrap();
debug_assert_eq!(
get_macro_path(PathBuf::from("book"), &cfg.macros),
Some(PathBuf::from("book/macros.txt")) // We supply a root, just like the preproccessor context does
);
}
#[test]
fn test_rendering_table_with_math() {
let raw_content = r"| Syntax | Description |
| --- | ----------- |
| $\vec{a}$ | Title |
| Paragraph | Text |";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(
expected_output.lines().count(),
rendered_content.lines().count()
);
}
#[test]
fn test_rendering_delimiter_in_code_block() {
let raw_content = r"``` $\omega$ ```";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_rendering_delimiter_in_inline_code() {
let raw_content = r"`$\omega$`";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_rendering_delimiter_in_inline_code_when_inline_delimiter_starts_with_backtick() {
let raw_content = r"`` `$\omega$` ``";
let cfg = KatexConfig {
inline_delimiter: Delimiter {
left: "`$".into(),
right: "$`".into(),
},
..KatexConfig::default()
};
let (stylesheet_header, mut rendered_content) =
test_render_with_cfg(&[raw_content], HashMap::new(), cfg);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content.pop().unwrap());
}
#[test]
fn test_rendering_delimiter_in_block_code_when_block_delimiter_starts_with_backtick() {
let raw_content = r#"````
```math
$\omega$
```
````
"#;
let cfg = KatexConfig {
block_delimiter: Delimiter {
left: "```math".into(),
right: "```".into(),
},
..KatexConfig::default()
};
let (stylesheet_header, mut rendered_content) =
test_render_with_cfg(&[raw_content], HashMap::new(), cfg);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content.pop().unwrap());
}
#[test]
fn test_rendering_delimiter_in_inline_code_when_block_delimiter_starts_with_backtick() {
let raw_content = r"`$\omega$`";
let cfg = KatexConfig {
block_delimiter: Delimiter {
left: "```math".into(),
right: "```".into(),
},
..KatexConfig::default()
};
let (stylesheet_header, mut rendered_content) =
test_render_with_cfg(&[raw_content], HashMap::new(), cfg);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content.pop().unwrap());
}
#[test]
fn test_invalid_expr_inline() {
init_tracing();
let raw_content = r"$\<$";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_invalid_expr_display() {
init_tracing();
let raw_content = r"$$ \< $$";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + raw_content;
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_escaping_backtick() {
let raw_content = r"\`$\omega$\`";
let (stylesheet_header, rendered_content) = test_render(raw_content);
let expected_output = stylesheet_header + "\\`<span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.4306em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.03588em;\">ω</span></span></span></span>\\`";
debug_assert_eq!(expected_output, rendered_content);
}
#[test]
fn test_include_src() {
let raw_content = r"Define $f(x)$:
$$
f(x)=x^2\\
x\in\R
$$";
let (stylesheet_header, rendered_content) = test_render_with_cfg(
&[raw_content],
HashMap::new(),
KatexConfig {
include_src: true,
..KatexConfig::default()
},
);
debug_assert_eq!(stylesheet_header + "Define <data class=\"katex-src\" value=\"f(x)\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:1em;vertical-align:-0.25em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.10764em;\">f</span><span class=\"mopen\">(</span><span class=\"mord mathnormal\">x</span><span class=\"mclose\">)</span></span></span></span></data>:\n\n<data class=\"katex-src\" value=\" f(x)=x^2\\\\ x\\in\\R \"><span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:1em;vertical-align:-0.25em;\"></span><span class=\"mord mathnormal\" style=\"margin-right:0.10764em;\">f</span><span class=\"mopen\">(</span><span class=\"mord mathnormal\">x</span><span class=\"mclose\">)</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">=</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.8641em;\"></span><span class=\"mord\"><span class=\"mord mathnormal\">x</span><span class=\"msupsub\"><span class=\"vlist-t\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.8641em;\"><span style=\"top:-3.113em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">2</span></span></span></span></span></span></span></span></span><span class=\"mspace newline\"></span><span class=\"base\"><span class=\"strut\" style=\"height:0.5782em;vertical-align:-0.0391em;\"></span><span class=\"mord mathnormal\">x</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span><span class=\"mrel\">∈</span><span class=\"mspace\" style=\"margin-right:0.2778em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6889em;\"></span><span class=\"mord mathbb\">R</span></span></span></span></span></data>", rendered_content[0]);
}
#[test]
fn test_fenced_code() {
let raw_content = r"`\` and `` ` `` $\Leftarrow$
```
`\` and `` ` ``
```
while ` ``` ` and ````` ```` ````` $\Leftarrow$
``````
` ``` ` and ````` ```` `````
``````
$$
\Uparrow
$$";
let (stylesheet_header, rendered_content) =
test_render_with_cfg(&[raw_content], HashMap::new(), KatexConfig::default());
debug_assert_eq!(
stylesheet_header +
"`\\` and `` ` `` <span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.3669em;\"></span><span class=\"mrel\">⇐</span></span></span></span>\n```\n`\\` and `` ` ``\n```\nwhile ` ``` ` and ````` ```` ````` <span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.3669em;\"></span><span class=\"mrel\">⇐</span></span></span></span>\n``````\n` ``` ` and ````` ```` `````\n``````\n<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.8889em;vertical-align:-0.1944em;\"></span><span class=\"mrel\">⇑</span></span></span></span></span>",
rendered_content[0]
);
}
#[test]
fn test_inline_rendering_w_custom_delimiter() {
let raw_content = r"These $\(a\times b\) are from
\[
\int_0^abdx
\]";
let (stylesheet_header, rendered_content) = test_render_with_cfg(
&[raw_content],
HashMap::new(),
KatexConfig {
inline_delimiter: Delimiter {
left: r"\(".into(),
right: r"\)".into(),
},
block_delimiter: Delimiter {
left: r"\[".into(),
right: r"\]".into(),
},
..KatexConfig::default()
},
);
let expected_output = stylesheet_header + "These $<span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:0.6667em;vertical-align:-0.0833em;\"></span><span class=\"mord mathnormal\">a</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span><span class=\"mbin\">×</span><span class=\"mspace\" style=\"margin-right:0.2222em;\"></span></span><span class=\"base\"><span class=\"strut\" style=\"height:0.6944em;\"></span><span class=\"mord mathnormal\">b</span></span></span></span> are from\n<span class=\"katex-display\"><span class=\"katex\"><span class=\"katex-html\" aria-hidden=\"true\"><span class=\"base\"><span class=\"strut\" style=\"height:2.3262em;vertical-align:-0.9119em;\"></span><span class=\"mop\"><span class=\"mop op-symbol large-op\" style=\"margin-right:0.44445em;position:relative;top:-0.0011em;\">∫</span><span class=\"msupsub\"><span class=\"vlist-t vlist-t2\"><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:1.4143em;\"><span style=\"top:-1.7881em;margin-left:-0.4445em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mtight\">0</span></span></span><span style=\"top:-3.8129em;margin-right:0.05em;\"><span class=\"pstrut\" style=\"height:2.7em;\"></span><span class=\"sizing reset-size6 size3 mtight\"><span class=\"mord mathnormal mtight\">a</span></span></span></span><span class=\"vlist-s\">\u{200b}</span></span><span class=\"vlist-r\"><span class=\"vlist\" style=\"height:0.9119em;\"><span></span></span></span></span></span></span><span class=\"mspace\" style=\"margin-right:0.1667em;\"></span><span class=\"mord mathnormal\">b</span><span class=\"mord mathnormal\">d</span><span class=\"mord mathnormal\">x</span></span></span></span></span>";
debug_assert_eq!(expected_output, rendered_content[0]);
}
#[cfg(not(feature = "duktape"))]
mod not_duktape;
gitextract_9swmyqjt/
├── .github/
│ ├── dependabot.yml
│ └── workflows/
│ ├── deploy.yml
│ ├── release-plz.yml
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── Cargo.toml
├── LICENSE
├── README.md
└── src/
├── cfg.rs
├── escape.rs
├── lib.rs
├── main.rs
├── preprocess.rs
├── render/
│ ├── cfg.rs
│ └── preprocess.rs
├── render.rs
├── scan.rs
└── tests/
├── escape.rs
├── mod.rs
├── render/
│ └── not_duktape.rs
└── render.rs
SYMBOL INDEX (80 symbols across 13 files)
FILE: src/cfg.rs
type KatexConfig (line 8) | pub struct KatexConfig {
method build_extra_opts (line 70) | pub fn build_extra_opts(&self) -> ExtraOpts {
method default (line 44) | fn default() -> KatexConfig {
function get_config (line 80) | pub fn get_config(
FILE: src/escape.rs
function escape_math_with_delimiter (line 7) | pub fn escape_math_with_delimiter(item: &str, delimiter: &Delimiter) -> ...
function escape_math (line 19) | pub fn escape_math(item: &str, result: &mut String) {
FILE: src/lib.rs
function init_tracing (line 40) | pub fn init_tracing() {
FILE: src/main.rs
function make_app (line 9) | pub fn make_app() -> Command {
function check_mdbook_version (line 21) | fn check_mdbook_version(version: &str) {
function handle_supports (line 33) | fn handle_supports(pre: &dyn Preprocessor, sub_args: &ArgMatches) -> Res...
function handle_preprocessing (line 48) | fn handle_preprocessing(pre: &dyn Preprocessor) -> Result<()> {
function main (line 57) | fn main() -> Result<()> {
FILE: src/preprocess.rs
function process_all_chapters_prerender (line 6) | pub fn process_all_chapters_prerender(
constant KATEX_HEADER (line 16) | pub const KATEX_HEADER: &str = r#"<link rel="stylesheet" href="https://c...
type ExtraOpts (line 22) | pub struct ExtraOpts {
type KatexProcessor (line 32) | pub struct KatexProcessor;
method name (line 36) | fn name(&self) -> &str {
method run (line 40) | fn run(&self, ctx: &PreprocessorContext, mut book: Book) -> Result<Book> {
function process_all_chapters_escape (line 55) | pub fn process_all_chapters_escape(
function process_chapter_escape (line 68) | pub fn process_chapter_escape(
type Render (line 90) | pub enum Render<'a> {
function get_render_tasks (line 100) | pub fn get_render_tasks<'a>(
FILE: src/render.rs
function render (line 14) | pub fn render(item: &str, opts: Opts, extra_opts: ExtraOpts, display: bo...
FILE: src/render/cfg.rs
method output_type (line 7) | pub fn output_type(&self) -> katex::OutputType {
method build_opts (line 24) | pub fn build_opts<P>(&self, root: P) -> (katex::Opts, katex::Opts)
method build_opts_from_macros (line 35) | pub fn build_opts_from_macros(
function load_macros (line 64) | fn load_macros<P>(root: P, macros_path: &Option<String>) -> HashMap<Stri...
function get_macro_path (line 84) | pub fn get_macro_path<P>(root: P, macros_path: &Option<String>) -> Optio...
function load_as_string (line 94) | pub fn load_as_string(path: &Path) -> String {
FILE: src/render/preprocess.rs
function process_all_chapters_prerender (line 7) | pub fn process_all_chapters_prerender(
function process_chapter_prerender (line 28) | pub fn process_chapter_prerender(
FILE: src/scan.rs
type Delimiter (line 6) | pub struct Delimiter {
method same (line 15) | pub fn same(delimiter: String) -> Self {
method first (line 23) | pub fn first(&self) -> u8 {
method match_left (line 28) | pub fn match_left(&self, to_match: &[u8]) -> bool {
type Event (line 43) | pub enum Event {
type Scan (line 56) | pub struct Scan<'a> {
type Item (line 67) | type Item = Event;
method next (line 69) | fn next(&mut self) -> Option<Self::Item> {
function new (line 81) | pub fn new(
function run (line 97) | pub fn run(&mut self) {
function get_byte (line 102) | fn get_byte(&self) -> Result<u8, ()> {
function inc (line 107) | fn inc(&mut self) {
function process_byte (line 116) | fn process_byte(&mut self) -> Result<(), ()> {
function process_backtick (line 148) | fn process_backtick(&mut self) -> Result<(), ()> {
function process_delimit (line 181) | fn process_delimit(&mut self, inline: bool) -> Result<(), ()> {
FILE: src/tests/escape.rs
function test_render (line 3) | fn test_render(raw_content: &str) -> (String, String) {
function test_render_with_cfg (line 9) | fn test_render_with_cfg(raw_contents: &[&str], cfg: KatexConfig) -> (Str...
function test_escape_without_math (line 20) | fn test_escape_without_math() {
function test_dollar_escape (line 28) | fn test_dollar_escape() {
function test_escape_with_math (line 36) | fn test_escape_with_math() {
function test_escape_underscore (line 45) | fn test_escape_underscore() {
function test_escape_vmatrix (line 54) | fn test_escape_vmatrix() {
FILE: src/tests/mod.rs
function test_name (line 4) | fn test_name() {
function test_support_html (line 11) | fn test_support_html() {
FILE: src/tests/render.rs
function test_render (line 3) | fn test_render(raw_content: &str) -> (String, String) {
function test_render_with_macro (line 8) | fn test_render_with_macro(
function test_render_with_cfg (line 15) | fn test_render_with_cfg(
function test_rendering_without_math (line 39) | fn test_rendering_without_math() {
function test_dollar_escaping (line 47) | fn test_dollar_escaping() {
function test_dollar_escaping_inside_expr (line 55) | fn test_dollar_escaping_inside_expr() {
function test_inline_rendering (line 64) | fn test_inline_rendering() {
function test_display_rendering (line 72) | fn test_display_rendering() {
function test_macros_without_argument (line 80) | fn test_macros_without_argument() {
function test_macros_with_argument (line 90) | fn test_macros_with_argument() {
function test_macro_file_loading (line 100) | fn test_macro_file_loading() {
function test_rendering_table_with_math (line 119) | fn test_rendering_table_with_math() {
function test_rendering_delimiter_in_code_block (line 133) | fn test_rendering_delimiter_in_code_block() {
function test_rendering_delimiter_in_inline_code (line 141) | fn test_rendering_delimiter_in_inline_code() {
function test_rendering_delimiter_in_inline_code_when_inline_delimiter_starts_with_backtick (line 149) | fn test_rendering_delimiter_in_inline_code_when_inline_delimiter_starts_...
function test_rendering_delimiter_in_block_code_when_block_delimiter_starts_with_backtick (line 165) | fn test_rendering_delimiter_in_block_code_when_block_delimiter_starts_wi...
function test_rendering_delimiter_in_inline_code_when_block_delimiter_starts_with_backtick (line 186) | fn test_rendering_delimiter_in_inline_code_when_block_delimiter_starts_w...
function test_invalid_expr_inline (line 202) | fn test_invalid_expr_inline() {
function test_invalid_expr_display (line 211) | fn test_invalid_expr_display() {
function test_escaping_backtick (line 220) | fn test_escaping_backtick() {
function test_include_src (line 228) | fn test_include_src() {
function test_fenced_code (line 248) | fn test_fenced_code() {
function test_inline_rendering_w_custom_delimiter (line 270) | fn test_inline_rendering_w_custom_delimiter() {
FILE: src/tests/render/not_duktape.rs
function test_katex_rendering_vmatrix (line 4) | fn test_katex_rendering_vmatrix() {
function test_rendering_vmatrix (line 14) | fn test_rendering_vmatrix() {
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (82K chars).
[
{
"path": ".github/dependabot.yml",
"chars": 277,
"preview": "# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repositor"
},
{
"path": ".github/workflows/deploy.yml",
"chars": 8986,
"preview": "name: deploy\n\non:\n push:\n tags:\n - \"v*.*.*\"\n release:\n types: [published]\n workflow_dispatch:\n # Manual"
},
{
"path": ".github/workflows/release-plz.yml",
"chars": 765,
"preview": "name: Release-plz\n\npermissions:\n pull-requests: write\n contents: write\n\non:\n push:\n branches:\n - master\n p"
},
{
"path": ".github/workflows/test.yml",
"chars": 2539,
"preview": "name: test-ci\non:\n push:\n pull_request:\n\njobs:\n test-musl:\n runs-on: ubuntu-22.04\n steps:\n - uses: actions"
},
{
"path": ".gitignore",
"chars": 8,
"preview": "target/\n"
},
{
"path": "CHANGELOG.md",
"chars": 1991,
"preview": "# Changelog\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changel"
},
{
"path": "Cargo.toml",
"chars": 1038,
"preview": "[package]\nname = \"mdbook-katex\"\nversion = \"0.10.0-alpha\"\nauthors = [\n \"Lucas Zanini <zanini.lcs@gmail.com>\",\n \"Ste"
},
{
"path": "LICENSE",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2022 Lucas Zanini\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 10310,
"preview": "# mdBook-KaTeX\n\n[](https://crates.io/crates/mdbook-kat"
},
{
"path": "src/cfg.rs",
"chars": 3015,
"preview": "//! Configurations for preprocessing KaTeX.\nuse super::*;\n\n/// Configuration for KaTeX preprocessor,\n/// including optio"
},
{
"path": "src/escape.rs",
"chars": 1169,
"preview": "//! Escaping math blocks to fix KaTeX rendering.\n\nuse super::*;\n\n/// Escape a math block `item` into a delimited string."
},
{
"path": "src/lib.rs",
"chars": 1010,
"preview": "#![deny(missing_docs)]\n//! Preprocess math blocks using KaTeX for mdBook.\nuse std::{\n borrow::Cow,\n collections::H"
},
{
"path": "src/main.rs",
"chars": 2315,
"preview": "use clap::{crate_version, Arg, ArgMatches, Command};\nuse mdbook_katex::{init_tracing, preprocess::KatexProcessor};\nuse m"
},
{
"path": "src/preprocess.rs",
"chars": 4334,
"preview": "//! Preprocessing and escaping with KaTeX.\nuse super::*;\n\n/// When `pre-render` is called but not enabled.\n#[cfg(not(fea"
},
{
"path": "src/render/cfg.rs",
"chars": 3513,
"preview": "//! Extra configurations for pre-rendering KaTeX.\nuse super::*;\n\nimpl KatexConfig {\n /// Configured output type.\n "
},
{
"path": "src/render/preprocess.rs",
"chars": 1400,
"preview": "//! Preprocessing and pre-rendering with KaTeX.\nuse katex::Opts;\n\nuse super::*;\n\n/// Render all Katex equations.\npub fn "
},
{
"path": "src/render.rs",
"chars": 1922,
"preview": "//! Render KaTeX math block to HTML\nuse katex::{Error, Opts};\n\nuse super::*;\n\npub use {cfg::*, preprocess::*};\n\nmod cfg;"
},
{
"path": "src/scan.rs",
"chars": 6361,
"preview": "//! Scan Markdown text and identify math block events.\nuse super::*;\n\n/// A pair of strings are delimiters.\n#[derive(Clo"
},
{
"path": "src/tests/escape.rs",
"chars": 2324,
"preview": "use super::*;\n\nfn test_render(raw_content: &str) -> (String, String) {\n let (stylesheet_header, mut rendered) =\n "
},
{
"path": "src/tests/mod.rs",
"chars": 430,
"preview": "use super::*;\n\n#[test]\nfn test_name() {\n let pre = KatexProcessor;\n let preprocessor: &dyn Preprocessor = ⪯\n "
},
{
"path": "src/tests/render/not_duktape.rs",
"chars": 3800,
"preview": "use super::*;\n\n#[test]\nfn test_katex_rendering_vmatrix() {\n use crate::cfg::KatexConfig;\n\n let math_expr = r\"\\begi"
},
{
"path": "src/tests/render.rs",
"chars": 18007,
"preview": "use super::*;\n\nfn test_render(raw_content: &str) -> (String, String) {\n let (stylesheet_header, mut rendered) = test_"
}
]
About this extraction
This page contains the full source code of the lzanini/mdbook-katex GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (74.8 KB), approximately 20.9k tokens, and a symbol index with 80 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.