Showing preview only (280K chars total). Download the full file or copy to clipboard to get everything.
Repository: korsbo/Latexify.jl
Branch: master
Commit: 8b633ca3bbad
Files: 85
Total size: 256.1 KB
Directory structure:
gitextract_q48ri8b3/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── CompatHelper.yml
│ ├── Invalidations.yml
│ ├── TagBot.yml
│ ├── benchmark.yml
│ └── ci.yml
├── .gitignore
├── LICENSE
├── Project.toml
├── README.md
├── assets/
│ ├── Project.toml
│ └── assets.jl
├── benchmark/
│ ├── Project.toml
│ └── benchmarks.jl
├── docs/
│ ├── .gitignore
│ ├── Project.toml
│ ├── make.jl
│ └── src/
│ ├── arguments.md
│ ├── functions/
│ │ ├── latexalign.md
│ │ ├── latexarray.md
│ │ ├── latexify.md
│ │ ├── latexoperation.md
│ │ └── latexraw.md
│ ├── index.md
│ ├── table_generator.jl
│ └── tutorials/
│ ├── Catalyst.md
│ ├── inner_workings.md
│ ├── latexalign.md
│ ├── latexarray.md
│ ├── latexify.md
│ ├── latexinline.md
│ ├── latextabular.md
│ ├── notebooks.md
│ ├── parameterizedfunctions.md
│ ├── recipes.md
│ └── rendering_latex.md
├── ext/
│ ├── DataFramesExt.jl
│ ├── SparseArraysExt.jl
│ ├── SymEngineExt.jl
│ └── TectonicExt.jl
├── paper/
│ ├── paper.bib
│ └── paper.md
├── src/
│ ├── Latexify.jl
│ ├── default_kwargs.jl
│ ├── error.jl
│ ├── internal_recipes.jl
│ ├── latexalign.jl
│ ├── latexarray.jl
│ ├── latexbracket.jl
│ ├── latexequation.jl
│ ├── latexify_function.jl
│ ├── latexinline.jl
│ ├── latexoperation.jl
│ ├── latexraw.jl
│ ├── latextabular.jl
│ ├── macros.jl
│ ├── md.jl
│ ├── mdtable.jl
│ ├── mdtext.jl
│ ├── numberformatters.jl
│ ├── recipes.jl
│ ├── symbol_translations.jl
│ ├── unicode2latex.jl
│ └── utils.jl
└── test/
├── cdot_test.jl
├── chemical_arrows_test.jl
├── latexalign_test.jl
├── latexarray_test.jl
├── latexbracket_test.jl
├── latexequation_test.jl
├── latexify_test.jl
├── latexinline_test.jl
├── latexraw_test.jl
├── latextabular_test.jl
├── macros.jl
├── manual_test.jl
├── mdtable_test.jl
├── numberformatters_test.jl
├── plugins/
│ ├── DataFrames_test.jl
│ ├── SparseArrays_test.jl
│ └── SymEngine_test.jl
├── recipe_test.jl
├── runtests.jl
├── unicode2latex.jl
└── utils_test.jl
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
github: korsbo
================================================
FILE: .github/workflows/CompatHelper.yml
================================================
name: CompatHelper
on:
schedule:
- cron: 0 0 * * *
workflow_dispatch:
jobs:
CompatHelper:
runs-on: ubuntu-latest
steps:
- name: "Install CompatHelper"
run: |
import Pkg
name = "CompatHelper"
uuid = "aa819f21-2bde-4658-8897-bab36330d9b7"
version = "3"
Pkg.add(; name, uuid, version)
shell: julia --color=yes {0}
- name: "Run CompatHelper"
run: |
import CompatHelper
CompatHelper.main()
shell: julia --color=yes {0}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }}
================================================
FILE: .github/workflows/Invalidations.yml
================================================
name: Invalidations
on:
pull_request:
concurrency:
# Skip intermediate builds: always.
# Cancel intermediate builds: always.
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
evaluate:
# Only run on PRs to the default branch.
# In the PR trigger above branches can be specified only explicitly whereas this check should work for master, main, or any other default branch
if: github.base_ref == github.event.repository.default_branch
runs-on: ubuntu-latest
steps:
- uses: julia-actions/setup-julia@v1
with:
version: '1'
- uses: actions/checkout@v3
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-invalidations@v1
id: invs_pr
- uses: actions/checkout@v3
with:
ref: ${{ github.event.repository.default_branch }}
- uses: julia-actions/julia-buildpkg@v1
- uses: julia-actions/julia-invalidations@v1
id: invs_default
- name: Report invalidation counts
run: |
echo "Invalidations on default branch: ${{ steps.invs_default.outputs.total }} (${{ steps.invs_default.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" >> $GITHUB_STEP_SUMMARY
- name: Check if the PR does increase number of invalidations
if: steps.invs_pr.outputs.total > steps.invs_default.outputs.total
run: exit 1
================================================
FILE: .github/workflows/TagBot.yml
================================================
name: TagBot
on:
issue_comment: # THIS BIT IS NEW
types:
- created
workflow_dispatch:
jobs:
TagBot:
if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot'
runs-on: ubuntu-latest
steps:
- uses: JuliaRegistries/TagBot@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
ssh: ${{ secrets.DOCUMENTER_KEY }}
================================================
FILE: .github/workflows/benchmark.yml
================================================
name: Run benchmarks
on:
pull_request:
types: [labeled, opened, synchronize, reopened]
jobs:
Benchmark:
runs-on: ubuntu-latest
if: contains(github.event.pull_request.labels.*.name, 'run benchmark')
steps:
- uses: actions/checkout@v2
- uses: julia-actions/setup-julia@latest
with:
version: 1
- uses: julia-actions/julia-buildpkg@latest
- name: Install dependencies
run: julia -e 'using Pkg; pkg"add PkgBenchmark BenchmarkCI@0.1"'
- name: Run benchmarks
run: julia -e 'using BenchmarkCI; BenchmarkCI.judge()'
- name: Post results
run: julia -e 'using BenchmarkCI; BenchmarkCI.postjudge()'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Print results
run: julia -e 'using BenchmarkCI; BenchmarkCI.displayjudgement()'
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
branches:
- master
push:
branches:
- master
tags: '*'
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
test:
name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }}
continue-on-error: ${{ matrix.experimental }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
version:
- '1.6' # lowest julia declared compat in `Project.toml`
- '1'
experimental:
- false
os: [ubuntu-latest]
arch: [x64]
include: # spare windows/macos CI credits
- os: windows-latest
experimental: false
version: '1'
arch: x64
- os: macOS-latest
experimental: false
version: '1'
arch: x64
- os: ubuntu-latest
experimental: true
version: '~1.11.0-0'
arch: x64
- os: ubuntu-latest
experimental: true
version: 'nightly'
arch: x64
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@latest
with:
version: ${{ matrix.version }}
arch: ${{ matrix.arch }}
- uses: julia-actions/cache@v1
- name: install latex dependencies
if: startsWith(matrix.os,'ubuntu')
run: |
sudo apt-get -y update
sudo apt-get -y install latexmk texlive-{luatex,latex-extra}
sudo fc-cache -vr
- uses: julia-actions/julia-buildpkg@latest
- uses: julia-actions/julia-runtest@latest
- uses: julia-actions/julia-processcoverage@latest
- uses: codecov/codecov-action@v3
with:
file: lcov.info
docs:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: julia-actions/setup-julia@latest
with:
version: '1'
- run: |
julia --project=docs -e '
using Pkg
Pkg.develop(PackageSpec(path=pwd()))
Pkg.instantiate()'
# - run: |
# julia --project=docs -e '
# using Documenter: doctest
# using Latexify
# doctest(Latexify)'
- run: julia --project=docs docs/make.jl
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }}
================================================
FILE: .gitignore
================================================
*.jl.cov
*.jl.*.cov
*.jl.mem
deps/deps.jl
*Manifest.toml
docs/build
docs/site
*.DS_Store
/.benchmarkci
/benchmark/*.json
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2017 Niklas Korsbo
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: Project.toml
================================================
name = "Latexify"
uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
authors = ["Niklas Korsbo <niklas.korsbo@gmail.com>"]
repo = "https://github.com/korsbo/Latexify.jl.git"
version = "0.16.10"
[deps]
Format = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
Ghostscript_jll = "61579ee1-b43e-5ca0-a5da-69d92c66a64b"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a"
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
Requires = "ae029012-a4dd-5104-9daa-d747884805df"
[weakdeps]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8"
tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5"
[extensions]
DataFramesExt = "DataFrames"
SparseArraysExt = "SparseArrays"
SymEngineExt = "SymEngine"
TectonicExt = "tectonic_jll"
[compat]
DataFrames = "1"
Format = "1.3"
Ghostscript_jll = "9"
LaTeXStrings = "0.3, 1"
MacroTools = "0.4 - 0.5"
OrderedCollections = "1"
Requires = "0.5, 1"
SparseArrays = "1.6"
SymEngine = "0.11, 0.12, 0.13"
julia = "1.6"
tectonic_jll = "0.15"
[extras]
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5"
[targets]
test = ["DataFrames", "OffsetArrays", "SymEngine", "SparseArrays", "tectonic_jll", "Test"]
================================================
FILE: README.md
================================================
[](https://korsbo.github.io/Latexify.jl/stable)
[](https://korsbo.github.io/Latexify.jl/latest)
[](https://codecov.io/gh/korsbo/Latexify.jl)
[](https://coveralls.io/github/korsbo/Latexify.jl)
[](http://juliapkgstats.com/pkg/Latexify)
# Latexify.jl
This is a package for generating LaTeX maths from julia objects.
This package utilises Julias
[homoiconicity](https://en.wikipedia.org/wiki/Homoiconicity) to convert
expressions to LaTeX-formatted strings. Latexify.jl supplies functionalities
for converting a range of different Julia objects, including:
- Expressions,
- Strings,
- Numbers (including rationals and complex),
- Symbolic expressions from SymEngine.jl,
- ParameterizedFunctions and ReactionNetworks from DifferentialEquations.jl,
- Other types for which a user recipe has been defined
as well as arrays or dicts of supported types.
## Recipes
To extend Latexify to work with your own type, you define a recipe using the
`@latexrecipe` macro. See the documentation.
### Examples
#### latexifying expressions
```julia
using Latexify
ex = :(x/(y+x)^2)
latexify(ex)
```
This generates a LaTeXString (from
[LaTeXStrings.jl](https://github.com/stevengj/LaTeXStrings.jl)) which, when
printed looks like:
```LaTeX
$\frac{x}{\left( y + x \right)^{2}}$
```
And when this LaTeXString is displayed in an environment which supports the
tex/latex MIME type (Jupyter and Pluto notebooks, Jupyterlab and Hydrogen for
Atom) it will automatically render as:

#### latexifying other things
Latexify.jl is equipped to convert a whole range of types to latex formatted
maths. This includes primitive types such as `Symbol`s and `Complex`, but also
of containers such as `Array`s and `Dict`s.
```julia
using Latexify
print(latexify("x+y/(b-2)^2"))
```
outputs:
```LaTeX
$x + \frac{y}{\left( b - 2 \right)^{2}}$
```
```julia
arr = ["x/y" 3//7 2+3im; 1 :P_x :(gamma(3))]
latexify(arr)
```

The GitHub website does not really support rendering of equations in the README
file, so I therefore refer you to the documentation for more info/examples.
#### latexifying custom types
You can add support for a type via `@latexrecipe`
```julia
using Latexify
struct Ket{T}
x::T
end
@latexrecipe function f(x::Ket)
return Expr(:latexifymerge, "\\left|", x.x, "\\right>")
end
latexify(:($(Ket(:a)) + $(Ket(:b))))
```

### Use with DifferentialEquations.jl
The [DifferentialEquations.jl](http://docs.juliadiffeq.org/stable/index.html)
suite has some nifty tools for generating differential equations.
One of them is
[ParameterizedFunctions](https://github.com/JuliaDiffEq/ParameterizedFunctions.jl)
which allows you to type in an ODE in something which looks very much like just
plain mathematics.
The ability to latexify such ODEs is pretty much what lured me to create this
package.
```julia
using ParameterizedFunctions
using Latexify
f = @ode_def positiveFeedback begin
dx = v*y^n/(k^n+y^n) - x
dy = x/(k_2+x) - y
end v n k k_2
latexify(f)
```
outputs:

[Catalyst.jl](https://github.com/SciML/Catalyst.jl)
provides another cool domain-specific language which allows you to generate
equations using a chemical arrow notation.
```julia
using Catalyst
using Latexify
rn = @reaction_network begin
(r_bind, r_unbind), A + B ↔ C
hill(C, v, k, n), 0 --> X
d_x, X --> 0
end
latexify(rn)
```

Or you can output the arrow notation directly to latex:
```julia
latexify(rn; env=:arrow)
```

There are more stuff that you can do, but for that I will refer you to the
[docs](https://korsbo.github.io/Latexify.jl/stable).
### Display equations in a terminal
One can use [`ImageInTerminal`](https://github.com/JuliaImages/ImageInTerminal.jl) with the [`Sixel`](https://github.com/JuliaIO/Sixel.jl) backend in order to display rendered `latexify`ed [equations](https://github.com/JuliaImages/ImageInTerminal.jl#display-equations).
## Convenience functions
- `copy_to_clipboard(::Bool)`, toggle automatic copying of the resulting LaTeX
code to the clipboard (default is false).
- `auto_display(::Bool)` toggles automatic display of your output, even if it
is not the last command to have run.
- `set_default(; kwargs...)`, set your own default kwargs for your Julia
session. This is not to be used within a package since the effect is global.
- `reset_default(; kwargs...)`, reset the changes you made with the above
command.
- `get_default(; kwargs...)`, view the changes you have made to the default
kwargs.
## Installation
This package is registered in the Julia registry, so to install it you can just
run
```julia
Pkg.add("Latexify")
```
## Further information
For further information see the
[docs](https://korsbo.github.io/Latexify.jl/stable).
## Contributing
I would be happy to receive feedback, suggestions, and help with improving this
package. Please feel free to open an issue or a PR.
If you want to add support for types defined in another package, primarily
create a PR in that package with a recipe. Latexify.jl is not intended to be a
collection of recipes for different types. The exceptions are the few types
which were included before the recipe system was finished. If the other package
is hesitant to pull in Latexify as a dependency, you can either use
Requires.jl, or create a separate glue package. If you do add support for
another package, please help update the list below:
### Supported types and packages
* Many base types
* LaTeXStrings.jl
* DifferentialEquations.jl
* DiffEqBiological.jl
* ParametrizedFunctions.jl
* DataFrames.jl
* Symbolics.jl
* Unitful.jl (via UnitfulLatexify.jl)
And more ...
================================================
FILE: assets/Project.toml
================================================
[deps]
Catalyst = "479239e8-5488-4da2-87a7-35f2df7eef83"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
ParameterizedFunctions = "65888b18-ceab-5e60-b2b9-181511a3b968"
================================================
FILE: assets/assets.jl
================================================
# Generate the assets (pngs for README)
using Latexify, LaTeXStrings, ParameterizedFunctions, Catalyst
struct Ket{T}
x::T
end
@latexrecipe function f(x::Ket)
return Expr(:latexifymerge, "\\left|", x.x, "\\right>")
end
things = [
"demo_fraction" => latexify(:(x / (y + x)^2)),
"demo_matrix" => latexify(["x/y" 3//7 2+3im; 1 :P_x :(gamma(3))]; env=:inline),
"demo_ket" => latexify(:($(Ket(:a)) + $(Ket(:b)))),
"ode_positive_feedback" => latexify(@ode_def positiveFeedback begin
dx = v * y^n / (k^n + y^n) - x
dy = x / (k_2 + x) - y
end v n k k_2),
"demo_rn" => latexify(@reaction_network demoNetwork begin
(r_bind, r_unbind), A + B ↔ C
Hill(C, v, k, n), 0 --> X
d_x, X --> 0
end; form=:ode),
"demo_rn_arrow" => latexify(@reaction_network demoNetwork begin
(r_bind, r_unbind), A + B ↔ C
Hill(C, v, k, n), 0 --> X
d_x, X --> 0
end),
]
cd("$(pkgdir(Latexify))/assets") do
for (name, s) in things
println(name)
render(s, MIME"image/png"(); name=name, debug=false, callshow=false, open=false, packages=["mhchem", "amssymb"])
run(`convert $name.png -flatten $name.png`)
end
end
================================================
FILE: benchmark/Project.toml
================================================
[deps]
BenchmarkCI = "20533458-34a3-403d-a444-e18f38190b5b"
BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
PkgBenchmark = "32113eaa-f34f-5b0d-bd6c-c81e245fc73d"
================================================
FILE: benchmark/benchmarks.jl
================================================
using BenchmarkTools
using Latexify
const SUITE = BenchmarkGroup()
chars = vcat(
'A':'Z',
'a':'z',
'Α':'Ρ', # skip unprintable char (no \Varsigma)
'Σ':'Ω',
'α':'ω',
'𝕒':'𝕫',
'𝐴':'𝑍',
)
SUITE["unicode"] = @benchmarkable latexify(string(c)) setup = (c = rand(chars))
struct AType
x
end
struct BType
a
end
@latexrecipe function f(a::AType)
return :($(a.x) + 1)
end
@latexrecipe function f(b::BType)
return :($(b.a)/2)
end
SUITE["user types"] = @benchmarkable latexify(BType(AType(x))) setup = (x=rand())
SUITE["expression"] = @benchmarkable latexify(:(2x + 3 ∈ 25/4 + y - z^2^4α ? 8 : 9))
================================================
FILE: docs/.gitignore
================================================
build/
site/
================================================
FILE: docs/Project.toml
================================================
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
Format = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8"
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
Latexify = "23fbe1c1-3f47-55db-b15f-69d7ec21a316"
[compat]
Documenter = "1"
Format = "1"
LaTeXStrings = "1"
Latexify = "0.16"
================================================
FILE: docs/make.jl
================================================
using Documenter
using Latexify
using LaTeXStrings
Base.show(io::IO, ::MIME"text/html", l::LaTeXString) = l.s
makedocs(
modules = [Latexify],
format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true", mathengine = MathJax3()),
sitename = "Latexify.jl",
pages = [
"index.md",
# "Functions" => [
# "tutorials/latexify.md",
# "tutorials/latexinline.md",
# "tutorials/latexalign.md",
# "tutorials/latexarray.md",
# "tutorials/latextabular.md"
# ],
"tutorials/recipes.md",
"Use with other packages" => [
"tutorials/parameterizedfunctions.md",
"tutorials/Catalyst.md"
],
"tutorials/notebooks.md",
"arguments.md",
"tutorials/inner_workings.md",
],
doctest = false,
checkdocs = :exports,
warnonly = :missing_docs
)
deploydocs(
#deps = Deps.pip("mkdocs", "python-markdown-math"),
repo = "github.com/korsbo/Latexify.jl.git",
target = "build",
# make = nothing,
# deps = nothing,
)
# Documenter can also automatically deploy documentation to gh-pages.
# See "Hosting Documentation" and deploydocs() in the Documenter manual
# for more information.
#=deploydocs(
repo = "<repository url>"
)=#
================================================
FILE: docs/src/arguments.md
================================================
# List of possible arguments
## Align
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :align in arg.env]
latexify(args, env=:mdtable)
```
## Equation
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :equation in arg.env]
latexify(args, env=:mdtable)
```
## Array
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :array in arg.env]
latexify(args, env=:mdtable)
```
## Tabular
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :tabular in arg.env]
latexify(args, env=:mdtable)
```
## Markdown Table
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :mdtable in arg.env]
latexify(args, env=:mdtable)
```
## Inline and raw
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :raw in arg.env || :inline in arg.env]
latexify(args, env=:mdtable)
```
## Chemical arrow notation
Available with `ReactionNetwork`s from `Catalyst`.
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :arrow in arg.env]
latexify(args, env=:mdtable, types=false)
```
================================================
FILE: docs/src/functions/latexalign.md
================================================
# `latexalign`
```@meta
DocTestSetup = quote
using Latexify
using DifferentialEquations
end
```
```@docs
latexalign
```
```@meta
DocTestSetup = nothing
```
================================================
FILE: docs/src/functions/latexarray.md
================================================
# `latexarray`
```@meta
DocTestSetup = quote
using Latexify
using DifferentialEquations
end
```
```@docs
latexarray
```
```@meta
DocTestSetup = nothing
```
================================================
FILE: docs/src/functions/latexify.md
================================================
# `latexify`
```@meta
DocTestSetup = quote
using Latexify
end
```
```@docs
latexify
```
```@meta
DocTestSetup = nothing
```
================================================
FILE: docs/src/functions/latexoperation.md
================================================
# `latexoperation`
This function is not exported.
```@meta
DocTestSetup = quote
using Latexify
using DifferentialEquations
end
```
```@docs
Latexify.latexoperation
```
```@meta
DocTestSetup = nothing
```
================================================
FILE: docs/src/functions/latexraw.md
================================================
# `latexraw`
Formats the input for ``\LaTeX`` without surrounding it with an environment.
```@docs
latexraw
```
================================================
FILE: docs/src/index.md
================================================
# Latexify.jl
[Latexify.jl](https://github.com/korsbo/Latexify.jl) is a package which supplies functions for producing ``\LaTeX``
formatted strings from Julia objects. The package allows for latexification of a many different kinds of Julia object
and it can output several different ``\LaTeX`` or Markdown environments.
A small teaser:
```@example main
using Latexify
copy_to_clipboard(false) # hide
Latexify.set_default(; starred=true)
m = [2//3 "e^(-c*t)" 1+3im; :(x/(x+k_1)) "gamma(n)" :(log10(x))]
latexify(m)
```
## Supported input
This package supplies functionality for latexifying objects of the following types:
- Expressions,
- Strings,
- Numbers (including rational and complex),
- Missing,
- Symbols,
- Symbolic expressions from SymEngine.jl,
- DataFrame from DataFrames.jl,
- Any shape of array containing a mix of any of the above types,
- ParameterizedFunctions from DifferentialEquations.jl,
- ReactionNetworks from DifferentialEquations.jl
Example:
```@example main
str = "x/(2*k_1+x^2)"
latexify(str)
```
## Supported output
Latexify has support for generating a range of different ``\LaTeX`` environments.
The main function of the package, `latexify()`, automatically picks a suitable output environment based on the type(s) of the input.
However, you can override this by passing the keyword argument `env = `. The following environments are available:
| environment | `env= ` | description
| ------ | ---- | --- |
| no env | `:raw` | Latexifies an object and returns a ``\LaTeX`` formatted string. If the input is an array it will be recursed and all its elements latexified. This function does not surround the resulting string in any ``\LaTeX`` environments.
| Inline | `:inline` | latexify the input and surround it with $$ for inline rendering. |
| Align | `:align` | Latexifies input and surrounds it with an align environment. Useful for systems of equations and such fun stuff. |
| Equation | `:equation` or `:eq` | Latexifies input and surrounds it with an equation environment. |
| Array | `:array` | Latexify the elements of an Array or a Dict and output them in a ``\LaTeX`` array. |
| Tabular | `:table` or `:tabular` | Latexify the elements of an array and output a tabular environment. Note that tabular is not supported by MathJax and will therefore not be rendered in Jupyter, etc.|
| Markdown Table | `:mdtable` | Output a Markdown table. This will be rendered nicely by Jupyter, etc. |
| Markdown Text | `:mdtext` | Output and render any string which can be parsed into Markdown. This is really nothing but a call to `Base.Markdown.parse()`, but it does the trick. Useful for rendering bullet lists and such things. |
| Chemical arrow notation | `:chem`, `:chemical`, `:arrow` or `:arrows` | Latexify an AbstractReactionNetwork to ``\LaTeX`` formatted chemical arrow notation using [mhchem](https://ctan.org/pkg/mhchem?lang=en).
## Modifying the output
Some of the different outputs can be modified using keyword arguments. You can for example transpose an array with `transpose=true` or specify a header of a table or mdtable with `header=[]`. For more options, see the [List of possible arguments](@ref).
## Printing vs displaying
`latexify()` returns a LaTeXString. Using `display()` on such a string will try to render it.
```@example main
latexify("x/y") |> display
```
$\frac{x}{y}$
Using `print()` will output text which is formatted for latex.
```@example main
latexify("x/y") |> print
```
## Number formatting
You can control the formatting of numbers by passing any of the following to the `fmt` keyword:
- a [printf-style](https://en.wikipedia.org/wiki/Printf_format_string) formatting string, for example `fmt = "%.2e"`.
- a single argument function, for example `fmt = x -> round(x, sigdigits=2)`.
- a formatter supplied by Latexify.jl, for example `fmt = FancyNumberFormatter(2)` (thanks to @simeonschaub). You can pass any of these formatters an integer argument which specifies how many significant digits you want.
- `FancyNumberFormatter()` replaces the exponent notation, from `1.2e+3` to `1.2 \cdot 10^3`.
- `StyledNumberFormatter()` replaces the exponent notation, from `1.2e+3` to `1.2 \mathrm{e} 3`.
- `SiunitxNumberFormatter()` uses the `siunitx` package's `\num`, so all the formatting is offloaded on the `\LaTeX` engine. Formatting arguments can be supplied as a string to the keyword argument `format_options`. If your `siunitx` installation is version 2 or older, use the keyword argument `version=2` to replace `\num` by `\si`. A boolean argument `simple` can be used to control syntax for units. These two latter options do not change output for unitless numbers.
Examples:
```@example main
latexify(12345.678; fmt="%.1e")
```
```@example main
latexify([12893.1 1.328e2; "x/y" 7832//2378]; fmt=FancyNumberFormatter(3))
```
```math
\begin{equation}
\left[
\begin{array}{cc}
1.29 \cdot 10^{4} & 133 \\
\frac{x}{y} & \frac{3916}{1189} \\
\end{array}
\right]
\end{equation}
```
```@example main
using Format
latexify([12893.1 1.328e2]; fmt=x->format(round(x, sigdigits=2), autoscale=:metric))
```
```@example main
str = latexify(12345.678; fmt=SiunitxNumberFormatter(format_options="round-mode=places,round-precision=1", version=3))
replace(string(str), "\$"=>"`") # hide
```
## Automatic copying to clipboard
The strings that you would see when using print on any of the above functions can be automatically copied to the clipboard if you so specify.
Since I do not wish to mess with your clipboard without you knowing it, this feature must be activated by you.
To do so, run
```julia
copy_to_clipboard(true)
```
To once again disable the feature, pass `false` to the same function.
The copying to the clipboard will now occur at every call to a Latexify.jl function, regardless of how you chose to display the output.
## Automatic displaying of result
You can toggle whether the result should be automatically displayed. Instead of
```julia
latexify("x/y") |> display
## or
display( latexify("x/y") )
```
one can toggle automatic display by:
```julia
auto_display(true)
```
after which all calls to `latexify` will automatically be displayed. This can be rather convenient, but it can also cause a lot of unwanted printouts if you are using `latexify` in any form of loop.
You can turn off this behaviour again by passing `false` to the same function.
## Setting your own defaults
If you get tired of specifying the same keyword argument over and over in a session, you can just reset its default:
```julia
set_default(fmt = "%.2f", convert_unicode = false)
```
Note that this changes Latexify.jl from within and should therefore only be used in your own Julia sessions (do not call this from within your packages).
The calls are additive so that a new call with
```julia
set_default(mult_symbol = "")
```
will not cancel out the changes we just made to `fmt` and `convert_unicode`.
To view your changes, use
```julia
get_default()
```
and to reset your changes, use
```julia
reset_default()
```
## Macros
Three macros are exported.
- `@latexify` simply latexifies the expression that you provide to it, similar to `latexify(:(...))`.
- `@latexrun` both executes and latexifies the given expression.
- `@latexdefine` executes the expression, and latexifies the expression together with the its value
They can for example be useful for latexifying simple mathsy functions like
```julia
julia> lstr = @latexrun f(x; y=2) = x/y
L"$f\left( x; y = 2 \right) = \frac{x}{y}$"
julia> f(1)
0.5
```
```julia
julia> @latexdefine x = 1/2
L"$x = \frac{1}{2} = 0.5
julia> x
0.5
```
The arguments to the macro can be interpolated with `$` to use the actual
value, instead of the representation:
```julia
julia> @latexify x = abs2(-3)
L"$x = \left|-3\right|^{2}$"
julia> @latexify x = $(abs2(-3))
L"$x = 9$"
```
Keyword arguments can be supplied after these macros:
```julia
julia> @latexdefine x env=:equation
L"\begin{equation}
x = 0.5
\end{equation}
"
```
A special keyword `post` can be supplied to `@latexdefine`, which is a
function that will be called on the final right hand sign before
latexification. This is merely formatting and will not affect any assignments.
```julia
julia> @latexdefine x=π post=round
L"$x = \pi = 3.0$"
julia> @latexdefine x
L"$x = \pi$"
```
## External rendering
While LaTeXStrings already render nicely in many IDEs or in Jupyter, they do not render in the REPL. Therefore, we provide a function `render(str)` which generates a standalone PDF using LuaLaTeX and opens that file in your default PDF viewer.
I have found the following syntax pretty useful:
```julia
latexify(:(x/y)) |> render
```
Alternatively, `render(str, mime)` can also be used to generate and display DVI, PNG and SVG files, which might be useful for other purposes:
```julia
latexify(:(x/y)) |> s -> render(s, MIME("image/png"))
```
PNG output defaults to using [ghostscript](https://www.ghostscript.com), which is available by default thanks to `Ghostscript_jll`.
[dvipng](http://www.nongnu.org/dvipng) can also be used by passing the keyword `convert = :dvipng` to `render`. However, this requires a working installation of dvipng.
SVG output relies on [dvisvgm](https://dvisvgm.de) or alternatively on [pdf2svg](https://github.com/dawbarton/pdf2svg).
If your code requires specific packages or document classes to render correctly, you can supply those as keyword arguments:
```julia
L"\qty{1.25}{nm}" |> render(s, MIME("image/png"); documentclass="article", packages=("microtype", ("siunitx", exponent-product="\cdot")))
```
The arguments to these are either strings, or tuples of strings where the first
one is the name of the package or class, and any further are optional arguments.
One can use `Latexify` together with `ImageInTerminal` to render equations in a [sixel compatible](https://github.com/JuliaIO/Sixel.jl#terminals-that-support-sixel) terminal, where the size of the sixel encoded image can be controlled using `dpi`:
```julia
using ImageInTerminal, Latexify
latexify(:(iħ * (∂Ψ(𝐫, t) / ∂t) = -ħ^2 / 2m * ΔΨ(𝐫, t) + V * Ψ(𝐫, t))) |> s -> render(s, dpi=200)
```
### Tectonic
The `tectonic_jll` package can be used as a lightweight compiler for CI and similar setups.
An extension to `Latexify` makes `render` (to pdf) automatically use `tectonic_jll` if both packages are loaded.
To still render using the default compiler, use `render(...; use_tectonic=false)`.
### Render command-line args
For fine-tuning or setting other command-line arguments to a render pipeline, you can pass any of the following keyword arguments to `render`, some of which have default values.
Since these are [interpolated into shell commands](https://julialang.org/blog/2017/10/command-interpolation-for-dummies/), note that you should surround with back-ticks `\`` rather than quotes.
- ```lualatex_flags=`` ``` (for default compilation to PDF)
- ```tectonic_flags=`` ``` (for tectonic_jll compilation to PDF)
- ```dvilualatex_flags=`` ``` (for compilation to DVI)
- ```ghostscript_flags=`-sDEVICE=pngalpha -dTextAlphaBits=4 -r$dpi` ``` (for generating PNG from PDF)
- ```dvipng_flags=`-bg Transparent -D $dpi -T tight` ``` (for dvipng, for generating PNG from DVI)
- ```pdf2svg_flags=`` ``` (for generating SVG from PDF)
- ```dvisvgm_flags=`` ``` (for generating SVG from DVI)
- ```dvilualatex_flags=`` ``` (for generating DVI)
## Legacy support
Latexify.jl has stopped supporting Julia versions older than 0.7. This does not mean that you cannot use Latexify with earlier versions, just that these will not get new features. Latexify.jl's release v0.4.1 was the last which supported Julia 0.6. Choose that release in the dropdown menu if you want to see that documentation.
================================================
FILE: docs/src/table_generator.jl
================================================
#=
In the documents, there are tables of what keyword arguments can be passed
to latexify for different outputs or inputs. This file contains that information
as well as the means to generate tables for the documentation.
This exists so that I do not have to repeat myself and to type the same
information into multiple places (that way lies madness!). With this code, I
can simply filter the information according to some criterion and automatically
have it inserted in the docs.
=#
using Latexify
struct KeywordArgument
kw::Symbol
env::Array{Symbol}
values::String
default::String
description::String
types::Array{Symbol}
end
# KeywordArgument(:template, [:array], "`Bool`", "`false`", "description", [:Any]),
keyword_arguments = [
KeywordArgument(:starred, [:align, :array, :arrow, :equation], "`Bool`", "`false`", "Star the environment to prevent equation numbering.", [:Any]),
KeywordArgument(:separator, [:align], "`String`", "`\" &= \"`", "Specify how to separate the left hand side and the right.", [:Any]),
KeywordArgument(:transpose, [:array, :tabular, :mdtable], "`Bool`", "`true`", "Flip rows for columns.", [:Any]),
KeywordArgument(:double_linebreak, [:array, :align, :arrow], "`Bool`", "`false`", "Add an extra `\\\\` to the end of the line.", [:Any]),
KeywordArgument(:bracket, [:align], "`Bool`", "`false`", "Surround variables with square brackets.", [:ParameterizedFunction, :ReactionNetwork]),
KeywordArgument(:noise, [:align], "`Bool`", "`false`", "Display the noise function instead of the deterministic one.", [:ReactionNetwork]),
KeywordArgument(:adjustment, [:tabular, :array, :mdtable], "`:c` for centered, `:l` for left, `:r` for right, or a vector with one such symbol per column.", "`:c`", "Set the adjustment of text within the table cells.", [:Any]),
KeywordArgument(:expand, [:arrow, :align], "`Bool`", "`true`", "Expand functions such as `hill(x, v, k, n)` to their mathematical expression.", [:ReactionNetwork]),
KeywordArgument(:mathjax, [:arrow], "`Bool`", "`true`", "Add `\\require{mhchem}` to tell MathJax to load the required module.", [:ReactionNetwork]),
KeywordArgument(:latex, [:mdtable, :tabular], "`Bool`", "`true`", "Toggle latexification of the table elements.", [:Any]),
KeywordArgument(:head, [:mdtable, :tabular], "`Array`", "`[]`", "Add a header to the table. It will error if it is not of the right length (unless empty). ", [:Any]),
KeywordArgument(:side, [:mdtable, :tabular], "`Array`", "`[]`", "Add a leftmost column to the table. It will error if it is not of the right length (unless empty). ", [:Any]),
KeywordArgument(:fmt, [:mdtable, :tabular, :align, :array, :raw, :inline], "format string", "`\"\"`", "Format number output in accordance with Printf. Example: \"%.2e\"", [:Any]),
KeywordArgument(:imaginary_unit, [:mdtable, :tabular, :align, :array, :raw, :inline], "`String`", "`\"\\\\mathit{i}\"`", "The symbol to use to represent the imaginary unit", [:Any]),
KeywordArgument(:escape_underscores, [:mdtable, :mdtext], "`Bool`", "`false`", "Prevent underscores from being interpreted as formatting.", [:Any]),
KeywordArgument(:convert_unicode, [:mdtable, :tabular, :align, :array, :raw, :inline], "`Bool`", "`true`", "Convert unicode characters to latex commands, for example `α` to `\\alpha`", [:Any]),
KeywordArgument(:mult_symbol, [:mdtable, :tabular, :align, :array, :raw, :inline], "`String`", "`\"\\\\cdot\"`", "Specify the symbol to use for the multiplication operator (`\"\"` for juxtaposition).", [:Any]),
KeywordArgument(:symbolic, [:align], "`Bool`", "`false`", "Use symbolic calculations to clean up the expression.", [:ReactionNetwork]),
KeywordArgument(:clean, [:align], "`Bool`", "`false`", "Clean out `1*` terms. Only useful for Catalyst (then named DiffEqBiological) versions 3.4 or below.", [:ReactionNetwork]),
KeywordArgument(:rows, [:align], "Iterable or symol", ":all", "Which rows to include in the output.", [:Any]),
KeywordArgument(:booktabs, [:tabular], "`Bool`", "`false`", "Add top, mid and bottom booktabs rule", [:Any]),
KeywordArgument(:index, [:mdtable, :tabular, :align, :array, :raw, :inline], "`Symb`", "`:bracket`", "Represent index specification with `:bracket` (`u[1]`) or `:subscript` (`u_1`). ", [:Any]),
KeywordArgument(:snakecase, [:mdtable, :tabular, :align, :array, :raw, :inline], "`Bool`", "`false`", "Treat underscores as literal underscores (if not, treat first underscore as subscript).", [:Any]),
KeywordArgument(:safescripts, [:mdtable, :tabular, :align, :array, :raw, :inline], "`Bool`", "`false`", "Put scripts inside brackets (`a{_b}`), sometimes making them uglier, but making alternating scripts possible.", [:Any]),
KeywordArgument(:arraystyle, [:array], "`Symbol`, `String`, `NTuple{3, String}`", "`:square`", "How to style (brackets around) arrays. `Symbol`s correspond to predefined styles: `:square`, `:round`, `:curly`, `:bmatrix`, `:pmatrix`. A string will be used as an environment, with no further brackets (e.g. `\"vmatrix\"`). Tuples should be `(<starting bracket>, <ending bracket>, <environment>)`, for instance `:square` corresponds to `(\"\\n\\\\left[\", \"\\\\right]\\n\", \"array\")`.", [:Any]),
# KeywordArgument(:template, [:array], "`Bool`", "`false`", "description", [:Any]),
]
@latexrecipe function f(list::Array{KeywordArgument}; types=true)
isempty(list) && return nothing
sort!(list, by=x->x.kw)
keys = ["`:$(x.kw)`" for x in list]
# values = [join(["$i" for i in x.values], ", ") for x in list]
applicable_types = [join(["`$i`" for i in x.types], ", ") for x in list]
values = [x.values for x in list]
defaults = [x.default for x in list]
descriptions = [x.description for x in list]
latex --> false
env := :mdtable
if any(x->x.types != [:Any], list) && types
head --> ["Keyword", "Values", "Default", "Applicable types", "Description"]
return hcat(keys, values, defaults, applicable_types, descriptions)
else
head --> ["Keyword", "Values", "Default", "Description"]
return hcat(keys, values, defaults, descriptions)
end
end
================================================
FILE: docs/src/tutorials/Catalyst.md
================================================
# Use with @reaction_network from Catalyst.jl.
Latexify.jl has methods for dealing with the `ReactionNetwork` models generated by Catalyst.jl. More information regarding these can be found [here](https://github.com/SciML/Catalyst.jl). The latexify end of things are pretty simple: feed a reaction network to `latexify()` and let it do its magic.
```julia
using Catalyst
using Latexify
copy_to_clipboard(true)
hill2(x, v, k) = v*x^2/(k^2 + x^2)
rn = @reaction_network begin
hill2(y, v_x, k_x), 0 --> x
p_y, 0 --> y
(d_x, d_y), (x, y) --> 0
(r_b, r_u), x ↔ y
end
latexify(rn; form=:ode)
```
```math
\begin{align}
\frac{dx}{dt} &= \frac{v_{x} \cdot y^{2}}{k_{x}^{2} + y^{2}} - d_{x} \cdot x - r_{b} \cdot x + r_{u} \cdot y \\
\frac{dy}{dt} &= p_{y} - d_{y} \cdot y + r_{b} \cdot x - r_{u} \cdot y \\
\end{align}
```
Alternatively, the SDEs generated through the chemical Langevin equations can be displayed by setting the `form` argument to `:sde`. Here, the noise in the reaction network is correlated/
```julia
latexify(rn; form=:sde)
```
```math
\begin{align}
\frac{dx}{dt} &= \sqrt{\frac{v_{x} \cdot y^{2}}{k_{x}^{2} + y^{2}}} - \sqrt{d_{x} \cdot x} - \sqrt{r_{b} \cdot x} + \sqrt{r_{u} \cdot y} \\
\frac{dy}{dt} &= \sqrt{p_{y}} - \sqrt{d_{y} \cdot y} + \sqrt{r_{b} \cdot x} - \sqrt{r_{u} \cdot y} \\
\end{align}
```
Note: On the current version of Latexify, generation of SDEs from reaction networks is broken.
## Chemical arrow notation
Catalyst reaction network is all about chemical arrow notation, so why should we not be able to render arrows?
This is the default output (when no value to `form` is given).
```julia
latexify(rn; env=:chemical)
```
\begin{align}
\require{mhchem}
\ce{ \varnothing &->[\frac{v_{x} \cdot y^{2}}{k_{x}^{2} + y^{2}}] x}\\\\
\ce{ \varnothing &->[p_{y}] y}\\\\
\ce{ x &->[d_{x}] \varnothing}\\\\
\ce{ y &->[d_{y}] \varnothing}\\\\
\ce{ x &<=>[{r_{b}}][{r_{u}}] y}\\\\
\end{align}
The default output is meant to be rendered directly on the screen. This rendering is typically done by MathJax. To get the chemical arrow notation to render automatically, I have included a MathJax command (`\require{mhchem}`) in the output string. If you want to use the output in a real LaTeX document, you can pass the keyword argument `mathjax=false` and this extra command will be omitted. In such case you should also add `\usepackage{mhchem}` to the preamble of your latex document.
Another keyword argument that may be of use is `expand=false` (defaults to `true`).
This determines whether your functions should be expanded or not.
Also, `starred=true` will change the outputted latex environment from `align` to `align*`. This results in the equations not being numbered.
```julia
latexify(rn; env=:chemical, expand=false, starred=true)
```
```math
\begin{align*}
\require{mhchem}
\ce{ \varnothing &->[\mathrm{hill2}\left( y, v_{x}, k_{x} \right)] x}\\
\ce{ \varnothing &->[p_{y}] y}\\
\ce{ x &->[d_{x}] \varnothing}\\
\ce{ y &->[d_{y}] \varnothing}\\
\ce{ x &<=>[{r_{b}}][{r_{u}}] y}\\
\end{align*}
```
## Available options
### Align
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if (:ReactionNetwork in arg.types || :Any in arg.types) && :align in arg.env]
latexify(args, env=:mdtable, types=false)
```
### Arrow notation
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if (:ReactionNetwork in arg.types || :Any in arg.types) && :arrow in arg.env]
latexify(args, env=:mdtable, types=false)
```
================================================
FILE: docs/src/tutorials/inner_workings.md
================================================
# Inner workings
This package contains a large number of methods, but two of these are of special importance.
These are:
- `latexraw(ex::Expr)`
and
- `latexoperation(ex::Expr, prevOp::AbstractArray)`
These two methods are involved with all conversions to ``\LaTeX`` equations.
`latexraw(ex::Expr)` utilises Julias homoiconicity to infer the correct latexification of an expression by recursing through the expression tree. Whenever it hits the end of a recursion it passes the last expression to `latexoperation()`.
By the nature of this recursion, this expression is one which only contains symbols or strings.
## Explanation by example
Let's define a variable of the expression type:
```julia-repl
julia> ex = :(x + y/z)
```
This expression has a field which contains the first operation which must be done, along with the objects that this operation will operate on:
```julia-repl
julia> ex.args
3-element Array{Any,1}:
:+
:x
:(y / z)
```
The first two element are both Symbols, while the third one is an expression:
```julia-repl
julia> typeof.(ex.args)
3-element Array{DataType,1}:
Symbol
Symbol
Expr
```
Since at least one of these elements is an expression, the next step of the recursive algorithm is to dive into that expression:
```julia-repl
julia> newEX = ex.args[3]
julia> newEx.args
3-element Array{Any,1}:
:/
:y
:z
```
Since none of these arguments is another expression, `newEx` will be passed to `latexoperation()`.
This function checks which mathematical operation is being done and converts newEx to an appropriately formatted string.
In this case, that string will be "\\\\frac{y}{z}" (and yes, a double slash is needed).
`newEx` is now a string (despite its name):
```julia
julia> newEx
"\\frac{y}{z}"
```
The recursive `latexraw()` pulls this value back to the original expression `ex`, such that:
```julia-repl
julia> ex.args
3-element Array{Any,1}:
:+
:x
:"\\frac{y}{z}"
```
Now, since this expression does not consist of any further expressions, it is passed to `latexoperation()`.
The operator is now "+", and it should be applied on the second and third element of the expression, resulting in:
```julia
"x + \\frac{y}{z}"
```
using the print function you get:
```julia-repl
julia> print(latexraw(ex))
"x + \frac{y}{z}"
```
which in a ``\LaTeX`` maths environment renders as:
```math
x + \frac{y}{z}
```
## Extended functionality
With the above example we can understand how an expression is converted to a ``\LaTeX`` formatted string (unless my pedagogical skills are worse than I fear).
So, anything which can be converted to a Julia expression of the Expr type can be latexified.
Luckily, since Julia needs to convert your code to expressions before it can be evaluated, Julia is already great at doing this.
There are already some methods for converting other types to expressions and passing them to the core method, for example:
```julia
latexraw(str::String) = latexraw(parse(str))
```
but if you find yourself wanting to parse some other type, it is often easy to overload the `latexraw` function.
## Latexifying Arrays
Also, if you pass an array to `latexraw`, it will recursively try to convert the elements of that array to ``\LaTeX`` formatted strings.
```julia-repl
julia> arr = [:(x-y/(k_10+z)), "x*y*z/3"]
julia> latexraw(arr)
2-element Array{String,1}:
"x - \\frac{y}{k_{10} + z}"
"\\frac{x \\cdot y \\cdot z}{3}"
julia> println.(latexraw(arr))
x - \frac{y}{k_{10} + z}
\frac{x \cdot y \cdot z}{3}
```
================================================
FILE: docs/src/tutorials/latexalign.md
================================================
# [`latexalign`](@id latexalign_tutorial)
This function converts its input to ``\LaTeX`` align environments.
One way of using the function is to pass it two vectors, one which holds the left-hand-side of the equations and the other which holds the right. For example:
```julia
lhs = ["dx/dt", "dy/dt"]
rhs = ["y^2 - x", "x/y - y"]
print(latexalign(lhs, rhs))
```
outputs:
```maths
\begin{align}
\frac{dx}{dt} &= y^{2} - x \\
\frac{dy}{dt} &= \frac{x}{y} - y \\
\end{align}
```
In Jupyter, this can be rendered by:
```julia
display( latexalign(lhs, rhs))
```
\begin{align\*}
\frac{dx}{dt} &= y^{2} - x \\\\
\frac{dy}{dt} &= \frac{x}{y} - y \\\\
\end{align\*}
## Using DifferentialEquations.jl
The motivation for creating this function was mainly to be able to render ODEs.
In my own work, I tend to use [DifferentialEquations.jl](http://docs.juliadiffeq.org/stable/index.html) to define ODEs as [ParameterizedFunctions](http://docs.juliadiffeq.org/stable/analysis/parameterized_functions.html#Function-Definition-Macros-1).
Therefore, I found it useful to create a method which simply takes the ParameterizedFunction as input:
```julia
using Latexify
using DifferentialEquations
ode = @ode_def positiveFeedback begin
dx = y/(k_y + y) - x
dy = x^n_x/(k_x^n_x + x^n_x) - y
end k_y k_x n_x
latexalign(ode)
```
\begin{align}
\frac{dx}{dt} &= \frac{y}{k_{y} + y} - x \\\\
\frac{dy}{dt} &= \frac{x^{n_{x}}}{k_{x}^{n_{x}} + x^{n_{x}}} - y \\\\
\end{align}
================================================
FILE: docs/src/tutorials/latexarray.md
================================================
# `latexarray`
This functions takes a 1 or 2D array and spits out a latex array environment.
For example:
```julia-repl
julia> arr = eye(Int,3)
julia> print(latexarray(arr))
\begin{equation}
\left[
\begin{array}{ccc}
1 & 0 & 0\\
0 & 1 & 0\\
0 & 0 & 1\\
\end{array}
\right]
\end{equation}
```
which renders as:
\begin{equation}
\left[
\begin{array}{ccc}
1 & 0 & 0\\\\
0 & 1 & 0\\\\
0 & 0 & 1\\\\
\end{array}
\right]
\end{equation}
`latexraw()` is called for each element of the input, individually.
It therefore does not matter if the input array is of a mixed type.
```julia
arr = [1.0, 2-3im, 3//4, :(x/(k_1+x)), "e^(-k_b*t)"]
latexarray(arr)
```
renders as:
\begin{equation}
\left[
\begin{array}{c}
1.0\\\\
2-3\textit{i}\\\\
\frac{3}{4}\\\\
\frac{x}{k_{1} + x}\\\\
e^{- k_{b} \cdot t}\\\\
\end{array}
\right]
\end{equation}
================================================
FILE: docs/src/tutorials/latexify.md
================================================
# `latexify`
This is a wrapper of some of the other `latexXXX` functions. It tries to infer a suitable output mode for the given input. If the environment you are using supports the MIME type "text/latex", then the output will be rendered nicely.
```julia
using Latexify
copy_to_clipboard(true)
ex = :(x/y)
latexify(ex)
```
$\frac{x}{y}$
If you `print` the output rather than `display`, then you will enforce the print-out of a string which is ready for some copy-pasting into your LaTeX document.
```julia
println(latexify(ex))
## or the equivalent:
latexify(ex) |> println
```
```LaTeX
$\frac{x}{y}$
```
A matrix, or a single vector, is turned into an array.
```julia
M = signif.(rand(3,4), 2)
latexify(M)
```
\begin{equation}
\left[
\begin{array}{cccc}
0.85 & 0.99 & 0.85 & 0.5\\\\
0.59 & 0.061 & 0.77 & 0.48\\\\
0.7 & 0.17 & 0.7 & 0.82\\\\
\end{array}
\right]
\end{equation}
You can transpose the output using the keyword argument `transpose=true`.
If you give two vectors as an argument, they will be displayed as the left-hand-side and right-hand-side of an align environment:
```julia
latexify(["x/y", :z], Any[2.3, 1//2])
```
\begin{align}
\frac{x}{y} &= 2.3 \\\\
z &= \frac{1}{2} \\\\
\end{align}
If you input a ParameterizedFunction or a ReactionNetwork from DifferentialEquations.jl you will also get an align environment. For more on this, have a look on their respective sections.
================================================
FILE: docs/src/tutorials/latexinline.md
================================================
# `latexinline`
takes a Julia object `x` and returns a ``\LaTeX`` formatted string.
It also surrounds the output in a simple \$\$ environment.
This works for `x` of many types, including expressions, which returns ``\LaTeX`` code for an equation.
```julia-repl
julia> ex = :(x-y/z)
julia> latexinline(ex)
L"$x - \frac{y}{z}$"
```
In Jupyter or Hydrogen this automatically renders as:
$x - \frac{y}{z}$
Among the supported types are:
- Expressions,
- Strings,
- Numbers (including rational and complex),
- Symbols,
- Symbolic expressions from SymEngine.jl.
- ParameterizedFunctions.
It can also take arrays, which it recurses and latexifies the elements, returning an array of latex strings.
================================================
FILE: docs/src/tutorials/latextabular.md
================================================
# `latextabular`
```julia
using Latexify
copy_to_clipboard(true)
arr = ["x/y" :(y^n); 1.0 :(alpha(x))]
latextabular(arr) |> println
```
outputs:
```LaTeX
\begin{tabular}{cc}
$\frac{x}{y}$ & $y^{n}$\\
$1.0$ & $\alpha\left( x \right)$\\
\end{tabular}
```
Unfortunately, this does not render nicely in Markdown. But you get the point.
`latextabular` takes two keywords, one for changing the adjustment of the columns (centered by default), and one for transposing the whole thing.
```julia
latextabular(arr; adjustment=:l, transpose=true) |> println
```
```LaTeX
\begin{tabular}{ll}
$\frac{x}{y}$ & $1.0$\\
$y^{n}$ & $\alpha\left( x \right)$\\
\end{tabular}
```
The adjustments can be set per column by providing a vector like `[:c, :l, :r]`.
If you want to use the `S` column type from `siunitx`, set `latex=false, adjustment=:S`.
Some post-adjustment may be necessary.
================================================
FILE: docs/src/tutorials/notebooks.md
================================================
# Notebook workflows
When working in a notebook (These tips assume Pluto, but will apply at least
in part to other similar environments), there's a number of options to
incorporate latexifications.
As a first principle, any cell that returns a single `LaTeXString` (or a
string surrounded by `$` in general) will be displayed as math:
```julia
latexify(35e-9; fmt=FancyNumberFormatter())
```
```math
3.5 \cdot 10^{-8}
```
```julia
@latexify (3x + 45)/2y
```
```math
\frac{3 \cdot x + 45}{2 \cdot y}
```
There's a visual bug in Pluto where any expression looking like an assignment
is printed with extra unnecessary information. To avoid this, encase such in a `begin/end` block:
```julia
begin
@latexrun x = 125
end
```
```math
x = 125
```
```julia
begin
@latexdefine y = x
end
```
```math
y = x = 125
```
One very nice workflow is to use `Markdown.parse` to embed latexifications in
markdown text. Note that `md""` does
*not* work very well for this, as the
dollar signs signifying math mode will
clash with those signifying
interpolation. In `parse`, you need to
escape special characters like
backslashes, but since we're using
`Latexify` we don't need to write very
many of those anyway.
```julia
Markdown.parse("""
## Results
With the previously calculated
$(@latexdefine x), we can use
$(@latexify x = v*t) to calculate
$(@latexrun v = x/10), giving a final
velocity of $(latexify(v)).
If we want more manual control, we can
combine manual dollar signs with
`env=:raw`: \$ \hat{v} =
$(latexify(v, env=:raw))\;\mathrm{m}/\mathrm{s} \$
""")
```
## Results
With the previously calculated
$x = 125$, we can use $x = v \cdot t$
to calculate $v = \frac{x}{10}$,
giving a final velocity of $12.5$.
If we want more manual control, we can combine manual dollar signs with `env=:raw`: $ \hat{v} = 12.5\;\mathrm{m}/\mathrm{s} $
================================================
FILE: docs/src/tutorials/parameterizedfunctions.md
================================================
# Use with ParameterizedFunctions
In the [latexalign tutorial](@ref latexalign_tutorial) I mentioned that one can use `latexalign` directly on a [ParameterizedFunction](http://docs.juliadiffeq.org/stable/analysis/parameterized_functions.html#Function-Definition-Macros-1).
Here, I make a somewhat more convoluted and hard-to-read example (you'll soon se why):
```julia
using Latexify
using ParameterizedFunctions
copy_to_clipboard(true)
ode = @ode_def positiveFeedback begin
dx = y*y*y/(k_y_x + y) - x - x
dy = x^n_x/(k_x^n_x + x^n_x) - y
end k_y k_x n_x
latexify(ode)
```
```math
\begin{align}
\frac{dx}{dt} &= \frac{y \cdot y \cdot y}{k_{y\_x} + y} - x - x \\
\frac{dy}{dt} &= \frac{x^{n_{x}}}{k_{x}^{n_{x}} + x^{n_{x}}} - y \\
\end{align}
```
This is pretty nice, but there are a few parts of the equation which could be reduced.
Using a keyword argument, you can utilise the SymEngine.jl to reduce the expression before printing.
```julia
latexify(ode, field=:symfuncs)
```
```math
\begin{align}
\frac{dx}{dt} &= -2 \cdot x + \frac{y^{3}}{k_{y\_x} + y} \\
\frac{dy}{dt} &= - y + \frac{x^{n_{x}}}{k_{x}^{n_{x}} + x^{n_{x}}} \\
\end{align}
```
### Side-by-side rendering of multiple system.
A vector of ParameterizedFunctions will be rendered side-by-side:
```julia
ode2 = @ode_def negativeFeedback begin
dx = y/(k_y + y) - x
dy = k_x^n_x/(k_x^n_x + x^n_x) - y
end k_y k_x n_x
latexify([ode, ode2])
```
```math
\begin{align}
\frac{dx}{dt} &= \frac{y \cdot y \cdot y}{k_{y\_x} + y} - x - x & \frac{dx}{dt} &= \frac{y}{k_{y} + y} - x & \\
\frac{dy}{dt} &= \frac{x^{n_{x}}}{k_{x}^{n_{x}} + x^{n_{x}}} - y & \frac{dy}{dt} &= \frac{k_{x}^{n_{x}}}{k_{x}^{n_{x}} + x^{n_{x}}} - y & \\
\end{align}
```
### Visualise your parameters.
Another thing that I have found useful is to display the parameters of these functions. The parameters are usually in a vector, and if it is somewhat long, then it can be annoying to try to figure out which element belongs to which parameter. There are several ways to solve this. Here are two:
```julia
## lets say that we have some parameters
param = [3.4,5.2,1e-2]
latexify(ode.params, param)
```
```math
\begin{align}
k_{y} &= 3.4 \\
k_{x} &= 5.2 \\
n_{x} &= 0.01 \\
\end{align}
```
or
```julia
latexify([ode.params, param]; env=:array, transpose=true)
```
```math
\begin{equation}
\left[
\begin{array}{ccc}
k_{y} & k_{x} & n_{x} \\
3.4 & 5.2 & 0.01 \\
\end{array}
\right]
\end{equation}
```
`signif.()` is your friend if your parameters have more significant numbers than you want to see.
### Get the jacobian, hessian, etc.
ParameterizedFunctions symbolically calculates the jacobian, inverse jacobian, hessian, and all kinds of goodness. Since they are available as arrays of symbolic expressions, which are latexifyable, you can render pretty much all of them.
```julia
latexify(ode.symjac)
```
```math
\begin{equation}
\left[
\begin{array}{cc}
-2 & \frac{3 \cdot y^{2}}{k_{y\_x} + y} - \frac{y^{3}}{\left( k_{y\_x} + y \right)^{2}} \\
\frac{x^{-1 + n_{x}} \cdot n_{x}}{k_{x}^{n_{x}} + x^{n_{x}}} - \frac{x^{-1 + 2 \cdot n_{x}} \cdot n_{x}}{\left( k_{x}^{n_{x}} + x^{n_{x}} \right)^{2}} & -1 \\
\end{array}
\right]
\end{equation}
```
## Available options
```@eval
Base.include(@__MODULE__, "src/table_generator.jl")
args = [arg for arg in keyword_arguments if :ParameterizedFunction in arg.types || :Any in arg.types]
latexify(args, env=:mdtable, types=false)
```
================================================
FILE: docs/src/tutorials/recipes.md
================================================
# Recipes
Recipes provides a concise means of extending Latexify.jl to work with types of your own making or of other packages. The `@latexrecipe` macro allows you to specify how a an argument type (or a set of types) should be pre-processed before they are passed to the standard `latexify` function. Also, it allows you to define both new keyword arguments as well as to set the defaults of pre-existing ones. The main power of this macro is that is defines the necessary functions *within* Latexify.jl itself, as opposed to within the module where it is called.
The recipe syntax closely follow that of the [Plots.jl](https://github.com/JuliaPlots/Plots.jl) [recipes](https://github.com/JuliaPlots/RecipesBase.jl) and, indeed, most of the code is copied and adapted from them (cred to the authors!).
So. The easiest way to explain it is by showing an example where we define a recipe for our type `MyType`.
```julia
using Latexify
struct MyType
vector::Vector
end
```
```julia
@latexrecipe function f(x::MyType; reverse=false)
## we can access the input object and perform operations like in a normal function.
vec = x.vector
if reverse
vec = vec[end:-1:1]
end
## we can define defult keyword arguments to be passed along to latexify
## using an arrow notation, -->
env --> :array
transpose --> true
## These can be overridden by the keyword arguments passed to the latexify function.
## If you use the := operator to specify a value it cannot be overridden.
fmt := "%.2f"
## The return value should be something that latexify already knows how to work with.
## In this case, we have a simple vector which is fine!
return vec
end
```
```julia
mytype = MyType([1, 2, 3])
latexify(mytype; reverse=true)
```
```math
\begin{equation}
\left[
\begin{array}{c}
3.00 \\
2.00 \\
1.00 \\
\end{array}
\right]
\end{equation}
```
The required signature of the macro is
```julia
@latexrecipe function f(x::MyType, ...; ...)
return something
end
```
Here, the function name is unimportant, but the type signature is key.
There must also be an explicit `return` statement which returns something that
base Latexify already works with (Arrays, Tuples, Numbers, Symbols, Strings, etc.).
In particular, you can not rely on Julia's default to return the value of the
last expression evaluated in a function body.
The special notation `kwarg --> value` resets the default value of a keyword argument for your specific inputs. This will be overridden if the keyword argument in quesion is specified in a call to `latexify`.
To disallow this overriding, use `kwarg := value` instead.
The use of `@latexrecipe` to redefine how an already supported type should be interpreted is highly discouraged. There is (currently) nothing in place to forbid this but it could mess up how latexify works with other packages. Disregarding this in your own sessions is one thing, but doing it in a package could cause very difficult issues for the users of your package.
If a recipe is defined within a module, everything should just work without the need to export anything.
The special keyword argument `operation` lets you specify that a type corresponds to a specific arithmetic operation.
For instance, if we define a type
```julia
struct MyDifference
x
y
end
```
that is meant to represent the operation `x - y`, we might want to create the recipe
```julia
@latexrecipe function f(m::MyDifference)
return :($(m.y) - $(m.x))
end
```
so that the result of `latexify(MyDifference(2,3))` is ``3 - 2``.
But right now, `latexify` does not know that this represents an operation, so for instance
`@latexify $(MyDifference(2,3))*4` gives ``3 - 2 \cdot 4``, which is incorrect.
The way around this is to edit the recipe:
```julia
@latexrecipe function f(m::MyDifference)
operation := :-
return :($(m.y) - $(m.x))
end
```
Now `latexify` knows that `MyDifference` represents a subtraction, and parenthesis rules kick in:
`@latexify $(MyDifference(2,3))*4` gives ``\left( 3 - 2 \right) \cdot 4``.
================================================
FILE: docs/src/tutorials/rendering_latex.md
================================================
# A note on rendering ``\LaTeX``
Using the `print` function on a latexified object prints text which is suitable for copy-pasting into a ``\LaTeX`` document.
However, it is often also useful to be able to render the equation inside the document that one is using to develop code. The Julia REPL does not support this, but IJulia does.
So, inside a Jupyter or Pluto notebook (or if you are running Atom with Hydrogen), you can render ``\LaTeX`` using
```julia
display("text/latex", x)
```
where `x` is a latex-formatted string.
This requires `x` to specify a ``\LaTeX`` environment. `latexalign` and `latexequation` already does this, but if you want to render the result of `latexify` you must supply an environment (for example `"\$ $x \$"`).
================================================
FILE: ext/DataFramesExt.jl
================================================
module DataFramesExt
using Latexify
isdefined(Base, :get_extension) ? (using DataFrames) : (using ..DataFrames)
@latexrecipe function f(d::DataFrame)
env --> :mdtable
head --> propertynames(d)
if kwargs[:env] == :array
return vcat(permutedims(propertynames(d)), Matrix(d))
end
return Matrix(d)
end
end
================================================
FILE: ext/SparseArraysExt.jl
================================================
module SparseArraysExt
using Latexify
isdefined(Base, :get_extension) ? (using SparseArrays) : (using ..SparseArrays)
@latexrecipe function f(x::AbstractSparseArray)
return collect(x)
end
end
================================================
FILE: ext/SymEngineExt.jl
================================================
module SymEngineExt
using Latexify
isdefined(Base, :get_extension) ? (using SymEngine) : (using ..SymEngine)
@latexrecipe function f(x::SymEngine.Basic)
return string(x)
end
end
================================================
FILE: ext/TectonicExt.jl
================================================
module TectonicExt
import LaTeXStrings.LaTeXString
import Latexify.render, Latexify._compile
isdefined(Base, :get_extension) ? (using tectonic_jll) : (using ..tectonic_jll)
__precompile__(false)
function render(s::LaTeXString, ::MIME"application/pdf"; use_tectonic=true, tectonic_flags=``, lualatex_flags=``, kw...)
use_tectonic && return _compile(s, `$(tectonic()) --keep-logs $tectonic_flags main.tex`, "pdf"; kw...)
return _compile(s, `lualatex --interaction=batchmode $lualatex_flags main.tex`, "pdf"; kw...)
end
end
================================================
FILE: paper/paper.bib
================================================
@article{julia,
author = {Jeff Bezanson and Alan Edelman and Stefan Karpinski and Viral B. Shah},
title = {Julia: A Fresh Approach to Numerical Computing},
journal = {SIAM Review},
volume = {59},
number = {1},
pages = {65-98},
year = {2017},
doi = {10.1137/141000671},
}
@article{weave,
author = {Pastell},
title = {Weave.jl: Scientific Reports Using Julia},
journal = {Journal of Open Source Software},
volume = {2},
number = {11},
pages = {204},
year = {2017},
doi = {doi:10.21105/joss.00204},
}
@article{diffeq,
author = {Rackauckas, Christopher and Nie, Qing},
doi = {10.5334/jors.151},
journal = {The Journal of Open Research Software},
keywords = {Applied Mathematics},
note = {Exported from https://app.dimensions.ai on 2019/05/05},
number = {1},
pages = {},
title = {DifferentialEquations.jl – A Performant and Feature-Rich Ecosystem for Solving Differential Equations in Julia},
url = {https://app.dimensions.ai/details/publication/pub.1085583166 and http://openresearchsoftware.metajnl.com/articles/10.5334/jors.151/galley/245/download/},
volume = {5},
year = {2017}
}
================================================
FILE: paper/paper.md
================================================
---
title: 'Latexify.jl, translating mathematical Julia objects to renderable equations and tables.'
tags:
- Julia
- LaTeX
- Markdown
- equations
- rendering
authors:
- name: Niklas Korsbo
orcid: 0000-0001-9811-3190
affiliation: "1, 2"
affiliations:
- name: The Sainsbury Laboratory, Cambridge University
index: 1
- name: Department of Applied Mathematics and Theoretical Physics, Cambridge University
index: 2
date: 27 January 2020
bibliography: paper.bib
---
# Summary
Human-understandable representation and visualisation of data and objects is
important for understanding and communicating the results of scientific
software.
Latexify.jl is a tool for converting Julia [@julia] objects to humanly
accessible and renderable equations and tables. It allows for the conversion
of multiple types to LaTeX or Markdown-formatted strings and, in many
development environments, for immediate rendering of the result. Among the
supported inputs is a class of Expressions that describe mathematical
equations. These can be translated into LaTeX formatted mathematics and can be
outputted as LaTeX environments such as align, equation or in-line equations.
The conversion can recurse through many container types, even if they contain
mixed types, and produce resulting tables, arrays or aligned equations for
LaTeX or Markdown. The output is configurable and a recipe system makes it easy
to extend Latexify.jl to work with custom types without having to modify
Latexify.jl itself. This becomes especially powerful in combination with
Julia's metaprogramming facilities and easily generated domain-specific
languages (DSLs). Latexify.jl can, for example, output the system of
differential equations (and much more) that is automatically generated by a
chemical reaction arrow DSL provided by Catalyst.jl [@diffeq].
The package aims to support the scientific work-flow through facilitating
inspection, automation and representation. Simple inspection of the equations
that a computational model is ultimately simulating may increase
comprehensibility and improve troubleshooting. It, furthermore, allows for
de-mystification of computational models generated using DSLs. The
programmatic formatting of equations and tables enables their automatic
inclusion into generated documents, documentation, reports or posts (possibly
in combination with tools such as Weave.jl [@weave]). Such programmatic
translation can also help to ensure an accurate correspondence between what
software does and what reports or articles claim that they do. It is also just
rather convenient.
# Acknowledgements
I acknowledge the contributions from the Julia programming community and
especially those who through issues and pull requests have improved
Latexify.jl. I would also like to acknowledge my PhD supervisor, Henrik
Jönsson, who supports me in my work while allowing me to allocate my efforts as
I see fit.
The development of this package was, in part, supported by the Gatsby
Charitable Foundation, grant GAT3395-PR4.
# References
================================================
FILE: src/Latexify.jl
================================================
module Latexify
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@optlevel"))
@eval Base.Experimental.@optlevel 1
end
if isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol("@max_methods"))
@eval Base.Experimental.@max_methods 1
end
using LaTeXStrings
using InteractiveUtils
using Markdown
using MacroTools: postwalk
import MacroTools
using Format
import Base.showerror
import Ghostscript_jll
export latexify, md, copy_to_clipboard, auto_display, set_default, get_default,
reset_default, @latexrecipe, render, @latexify, @latexrun, @latexdefine
## Allow some backwards compatibility until its time to deprecate.
export latexequation, latexarray, latexalign, latexraw, latexinline, latextabular, mdtable
export StyledNumberFormatter, FancyNumberFormatter, SiunitxNumberFormatter
COPY_TO_CLIPBOARD = false
function copy_to_clipboard(x::Bool)
global COPY_TO_CLIPBOARD = x
end
AUTO_DISPLAY = false
function auto_display(x::Bool)
global AUTO_DISPLAY = x
end
const DEFAULT_DPI = Ref(300)
include("unicode2latex.jl")
include("symbol_translations.jl")
include("latexraw.jl")
include("latexoperation.jl")
include("latexarray.jl")
include("latexalign.jl")
include("latexbracket.jl")
include("latexinline.jl")
include("latexequation.jl")
include("latextabular.jl")
include("default_kwargs.jl")
include("recipes.jl")
include("macros.jl")
include("mdtable.jl")
include("mdtext.jl")
include("md.jl")
include("utils.jl")
include("error.jl")
include("numberformatters.jl")
include("latexify_function.jl")
include("internal_recipes.jl")
### Add support for additional packages without adding them as dependencies.
### Requires on <1.9 and weakdeps/extensions on >=1.9
if !isdefined(Base, :get_extension)
using Requires
end
@static if !isdefined(Base, :get_extension)
function __init__()
@require SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" begin
include("../ext/SymEngineExt.jl")
end
@require DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" begin
include("../ext/DataFramesExt.jl")
end
@require SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" begin
include("../ext/SparseArraysExt.jl")
end
@require tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" begin
include("../ext/TectonicExt.jl")
end
end
end
macro generate_test(expr)
return :(clipboard("@test $($(string(expr))) == replace(\nraw\"$($(esc(expr)))\", \"\\r\\n\"=>\"\\n\")\n"))
end
"""
@append_latexify_test!(fname, expr)
Generate a Latexify test and append it to the file `fname`.
The expression `expr` should return a string when evaluated.
Example use:
```
Latexify.@append_latexify_test!("./tests/latexify_tests.jl", latexify(:(x/y)))
```
The macro returns the output of the expression and can often be rendered
for a visual check that the test itself is ok.
```
Latexify.@append_latexify_test!("./tests/latexify_tests.jl", latexify(:(x/y))) |> render
```
"""
macro append_latexify_test!(fname, expr)
fname = esc(fname)
return :(
str = "@test $($(string(expr))) == replace(\nraw\"$($(esc(expr)))\", \"\\r\\n\"=>\"\\n\")\n\n";
open($fname, "a") do f
write(f,str)
end;
$(esc(expr))
)
end
"""
@append_test!(fname, expr)
Both execute and append code to a test file.
The code can be either a normal expression or a string.
Example use:
```
Latexify.@append_test A = [1 2; 3 4]
```
Useful for adding code that generates objects to be used in latexify tests.
"""
macro append_test!(fname, str)
fname = esc(fname)
returnobj = str isa String ? Meta.parse(str) : str
printobj = str isa String ? str : string(MacroTools.striplines(str))
return :(
open($fname, "a") do f
write(f, $(esc(printobj)))
write(f, "\n\n")
end;
$(esc(returnobj))
)
end
end
================================================
FILE: src/default_kwargs.jl
================================================
const default_kwargs = Dict{Symbol, Any}()
"""
set_default(; kwargs...)
Set default kwarg values for latexify.
This works for all keyword arguments except `:env`. It is additive such that if
you call it multiple times, defaults will be added or replaced, but not reset.
Example:
```julia
set_default(mult_symbol = "", transpose = true)
```
To reset the defaults that you have set, use `reset_default`.
To see your specified defaults, use `get_default`.
"""
function set_default(; kwargs...)
for key in keys(kwargs)
default_kwargs[key] = kwargs[key]
end
end
"""
reset_default()
Reset user-specified default kwargs for latexify, set by `set_default`.
"""
reset_default() = empty!(default_kwargs)
"""
get_default
Get a Dict with the user-specified default kwargs for latexify, set by `set_default`.
"""
function get_default end
get_default() = default_kwargs
get_default(arg::Symbol) = default_kwargs[arg]
get_default(args::AbstractArray) = map(x->default_kwargs[x], args)
get_default(args...) = Tuple(get_default(arg) for arg in args)
================================================
FILE: src/error.jl
================================================
abstract type LatexifyException <: Exception end
struct NoRecipeException <: LatexifyException
type::Type
end
function Base.showerror(io::IO, e::NoRecipeException)
return print(io, "cannot latexify objects of type ", e.type)
end
struct UnrecognizedExpressionException <: LatexifyException
ex::Expr
end
function Base.showerror(io::IO, e::UnrecognizedExpressionException)
return print(
io,
"latexoperation does not know what to do with one of the expressions provided (",
e.ex,
")",
)
end
struct UnrepresentableException <: LatexifyException
desc::String
end
function Base.showerror(io::IO, e::UnrepresentableException)
return print(io, e.desc, " cannot be represented as LaTeX")
end
struct MathParseError <: LatexifyException
input::String
end
function Base.showerror(io::IO, e::MathParseError)
return print(
io,
"""
You are trying to create LaTeX maths from a `String` that cannot be parsed as an expression: `""",
e.input,
"""`.
`latexify` will, by default, try to parse any string inputs into expressions and this parsing has just failed.
If you are passing strings that you want returned verbatim as part of your input, try making them `LaTeXString`s first.
If you are trying to make a table with plain text entries, try passing the keyword argument `latex=false`.
You should also ensure that you have chosen an output environment that is capable of displaying non-maths objects.
Try for example `env=:table` for a LaTeX table or `env=:mdtable` for a markdown table.
""",
)
end
struct RecipeException <: Exception
msg::String
end
Base.showerror(io::IO, e::RecipeException) = print(io, e.msg)
struct LatexifyRenderError <: Exception
logfilename::String
end
function Base.showerror(io::IO, e::LatexifyRenderError)
isfile(e.logfilename) ||
return println(io, "an error occured while rendering LaTeX, no log file available.")
println(io, "an error occured while rendering LaTeX: ")
secondline = false
for l in eachline(e.logfilename)
if secondline
println(io, "\t", l)
break
end
m = match(r"^! (.*)$", l)
isnothing(m) && continue
println(io, "\t", m[1])
secondline = true
end
return print(io, "Check the log file at ", e.logfilename, " for more information")
end
================================================
FILE: src/internal_recipes.jl
================================================
@latexrecipe function f(x::UnitRange; expand_ranges=false)
expand_ranges && return collect(x)
return :($(x.start) : $(x.stop))
end
@latexrecipe function f(x::StepRange; expand_ranges=false, expand_step_ranges=true)
(expand_ranges || expand_step_ranges) && return collect(x)
return :($(x.start) : $(step(x)) : $(x.stop))
end
@latexrecipe function f(x::StepRangeLen{T, <:Any, <:Any}; expand_ranges=false, expand_step_ranges=true) where {T}
(expand_ranges || expand_step_ranges) && return collect(x)
return :($(T(x.ref + (x.offset-1)*step(x))) : $(T(x.step)) : $(T(x.ref + (x.len-1)*x.step)))
end
================================================
FILE: src/latexalign.jl
================================================
@doc doc"""
latexalign()
Generate a ``LaTeX`` align environment from an input.
# Examples
## use with arrays
```julia
lhs = [:(dx/dt), :(dy/dt), :(dz/dt)]
rhs = [:(y-x), :(x*z-y), :(-z)]
latexalign(lhs, rhs)
```
```LaTeX
\begin{align}
\frac{dx}{dt} &= y - x \\\\
\frac{dy}{dt} &= x \cdot z - y \\\\
\frac{dz}{dt} &= - z \\\\
\end{align}
```
## use with ParameterizedFunction
```julia-repl
julia> using DifferentialEquations
julia> ode = @ode_def foldChangeDetection begin
dm = r_m * (i - m)
dy = r_y * (p_y * i/m - y)
end i r_m r_y p_y
julia> latexalign(ode)
```
```LaTeX
\begin{align}
\frac{dm}{dt} &= r_{m} \cdot \left( i - m \right) \\\\
\frac{dy}{dt} &= r_{y} \cdot \left( \frac{p_{y} \cdot i}{m} - y \right) \\\\
\end{align}
```
"""
latexalign(args...; kwargs...) = process_latexify(args...; kwargs..., env=:align)
function _latexalign(arr::AbstractMatrix; separator=" &= ", double_linebreak=false, starred=false, rows=:all, aligned=false, kwargs...)
eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n"
arr = latexraw.(arr; kwargs...)
separator isa String && (separator = fill(separator, size(arr)[1]))
str = ""
if aligned
str *= "\\begin{aligned}\n"
else
str *= "\\begin{align$(starred ? "*" : "")}\n"
end
if rows == :all
iterate_rows = 1:(size(arr)[1])
else
iterate_rows = rows
end
for i in iterate_rows
if i != last(iterate_rows)
str *= join(arr[i,:], separator[i]) * eol
else
str *= join(arr[i,:], separator[i]) * "\n"
end
end
if aligned
str *= "\\end{aligned}\n"
else
str *= "\\end{align$(starred ? "*" : "")}\n"
end
latexstr = LaTeXString(str)
COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
function _latexalign(lhs::AbstractArray, rhs::AbstractArray; kwargs...)
return latexalign(hcat(lhs, rhs); kwargs...)
end
function _latexalign(lhs::Tuple, rhs::Tuple; kwargs...)
return latexalign(hcat(collect(lhs), collect(rhs)); kwargs...)
end
_latexalign(args::Tuple...; kwargs...) = latexalign(safereduce(hcat, [collect(i) for i in args]); kwargs...)
_latexalign(arg::Tuple; kwargs...) = latexalign(safereduce(hcat, [collect(i) for i in arg]); kwargs...)
function _latexalign(nested::AbstractVector{AbstractVector}; kwargs...)
return latexalign(safereduce(hcat, nested); kwargs...)
end
function _latexalign(d::AbstractDict; kwargs...)
latexalign(collect(keys(d)), collect(values(d)); kwargs...)
end
"""
_latexalign(vec::AbstractVector)
Go through the elements, split at any = sign, pass on as a matrix.
"""
function _latexalign(vec::AbstractVector; kwargs...)
lvec = _latexraw.(vec; kwargs...)
## turn the array into a matrix
lmat = safereduce(hcat, split.(lvec, " = "))
## turn the matrix into arrays of left-hand-side, right-hand-side.
larr = [lmat[i,:] for i in 1:size(lmat, 1)]
length(larr) < 2 && throw(ArgumentError("Invalid input to _latexalign()."))
return latexalign( safereduce(hcat, larr) ; kwargs...)
end
================================================
FILE: src/latexarray.jl
================================================
"""
latexarray{T}(arr::AbstractArray{T, 2})
Create a LaTeX array environment using [`latexraw`](@ref).
# Examples
```julia
arr = [1 2; 3 4]
latexarray(arr)
```
```math
"\\begin{equation}\n\\left[\n\\begin{array}{cc}\n1 & 2\\\\ \n3 & 4\\\\ \n\\end{array}\n\\right]\n\\end{equation}\n"
```
"""
latexarray(args...; kwargs...) = process_latexify(args...;kwargs...,env=:array)
function _latexarray(
arr::AbstractArray; adjustment=:c, transpose=false,
double_linebreak=false, starred=false, arraystyle=:square, kwargs...
)
if !(0 < ndims(arr) < 3)
sentinel = get(kwargs, :sentinel, nothing)
isnothing(sentinel) && throw(UnrepresentableException("n-dimensional tensors with n≠1,2"))
return sentinel
end
transpose && (arr = permutedims(arr))
rows, columns = axes(arr, 1), axes(arr, 2)
eol = double_linebreak ? " \\\\\\\\\n" : " \\\\\n"
if adjustment isa AbstractArray
adjustmentstring = join(adjustment)
else
adjustmentstring = string(adjustment)^length(columns)
end
need_adjustmentstring = true
if arraystyle in AMSMATH_MATRICES ||
arraystyle isa NTuple{3,String} && arraystyle[3] == "matrix"
need_adjustmentstring = false
end
if arraystyle isa String
arraystyle = ("", "", arraystyle)
elseif arraystyle isa Symbol
arraystyle = ARRAYSTYLES[arraystyle]
end
str = string(arraystyle[1], "\\begin{", arraystyle[3], "}")
if need_adjustmentstring
str = str * string("{", adjustmentstring, "}\n")
else
str = str * "\n"
end
for i in rows, j in columns
if isassigned(arr, i, j)
str *= latexraw(arr[i,j]; kwargs...)
else
str *= raw"\cdot"
end
j == last(columns) ? (str *= eol) : (str *= " & ")
end
str *= string("\\end{", arraystyle[3], '}', arraystyle[2])
latexstr = LaTeXString(str)
# COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
_latexarray(args::AbstractArray...; kwargs...) = _latexarray(safereduce(hcat, args); kwargs...)
_latexarray(arg::AbstractDict; kwargs...) = _latexarray(collect(keys(arg)), collect(values(arg)); kwargs...)
_latexarray(arg::Tuple...; kwargs...) = _latexarray([collect(i) for i in arg]...; kwargs...)
function _latexarray(arg::Tuple; kwargs...)
if first(arg) isa Tuple || first(arg) isa AbstractArray
return _latexarray([collect(i) for i in arg]...; kwargs...)
end
return _latexarray(collect(arg); kwargs...)
end
const ARRAYSTYLES = Dict{Symbol, NTuple{3, String}}(
:array=>("", "", "array"),
:square=>("\\left[\n", "\n\\right]", "array"),
:round=>("\\left(\n", "\n\\right)", "array"),
:curly=>("\\left\\{\n", "\n\\right\\}", "array"),
:matrix=>("","","matrix"),
:pmatrix=>("","","pmatrix"),
:bmatrix=>("","","bmatrix"),
:Bmatrix=>("","","Bmatrix"),
:vmatrix=>("","","vmatrix"),
:Vmatrix=>("","","Vmatrix"),
)
const AMSMATH_MATRICES = [:matrix, :pmatrix, :bmatrix, :Bmatrix, :vmatrix, :Vmatrix]
================================================
FILE: src/latexbracket.jl
================================================
latexbracket(args...; kwargs...) = process_latexify(args...; kwargs..., env=:bracket)
function _latexbracket(x; kwargs...)
latexstr = LaTeXString( "\\[\n" * latexraw(x; kwargs...) * "\\]\n")
COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
_latexbracket(args...; kwargs...) = latexbracket(args; kwargs...)
================================================
FILE: src/latexequation.jl
================================================
latexequation(args...; kwargs...) = process_latexify(args...; kwargs..., env=:equation)
function _latexequation(eq; starred=false, kwargs...)
latexstr = latexraw(eq; kwargs...)
str = "\\begin{equation$(starred ? "*" : "")}\n"
str *= latexstr
str *= "\n"
str *= "\\end{equation$(starred ? "*" : "")}\n"
latexstr = LaTeXString(str)
COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
================================================
FILE: src/latexify_function.jl
================================================
@doc doc"""
latexify(args...; kwargs...)
Latexify a string, an expression, an array or other complex types.
```julia-repl
julia> latexify("x+y/(b-2)^2")
L"$x + \frac{y}{\left( b - 2 \right)^{2}}$"
julia> latexify(:(x/(y+x)^2))
L"$\frac{x}{\left( y + x \right)^{2}}$"
julia> latexify(["x/y" 3//7 2+3im; 1 :P_x :(gamma(3))])
L"\begin{equation}
\left[
\begin{array}{ccc}
\frac{x}{y} & \frac{3}{7} & 2+3\mathit{i} \\
1 & P_{x} & \Gamma\left( 3 \right) \\
\end{array}
\right]
\end{equation}
"
```
"""
function latexify(args...; kwargs...)
kwargs = merge(default_kwargs, kwargs)
result = process_latexify(args...; kwargs...)
should_render = get(kwargs, :render, false)
should_render isa Bool || throw(ArgumentError(
"The keyword argument `render` must be either `true` or `false`. Got $should_render"
))
should_render && render(result)
COPY_TO_CLIPBOARD && clipboard(result)
AUTO_DISPLAY && display(result)
return result
end
function process_latexify(args...; kwargs...)
## Let potential recipes transform the arguments.
args, kwargs = apply_recipe(args...; kwargs...)
## If the environment is unspecified, use auto inference.
env = get(kwargs, :env, :auto)
latex_function = infer_output(env, args...)
result = latex_function(args...; kwargs...)
end
apply_recipe(args...; kwargs...) = (args, kwargs)
# These functions should only be called from inside `latexify()`, so that
# `apply_recipe` gets a chance to change args
const OUTPUTFUNCTIONS = Dict(
:inline => _latexinline,
:tabular => _latextabular,
:table => _latextabular,
:raw => _latexraw,
:array => _latexarray,
:align => _latexalign,
:aligned => (args...; kwargs...) -> _latexbracket(_latexalign(args...; kwargs..., aligned=true, starred=false); kwargs...),
:eq => _latexequation,
:equation => _latexequation,
:bracket => _latexbracket,
:mdtable => _mdtable,
:mdtext => _mdtext,
)
function infer_output(env, args...)
env === :auto && return get_latex_function(args...)
# Must be like this, because items in OUTPUTFUNCTIONS must be defined
env in [:arrows, :chem, :chemical, :arrow] && return _chemical_arrows
return OUTPUTFUNCTIONS[env]
end
"""
get_latex_function(args...)
Use overloading to determine which latex environment to output.
This determines the default behaviour of `latexify()` for different inputs.
"""
get_latex_function(args...) = _latexinline
get_latex_function(args::AbstractArray...) = _latexequation
get_latex_function(args::AbstractDict) = (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...)
get_latex_function(args::Tuple...) = (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...)
get_latex_function(arg::LaTeXString) = (arg; kwargs...) -> arg
function get_latex_function(x::AbstractArray{T}) where T <: AbstractArray
try
x = safereduce(hcat, x)
return (args...; kwargs...) -> _latexequation(_latexarray(args...; kwargs...); kwargs...)
catch
return _latexinline
end
end
get_latex_function(lhs::AbstractVector, rhs::AbstractVector) = _latexalign
================================================
FILE: src/latexinline.jl
================================================
latexinline(args...;kwargs...) = process_latexify(args...;kwargs...,env=:inline)
function _latexinline(x; kwargs...)
latexstr = latexstring( process_latexify(x; kwargs...,env=:raw) )
COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
================================================
FILE: src/latexoperation.jl
================================================
"""
latexoperation(ex::Expr, prevOp::AbstractArray)
Translate a simple operation given by `ex` to LaTeX maths syntax.
This uses the information about the previous operations to decide if
a parenthesis is needed.
"""
function latexoperation(ex::Expr, prevOp::AbstractArray; kwargs...)::String
# If we used `mult_symbol` and `index` as keyword arguments before `kwargs...`
# and they are indeed contained in `kwargs`, they would get lost when
# passing `kwargs...` to `latexraw`below. Thus, we need to set default
# values as follows.
mult_symbol = get(kwargs, :mult_symbol, "\\cdot")
index = get(kwargs, :index, :bracket)
if haskey(kwargs, :cdot)
cdot = kwargs[:cdot]
mult_symbol = cdot ? "\\cdot" : ""
Base.depwarn("Latexify received the deprecated keyword argument cdot = $cdot and converted it to mult_symbol = \"$mult_symbol\". Pass the latter directly to remove this warning.", :latexoperation)
end
op = ex.args[1]
string(op)[1] == '.' && (op = Symbol(string(op)[2:end]))
filter!(x -> !(x isa LineNumberNode), ex.args)
args = map(i -> typeof(i) ∉ (String, LineNumberNode) ? latexraw(i; kwargs...) : i, ex.args)
# Remove math italics for variables (i.e. words) longer than 2 characters.
# args = map(i -> (i isa String && all(map(isletter, collect(i))) && length(i) > 2) ? "{\\rm $i}" : i, args)
if ex.head == :latexifymerge
if all(prevOp .== :none)
return join(args)
else
return "$(args[1])\\left( $(join(args[2:end])) \\right)"
end
end
if ex.head == :call && op isa Function
# Convert Expr(:call, sin, 3) to Expr(:call, :sin, 3)
op = Symbol(op)
end
if op in [:/, ://]
return "\\frac{$(args[2])}{$(args[3])}"
elseif op in [:*]
str=""
for i ∈ eachindex(args)[2:end]
arg = args[i]
(precedence(prevOp[i]) < precedence(op) || (ex.args[i] isa Complex && !iszero(ex.args[i].re))) && (arg = "\\left( $arg \\right)")
str = string(str, arg)
i == length(args) || (str *= mult_symbol == "" ? " " : " $mult_symbol ")
end
return str
elseif op in [:+]
str = ""
for i ∈ eachindex(args)[2:end]
arg = args[i]
precedence(prevOp[i]) < precedence(op) && (arg = "\\left( $arg \\right)")
str = string(str, arg)
i == length(args) || (str *= " + ")
end
str = replace(str, r"\+ *([\-±∓])"=>s"\1")
return str
elseif op in [:±, :∓]
str = ""
if length(args) == 2
# unary
precedence(prevOp[2]) <= precedence(op) && return "$(arithmetic_operators[op])\\left( $(args[2]) \\right)"
return "$(arithmetic_operators[op]) $(args[2])"
end
for i ∈ eachindex(args)[2:end]
arg = args[i]
precedence(prevOp[i]) < precedence(op) && (arg = "\\left( $arg \\right)")
str = string(str, arg)
i == length(args) || (str *=" $(arithmetic_operators[op]) ")
end
return str
elseif op in [:-]
if length(args) == 2
if prevOp[2] == :none && string(args[2])[1] == '-'
return " + " * string(args[2])[2:end]
elseif prevOp[2] == :none && string(args[2])[1] == '+'
return " - " * string(args[2])[2:end]
elseif precedence(prevOp[2]) ≤ precedence(:-) || (ex.args[2] isa Complex && !iszero(ex.args[2].re))
return " - \\left( $(args[2]) \\right)"
end
return " - $(args[2])"
end
if precedence(prevOp[3]) <= precedence(:-) ||
(ex.args[3] isa Complex && !iszero(ex.args[3].re))
args[3] = "\\left( $(args[3]) \\right)"
end
precedence(prevOp[2]) < precedence(:-) && (args[2] = "\\left( $(args[2]) \\right)")
if prevOp[3] ∈ keys(unary_operators)
return "$(args[2]) $(replace(args[3], unary_operators[prevOp[3]] => unary_opposites[prevOp[3]]; count=1))"
end
return replace("$(args[2]) - $(args[3])", r"- *-"=>"+ ")
elseif op in [:^]
if prevOp[2] in trigonometric_functions
str = get(functions, prevOp[2], "\\$(prevOp[2])")
return replace(args[2], str => "$(str)^{$(args[3])}")
end
if (prevOp[2] != :none) || (ex.args[2] isa Real && sign(ex.args[2]) == -1) || (ex.args[2] isa Complex && !iszero(ex.args[2].re)) || (ex.args[2] isa Rational)
args[2]="\\left( $(args[2]) \\right)"
end
return "$(args[2])^{$(args[3])}"
elseif (ex.head in (:(=), :function)) && length(args) == 2
return "$(args[1]) = $(args[2])"
elseif op == :(!)
return "\\neg $(args[2])"
elseif op == :(:) && length(args) == 4
return "$(args[2]) \\underset{$(args[3])}{$(binary_operators[:(:)])} $(args[4])"
end
if ex.head == :.
if length(ex.args) >= 2 && (
ex.args[2] isa Expr && ex.args[2].head == :tuple
||
ex.args[2] isa String
)
# broadcasted function call `f.(x)`
ex.head = :call
else
# property or field `f.x`
return "$(ex.args[1]).$(ex.args[2] isa QuoteNode ? ex.args[2].value : ex.args[2])"
end
end
if op in keys(binary_operators) && length(args) == 3
str = ""
if (precedence(prevOp[2]) < precedence(op)) ||
(precedence(prevOp[2]) == precedence(op) && associativity(op) != :left)
str = str*"\\left( $(args[2]) \\right)"
else
str = str*args[2]
end
str = str*" $(binary_operators[op]) "
if (precedence(prevOp[3]) < precedence(op)) ||
(precedence(prevOp[3]) == precedence(op) && associativity(op) != :right)
str = str*"\\left( $(args[3]) \\right)"
else
str = str*args[3]
end
return str
end
### Check for chained comparison operators
if ex.head == :comparison
for argind in 2:2:length(args)
arg = args[argind]
string(arg)[1] == '.' && (arg = Symbol(string(arg)[2:end]))
args[argind] = get(comparison_operators, arg, string(arg))
end
str = join(args, " ")
return str
end
if op in keys(functions)
return "$(functions[op])\\left( $(join(args[2:end], ", ")) \\right)"
end
op == :abs && return "\\left|$(args[2])\\right|"
op == :abs2 && return "\\left|$(args[2])\\right|^{2}"
op == :floor && return "\\left\\lfloor $(last(args))\\right\\rfloor "
op == :ceil && return "\\left\\lceil $(last(args))\\right\\rceil "
op == :round && return "\\left\\lfloor $(last(args))\\right\\rceil "
if op == :norm
length(args) == 2 && return "\\left\\|$(args[2])\\right\\|"
return "\\left\\|$(args[2])\\right\\|_{$(args[3])}"
end
op == :exp && return "e^{$(args[2])}"
op in (:sqrt, :√, :ssqrt) && return "\\sqrt{$(args[2])}"
op in (:cbrt, :∛, :scbrt) && return "\\sqrt[3]{$(args[2])}"
op in (:fourthroot, :∜) && return "\\sqrt[4]{$(args[2])}"
op in (:sum, :prod) && return "\\$(op) $(args[2])"
op == :binomial && return "\\binom{$(args[2])}{$(args[3])}"
## Leave math italics for single-character operator names (e.g., f(x)).
# convert subscript symbols to \_ if necessary, and make long function names
# upright
opname = operator_name(op; kwargs...);
if ex.head == :ref
if index == :subscript
if prevOp[1] == :ref
container = "\\left( $(op) \\right)"
else
container = opname
end
return "$(container)_{$(join(args[2:end], ","))}"
elseif index == :bracket
argstring = join(args[2:end], ", ")
prevOp[1] == :ref && return "$op\\left[$argstring\\right]"
return "$opname\\left[$argstring\\right]"
else
throw(ArgumentError("Incorrect `index` keyword argument to latexify. Valid values are :subscript and :bracket"))
end
end
if ex.head == :macrocall && ex.args[1] == Symbol("@__dot__")
return string(ex.args[end])
end
if ex.head == :macrocall
ex.head = :call
end
if ex.head == :call
if length(args) == 1
return "$opname()"
elseif args[2] isa String && occursin("=", args[2])
return "$opname\\left( $(join(args[3:end], ", ")); $(args[2]) \\right)"
else
return "$opname\\left( $(join(args[2:end], ", ")) \\right)"
end
end
if ex.head == :tuple
# return "\\left(" * join(ex.args, ", ") * "\\right)"
return join(ex.args, ", ")
end
ex.head == Symbol("'") && return "$(args[1])'"
## Enable the parsing of kwargs in a function definition
ex.head == :kw && return "$(args[1]) = $(args[2])"
ex.head == :parameters && return join(args, ", ")
## Use the last expression in a block.
## This is somewhat shady but it helps with latexifying functions.
ex.head == :block && return args[end]
## Sort out type annotations. Mainly for function arguments.
ex.head == :(::) && length(args) == 1 && return "::$(args[1])"
ex.head == :(::) && length(args) == 2 && return "$(args[1])::$(args[2])"
## Pass back values that were explicitly returned.
ex.head == :return && length(args) == 1 && return args[1]
## Case enviroment for if statements and ternary ifs.
if ex.head in (:if, :elseif)
textif::String = "\\text{if }"
begincases::String = ex.head == :if ? "\\begin{cases}\n" : ""
endcases::String = ex.head == :if ? "\n\\end{cases}" : ""
if length(args) == 3
# Check if already parsed elseif as args[3]
haselseif::Bool = occursin(Regex("\\$textif"), args[3])
otherwise::String = haselseif ? "" : " & \\text{otherwise}"
return """$begincases$(args[2]) & $textif $(args[1])\\\\
$(args[3])$otherwise$endcases"""
elseif length(args) == 2
return "$begincases$(args[2]) & $textif $(args[1])$endcases"
end
end
## Conditional operators converted to logical operators.
ex.head == :(&&) && length(args) == 2 && return "$(args[1]) \\wedge $(args[2])"
ex.head == :(||) && length(args) == 2 && return "$(args[1]) \\vee $(args[2])"
## Anonymous function definition
ex.head == :(->) && length(args) == 2 && return "$(args[1]) \\mapsto $(args[2])"
## if we have reached this far without a return, then error.
sentinel = get(kwargs, :sentinel, nothing)
isnothing(sentinel) && throw(UnrecognizedExpressionException(ex))
return sentinel
end
latexoperation(sym::Symbol, prevOp::AbstractArray; kwargs...) = "$sym"
function convert_subscript!(ex::Expr, kwargs...)
for i in 1:length(ex.args)
arg = ex.args[i]
if arg isa Symbol
ex.args[i] = convert_subscript(arg, kwargs...)
end
end
return nothing
end
function convert_subscript(str::String; snakecase=false, function_name=false, kwargs...)
subscript_list = split(str, r"\\?_")
if snakecase
return join(subscript_list, "\\_")
else
mainscript = subscript_list[1]
if function_name && length(mainscript) > 1 && isascii(mainscript)
mainscript = "\\mathrm{$mainscript}"
end
length(subscript_list) == 1 && return string(mainscript)
subscript = join(subscript_list[2:end], "\\_")
return "$(mainscript)_{$subscript}"
end
end
convert_subscript(sym::Symbol; kwargs...) = convert_subscript(string(sym); kwargs...)
convert_subscript(n::Number; kwargs...) = convert_subscript(string(n); kwargs...)
operator_name(sym; kwargs...) = convert_subscript(sym; kwargs..., function_name=true)
operator_name(lnn::LineNumberNode;kwargs...) = ""
function operator_name(ex::Expr; kwargs...)
if ex.head == :. && ex.args[2] isa QuoteNode
return convert_subscript(ex.args[1]; function_name=true, kwargs...) * "." * convert_subscript(ex.args[2].value; function_name=true, kwargs...)
else
error("I don't know what this is")
end
end
function operator_name(str::LaTeXString; kwargs...)
return str
end
"""
precedence(op)
The operator precedence of `op` strictly with regards to parenthesization.
If `f(a, g(b, c))` must be written `a f (b g c)` then precedence(:f) > precedence(:g)
"""
function precedence(op::Symbol)
startswith(string(op), "unary") && return Base.prec_power # Putting unary on par with :^, because there are no integers between 14 and 15. Should consider putting it with :<< instead
op ∈ [:comparison, :issubset] && return Base.prec_comparison
#op == :∀ && return Base.prec_control_flow
op == :(:) && return 10
prec = Base.operator_precedence(op)
prec == 0 && return 100 # Base treats unknown as parenthesizable, we want no parenthesis if uncertain
return prec
end
function associativity(op::Symbol)
startswith(string(op), "unary") && return :right
op == :comparison && return :none
op == :issubset && return :none
return Base.operator_associativity(op)
end
================================================
FILE: src/latexraw.jl
================================================
@doc doc"""
latexraw(arg)
Generate LaTeX equations from `arg`.
Parses expressions, ParameterizedFunctions, SymEngine.Base and arrays thereof.
Returns a string formatted for LaTeX.
# Examples
## using expressions
```jldoctest
expr = :(x/(y+x))
latexraw(expr)
# output
"\\frac{x}{y + x}"
```
```jldoctest
expr = Meta.parse("x/(y+x)")
latexraw(expr)
# output
"\\frac{x}{y + x}"
```
## using ParameterizedFunctions
```julia
using DifferentialEquations;
f = @ode_def feedback begin
dx = y/c_1 - x
dy = x^c_2 - y
end c_1=>1.0 c_2=>1.0
latexraw(f)
# output
2-element Array{String,1}:
"dx/dt = \\frac{y}{c_{1}} - x"
"dy/dt = x^{c_{2}} - y"
```
## using SymEngine
```jldoctest
using SymEngine
@vars x y
symExpr = x + x + x*y*y
latexraw(symExpr)
# output
"2 \\cdot x + x \\cdot y^{2}"
```
"""
latexraw(args...; kwargs...) = process_latexify(args...; kwargs..., env=:raw)
function _latexraw(inputex::Expr; convert_unicode=true, kwargs...)
## Pass all arrays or matrices in the expr to latexarray
inputex = postwalk(x -> Meta.isexpr(x, [:hcat, :vcat, :vect, :typed_vcat, :typed_hcat]) ?
latexarray(expr_to_array(x); kwargs...)
: x,
inputex)
recurseexp!(lstr::LaTeXString) = lstr.s
function recurseexp!(ex)
prevOp = fill(:none, length(ex.args))
if Meta.isexpr(ex, :call) && ex.args[1] in (:sum, :prod) && Meta.isexpr(ex.args[2], :generator)
op = ex.args[1]
term = latexraw(ex.args[2].args[1])
gen = ex.args[2].args[2]
itervar = latexraw(gen.args[1])
if Meta.isexpr(gen.args[2], :call) && gen.args[2].args[1] == :(:)
# sum(x_n for n in n_0:N) => \sum_{n=n_0}^{N} x_n
lower = latexraw(gen.args[2].args[2])
upper = latexraw(gen.args[2].args[end])
return "\\$(op)_{$(itervar) = $(lower)}^{$(upper)} $term"
elseif gen.args[2] in (:_, :(:))
# sum(x_n for n in :) => \sum_{n} x_n
return "\\$(op)_{$(itervar)} $term"
else
# sum(x_n for n in N) => \sum_{n \in N} x_n
set = latexraw(gen.args[2])
return "\\$(op)_{$(itervar) \\in $set} $term"
end
else
for i in 1:length(ex.args)
prevOp[i] = _getoperation(ex.args[i])
if isa(ex.args[i], Expr)
ex.args[i] = recurseexp!(ex.args[i])
elseif ex.args[i] isa AbstractArray
ex.args[i] = latexraw(ex.args[i]; kwargs...)
end
end
return latexoperation(ex, prevOp; convert_unicode=convert_unicode, kwargs...)
end
end
ex = deepcopy(inputex)
str = recurseexp!(ex)
convert_unicode && (str = unicode2latex(str))
return LaTeXString(str)
end
function _latexraw(args...; kwargs...)
length(args) > 1 && return _latexraw(args; kwargs...)
sentinel = get(kwargs, :sentinel, nothing)
isnothing(sentinel) && throw(NoRecipeException(typeof(args[1])))
return sentinel
end
_latexraw(arr::Union{AbstractArray, Tuple}; kwargs...) = _latexarray(arr; kwargs...)
_latexraw(i::Nothing; kwargs...) = ""
_latexraw(i::SubString; parse=true, kwargs...) = latexraw(parse ? Meta.parse(i) : i; kwargs...)
_latexraw(i::SubString{LaTeXStrings.LaTeXString}; kwargs...) = i
_latexraw(i::Rational; kwargs...) = i.den == 1 ? latexraw(i.num; kwargs...) : latexraw(:($(i.num)/$(i.den)); kwargs...)
_latexraw(i::QuoteNode; kwargs...) = _latexraw(i.value; kwargs...)
_latexraw(i::Function; kwargs...) = _latexraw(Symbol(i); kwargs...)
function _latexraw(z::Complex; kwargs...)
if iszero(z.re)
isone(z.im) && return LaTeXString(get(kwargs, :imaginary_unit, "\\mathit{i}"))
isone(-z.im) && return LaTeXString("-$(get(kwargs, :imaginary_unit, "\\mathit{i}"))")
return LaTeXString("$(latexraw(z.im))$(get(kwargs, :imaginary_unit, "\\mathit{i}"))")
end
return LaTeXString("$(latexraw(z.re;kwargs...))$(z.im < 0 ? "-" : "+" )$(latexraw(abs(z.im);kwargs...))$(get(kwargs, :imaginary_unit, "\\mathit{i}"))")
end
#latexraw(i::DataFrames.DataArrays.NAtype) = "\\textrm{NA}"
_latexraw(str::LaTeXStrings.LaTeXString; kwargs...) = str
function _latexraw(i::Number; fmt=PlainNumberFormatter(), kwargs...)
try isinf(i) && return LaTeXString("$(sign(i) == -1 ? "-" : "")\\infty") catch; end
fmt isa String && (fmt = PrintfNumberFormatter(fmt))
return fmt(i)
end
function _latexraw(i::Char; convert_unicode=true, kwargs...)
LaTeXString(convert_unicode ? unicode2latex(string(i)) : string(i))
end
function _latexraw(i::Symbol; convert_unicode=true, snakecase=false, safescripts=false, kwargs...)
str = get(special_symbols, i, string(i))
str = convert_subscript(str; snakecase=snakecase)
convert_unicode && (str = unicode2latex(str; safescripts=safescripts))
return LaTeXString(str)
end
_latexraw(i::String; parse=true, kwargs...) = _latexraw(Val(parse), i; kwargs...)
_latexraw(::Val{false}, i::String; convert_unicode=true, kwargs...) =
LaTeXString(convert_unicode ? unicode2latex(i) : i)
function _latexraw(::Val{true}, i::String; kwargs...)
try
ex = Meta.parse(i)
return latexraw(ex; kwargs...)
catch err
err isa Meta.ParseError && rethrow(MathParseError(i))
rethrow(err)
end
end
_latexraw(i::Missing; kwargs...) = "\\textrm{NA}"
"""
_getoperation(x)
Check if `x` represents something that could affect the vector of previous operations.
`:none` by default, recipes can use `operation-->:something` to hack this.
"""
function _getoperation(ex::Expr)
ex.head == :comparison && return :comparison
ex.head == :ref && return :ref
ex.head == :call || return :none
if length(ex.args) > 1 && (op = ex.args[1]) isa Symbol
if length(ex.args) == 2
# These are unary operators
op == :- && return :unaryminus
op == :+ && return :unaryplus
op == :± && return :unaryplusminus
op == :∓ && return :unaryminusplus
end
return op
end
return :none
end
_getoperation(x) = :none
================================================
FILE: src/latextabular.jl
================================================
latextabular(args...; kwargs...) = process_latexify(args...; kwargs..., env=:tabular)
function _latextabular(arr::AbstractMatrix; latex::Bool=true, booktabs::Bool=false, head=[], side=[], adjustment=:c, transpose=false, kwargs...)
transpose && (arr = permutedims(arr, [2,1]))
if !isempty(head)
arr = vcat(safereduce(hcat, head), arr)
@assert length(head) == size(arr, 2) "The length of the head does not match the shape of the input matrix."
end
if !isempty(side)
length(side) == size(arr, 1) - 1 && (side = [""; side])
@assert length(side) == size(arr, 1) "The length of the side does not match the shape of the input matrix."
arr = hcat(side, arr)
end
(rows, columns) = size(arr)
if ~isa(adjustment, AbstractArray)
adjustment = fill(adjustment, columns)
end
adjustmentstring = join(adjustment)
str = "\\begin{tabular}{$adjustmentstring}\n"
if booktabs
str *= "\\toprule\n"
end
formatter = get(kwargs, :fmt, nothing)
if formatter isa String
formatter = PrintfNumberFormatter(formatter)
end
if formatter isa SiunitxNumberFormatter && any(==(:S), adjustment)
# Do not format cell contents, "S" column type handles it
formatter = string
end
if latex
arr = latexinline.(arr; kwargs...)
elseif ~isnothing(formatter)
arr = map(x -> x isa Number ? formatter(x) : x, arr)
end
# print first row
str *= join(arr[1,:], " & ")
str *= "\\\\\n"
if booktabs && !isempty(head)
str *= "\\midrule\n"
end
for i in 2:size(arr, 1)
str *= join(arr[i,:], " & ")
str *= "\\\\\n"
end
if booktabs
str *= "\\bottomrule\n"
end
str *= "\\end{tabular}\n"
latexstr = LaTeXString(str)
COPY_TO_CLIPBOARD && clipboard(latexstr)
return latexstr
end
_latextabular(vec::AbstractVector; kwargs...) = latextabular(safereduce(hcat, vec); kwargs...)
_latextabular(vectors::AbstractVector...; kwargs...) = latextabular(safereduce(hcat, vectors); kwargs...)
_latextabular(dict::AbstractDict; kwargs...) = latextabular(hcat(collect(keys(dict)), collect(values(dict))); kwargs...)
================================================
FILE: src/macros.jl
================================================
"""
@latexify expression
Create `LaTeXString` representing `expression`.
Variables and expressions can be interpolated with `\$`.
Keyword arguments can be supplied to `latexify` by appending to the argument.
# Examples
```julia-repl
julia> @latexify x^2 + 3/2
L"\$x^{2} + \\frac{3}{2}\$"
julia> @latexify x^2 + \$(3/2)
L"\$x^{2} + 1.5\$"
julia> @latexify x^2 + 3/2 env=:raw
L"x^{2} + \\frac{3}{2}"
```
See also [`latexify`](@ref), [`@latexrun`](@ref), [`@latexdefine`](@ref).
"""
macro latexify(expr, kwargs...)
return esc(
Expr(
:call, :latexify, Expr(:parameters, _extractparam.(kwargs)...), Meta.quot(expr)
),
)
end
"""
@latexrun expression
Latexify and evaluate `expression`. Useful for expressions with side effects, like assignments.
# Examples
```julia-repl
julia> @latexrun y = 3/2 + \$(3/2)
L"\$y = \\frac{3}{2} + 1.5\$"
julia> y
3.0
```
See also [`@latexify`](@ref), [`@latexdefine`](@ref).
"""
macro latexrun(expr, kwargs...)
return esc(
Expr(
:block,
_executable(expr),
Expr(
:call,
:latexify,
Expr(:parameters, _extractparam.(kwargs)...),
Meta.quot(expr),
),
),
)
end
"""
@latexdefine expression
Latexify `expression`, followed by an equals sign and the return value of its evaluation.
Any side effects of the expression, like assignments, are evaluated as well.
The RHS can be formatted or otherwise transformed by supplying a function as kwarg `post`.
# Examples
```julia-repl
julia> @latexdefine y = 3/2 + \$(3/2) env=:equation
L"\\begin{equation}
y = \\frac{3}{2} + 1.5 = 3.0
\\end{equation}
"
julia> y
3.0
julia> @latexdefine y=π post=round
L"\$x = \\pi = 3.0\$"
```
See also [`@latexify`](@ref), [`@latexrun`](@ref).
"""
macro latexdefine(expr, kwargs...)
params = _extractparam.(kwargs)
post = :identity
for param in params
if param === :post
post = :post
break
end
if param isa Expr && param.args[1] === :post
post = param.args[2]
break
end
end
return esc(
Expr(
:call,
:latexify,
Expr(:parameters, _extractparam.(kwargs)...),
Expr(:call, :Expr, QuoteNode(:(=)), Meta.quot(expr), Expr(:call, post, _executable(expr))),
),
)
end
function _executable(expr)
return postwalk(expr) do ex
if Meta.isexpr(ex, :$)
return ex.args[1]
end
return ex
end
end
_extractparam(arg::Symbol) = arg
_extractparam(arg::Expr) = Expr(:kw, arg.args[1], arg.args[2])
================================================
FILE: src/md.jl
================================================
function md(args...; env=:auto, kwargs...)
md_function = infer_md_output(env, args...)
m = md_function(args...; kwargs...)
COPY_TO_CLIPBOARD && clipboard(m)
AUTO_DISPLAY && display(m)
return m
end
mdalign(args...; kwargs...) = latexalign(args...; kwargs...)
mdarray(args...; kwargs...) = latexarray(args...; kwargs...)
md_chemical_arrows(args...; kwargs...) = chemical_arrows(args...; kwargs...)
const MDOUTPUTS = Dict(
:table => mdtable,
:text => mdtext,
:align => mdalign,
:array => mdarray,
:inline => latexinline
)
function infer_md_output(env, args...)
env === :auto && return get_md_function(args...)
env in [:arrows, :chem, :chemical, :arrow] && return md_chemical_arrows
return MDOUTPUTS[env]
end
"""
get_md_function(args...)
Use overloading to determine what MD output to generate.
This determines the default behaviour of `md()` for different inputs.
"""
get_md_function(args...) = mdtext
get_md_function(args::AbstractArray...) = mdtable
get_md_function(args::AbstractDict) = mdtable
get_md_function(args::Tuple) = mdtable
================================================
FILE: src/mdtable.jl
================================================
@doc doc"""
mdtable(array; latex=true, head=[], side=[], transpose=false)
Latexify the input and output a markdown-formatted table.
```julia
julia> using Latexify
juila> copy_to_clipboard(true)
julia> M = ["x/y" 1//2
"p_m" "e^2"]
julia> mdtable(M)
```
| $\frac{x}{y}$ | $\frac{1}{2}$ |
| -------------:| -------------:|
| $p_{m}$ | $e^{2}$ |
```julia
julia> head = ["Column 1", "Column 2"]
julia> side = ["Row 1", "Row 2"]
julia> mdtable(M; head=head, side=side)
```
| . | Column 1 | Column 2 |
| -----:| -------------:| -------------:|
| Row 1 | $\frac{x}{y}$ | $\frac{1}{2}$ |
| Row 2 | $p_{m}$ | $e^{2}$ |
The value in the top right corner can be set if you let the `side` vector be one element
larger than the number of rows of your input:
```julia
julia> side = ["Corner", "Row 1", "Row 2"]
julia> mdtable(M; head=head, side=side)
```
| Corner | Column 1 | Column 2 |
| ------:| -------------:| -------------:|
| Row 1 | $\frac{x}{y}$ | $\frac{1}{2}$ |
| Row 2 | $p_{m}$ | $e^{2}$ |
The `head` and `side` vectors are not latexifed, but you can easily do this yourself:
```julia
julia> head = ["p_1", "p_2"]
julia> mdtable(M; head=latexinline(head))
```
| $p_{1}$ | $p_{2}$ |
| -------------:| -------------:|
| $\frac{x}{y}$ | $\frac{1}{2}$ |
| $p_{m}$ | $e^{2}$ |
"""
mdtable(args...; kwargs...) = latexify(args...; kwargs..., env=:mdtable)
function _mdtable(M::AbstractMatrix; latex::Bool=true, escape_underscores=false, head=[], side=[], transpose=false, adjustment=nothing, kwargs...)
transpose && (M = permutedims(M, [2,1]))
if latex
M = _latexinline.(M; kwargs...)
elseif haskey(kwargs, :fmt)
formatter = kwargs[:fmt] isa String ? PrintfNumberFormatter(kwargs[:fmt]) : kwargs[:fmt]
M = map(x -> x isa Number ? formatter(x) : x, M)
end
if !isempty(head)
M = vcat(safereduce(hcat, head), M)
@assert length(head) == size(M, 2) "The length of the head does not match the shape of the input matrix."
end
if !isempty(side)
length(side) == size(M, 1) - 1 && (side = [LaTeXString("∘"); side])
@assert length(side) == size(M, 1) "The length of the side does not match the shape of the input matrix."
M = hcat(side, M)
end
if adjustment isa AbstractArray
headerrules = get_header_rule.(adjustment)
else
headerrules = fill(get_header_rule(adjustment), size(M, 2))
end
t = "| " * join(M[1,:], " | ") * " |\n"
size(M, 1) > 1 && (t *= "| " * join(headerrules, " | ") * " |\n")
for i in 2:size(M,1)
t *= "| " * join(M[i,:], " | ") * " |\n"
end
escape_underscores && (t = replace(t, "_"=>"\\_"))
t = Markdown.parse(t)
COPY_TO_CLIPBOARD && clipboard(t)
return t
end
_mdtable(v::AbstractArray; kwargs...) = _mdtable(reshape(v, (length(v), 1)); kwargs...)
_mdtable(v::AbstractArray...; kwargs...) = _mdtable(safereduce(hcat, v); kwargs...)
_mdtable(d::AbstractDict; kwargs...) = _mdtable(collect(keys(d)), collect(values(d)); kwargs...)
_mdtable(arg::Tuple; kwargs...) = _mdtable(safereduce(hcat, [collect(i) for i in arg]); kwargs...)
get_header_rule(::Nothing) = "-------"
function get_header_rule(adjustment::Symbol)
adjustment === :c && return ":----:"
adjustment === :l && return ":-----"
adjustment === :r && return "-----:"
throw(ArgumentError("Unknown `adjustment` argument \"$adjustment\""))
end
================================================
FILE: src/mdtext.jl
================================================
mdtext(args...; kwargs...) = latexify(args...; kwargs..., env=:mdtext)
function _mdtext(s::String; escape_underscores = false, kwargs...)
escape_underscores && (s = replace(s, "_"=>"\\_"))
m = Markdown.parse(s)
return m
end
================================================
FILE: src/numberformatters.jl
================================================
abstract type AbstractNumberFormatter end
(::AbstractNumberFormatter)(x) = string(x)
const float_regex = r"(?'mantissa'(?'before_dp'(?'sign'-?)(?'before_dp_nosign'\d+))(\.(?'after_dp'\d+))?)(?'e_or_E'e)(?'raw_exp'(?'sign_exp'-?)\+?0*(?'mag_exp'\d+))"i
struct PlainNumberFormatter <: AbstractNumberFormatter end
struct PrintfNumberFormatter <: AbstractNumberFormatter
fmt::String
f::Function
PrintfNumberFormatter(fmt::String) = new(fmt, Format.generate_formatter(fmt))
end
(f::PrintfNumberFormatter)(x) = f.f(x)
struct StyledNumberFormatter <: AbstractNumberFormatter
fmt::String
f::Function
StyledNumberFormatter(fmt::String="%.4g") = new(fmt, Format.generate_formatter(fmt))
end
StyledNumberFormatter(significant_digits::Int) = StyledNumberFormatter("%.$(significant_digits)g")
(f::StyledNumberFormatter)(x::AbstractFloat) =
replace(f.f(x), float_regex => s"\g<mantissa> \\mathrm{\g<e_or_E>}{\g<sign_exp>\g<mag_exp>}")
(f::StyledNumberFormatter)(x::Unsigned) = "\\mathtt{0x$(string(x; base=16, pad=2sizeof(x)))}"
struct FancyNumberFormatter <: AbstractNumberFormatter
fmt::String
f::Function
exponent_format::SubstitutionString
FancyNumberFormatter(fmt::String="%.4g",
exponent_format::SubstitutionString=s"\g<mantissa> \\cdot 10^{\g<sign_exp>\g<mag_exp>}") = new(fmt, Format.generate_formatter(fmt), exponent_format)
end
FancyNumberFormatter(fmt::String, mult_symbol) =
FancyNumberFormatter(fmt, SubstitutionString("\\g<mantissa> $(escape_string(mult_symbol)) 10^{\\g<sign_exp>\\g<mag_exp>}"))
FancyNumberFormatter(significant_digits, mult_symbol="\\cdot") =
FancyNumberFormatter("%.$(significant_digits)g", mult_symbol)
(f::FancyNumberFormatter)(x::AbstractFloat) = replace(f.f(x), float_regex => f.exponent_format)
(f::FancyNumberFormatter)(x::Unsigned) = "\\mathtt{0x$(string(x; base=16, pad=2sizeof(x)))}"
struct SiunitxNumberFormatter <: AbstractNumberFormatter
format_options::String
version::Int
simple::Bool
end
function SiunitxNumberFormatter(;format_options="", version=3, simple=false)
if ~isempty(format_options) && (~startswith(format_options, '[') || ~endswith(format_options, ']'))
format_options = "[$format_options]"
end
SiunitxNumberFormatter(format_options, version, simple)
end
function (f::SiunitxNumberFormatter)(x::Number)
return "\\num$(f.format_options){$x}"
end
function (f::SiunitxNumberFormatter)(x::Vector{<:Number})
return "\\numlist$(f.format_options){$(join(x,';'))}"
end
function (f::SiunitxNumberFormatter)(x::AbstractRange{<:Number})
return "\\numrange$(f.format_options){$(x.start)}{$(x.stop)}"
end
================================================
FILE: src/recipes.jl
================================================
# Much of this is copied/adapted from RecipesBase.jl. Cred to everyone who has
# worked on that package!
const _debug_recipes = Bool[false]
function debug(v::Bool = true)
_debug_recipes[1] = v
end
# check for flags as part of the `-->` expression
function _is_arrow_tuple(expr::Expr)
expr.head == :tuple && !isempty(expr.args) &&
isa(expr.args[1], Expr) &&
expr.args[1].head == :(-->)
end
function _equals_symbol(arg::Symbol, sym::Symbol)
arg == sym
end
function _equals_symbol(arg::Expr, sym::Symbol) #not sure this method is necessary anymore on 0.7
arg.head == :quote && arg.args[1] == sym
end
function _equals_symbol(arg::QuoteNode, sym::Symbol)
arg.value == sym
end
_equals_symbol(x, sym::Symbol) = false
function create_kw_body(func_signature::Expr)
# get the arg list, stripping out any keyword parameters into a
# bunch of get!(kw, key, value) lines
func_signature.head == :where && return create_kw_body(func_signature.args[1])
args = func_signature.args[2:end]
kw_body = Expr(:block)
kw_dict = Dict{Symbol, Any}()
if isa(args[1], Expr) && args[1].head == :parameters
for kwpair in args[1].args
k, v = kwpair.args
if isa(k, Expr) && k.head == :(::)
k = k.args[1]
@warn("Type annotations on keyword arguments not currently supported in recipes. Type information has been discarded")
end
push!(kw_body.args, :($k = kwargs[$(Meta.quot(k))]))
if v == :nothing
kw_dict[k] = nothing
else
kw_dict[k] = v isa QuoteNode ? v.value : v
end
end
args = args[2:end]
end
args, kw_body, kw_dict
end
# build an apply_recipe function header from the recipe function header
function get_function_def(func_signature::Expr, args::Vector)
front = func_signature.args[1]
kwarg_expr = Expr(:parameters, Expr(:..., esc(:kwargs)))
if func_signature.head == :where
Expr(:where, get_function_def(front, args), esc.(func_signature.args[2:end])...)
elseif func_signature.head == :call
#= func = Expr(:call, :(Latexify.apply_recipe), esc.(args)..., Expr(:parameters, :kwargs)) =#
func = Expr(:call, :(Latexify.apply_recipe), kwarg_expr, esc.(args)...)
if isa(front, Expr) && front.head == :curly
Expr(:where, func, esc.(front.args[2:end])...)
else
func
end
else
throw(RecipeException("Expected `func_signature = ...` with func_signature as a call or where Expr... got: $func_signature"))
end
end
# process the body of the recipe recursively.
# when we see the series macro, we split that block off:
# let
# d2 = copy(d)
# <process_recipe_body on d2>
# RecipeData(d2, block_return)
# end
# and we push this block onto the series_blocks list.
# then at the end we push the main body onto the series list
function process_recipe_body!(expr::Expr)
operation = QuoteNode(:none)
for (i,e) in enumerate(expr.args)
if isa(e,Expr)
# process trailing flags, like:
# a --> b, :force
force = false
if _is_arrow_tuple(e)
for flag in e.args
if _equals_symbol(flag, :force)
force = true
end
end
e = e.args[1]
end
if e.head == :(:=)
force = true
e.head = :(-->)
end
# we are going to recursively swap out `a --> b, flags...` commands
# note: this means "x may become 5"
if e.head == :(-->)
k, v = e.args
if isa(k, Symbol)
if k == :operation
operation = v
expr.args[i] = nothing
continue
end
k = QuoteNode(k)
end
set_expr = if force
:(kwargs[$k] = $v)
else
:(haskey(kwargs, $k) || (kwargs[$k] = $v))
end
quiet = false
expr.args[i] = set_expr
elseif e.head != :call
# we want to recursively replace the arrows, but not inside function calls
# as this might include things like Dict(1=>2)
process_recipe_body!(e)
end
if e.head == :return
if e.args[1] isa Expr
if e.args[1] isa Tuple
e.args[1] = :(($(e.args[1]), kwargs))
else
e.args[1] = :((($(e.args[1]),), kwargs))
end
else
e.args[1] = :((($(e.args[1]),), kwargs))
end
end
end
end
return operation
end
macro latexrecipe(funcexpr)
func_signature, func_body = funcexpr.args
if !(funcexpr.head in (:(=), :function))
throw(RecipeException("Must wrap a valid function call!"))
end
if !(isa(func_signature, Expr) && func_signature.head in (:call, :where))
throw(RecipeException("Expected `func_signature = ...` with func_signature as a call or where Expr... got: $func_signature"))
end
if length(func_signature.args) < 2
throw(RecipeException("Missing function arguments... need something to dispatch on!"))
end
args, kw_body, kw_dict = create_kw_body(func_signature)
func = get_function_def(func_signature, args)
operation = process_recipe_body!(func_body)
# now build a function definition for apply_recipe
funcdef = Expr(:function, func, esc(quote
if Latexify._debug_recipes[1]
println("apply_recipe args: ", $args)
end
kwargs = merge($kw_dict, kwargs)
$kw_body
$func_body
end))
getopdef = Expr(:(=), copy(func), esc(operation))
signature = getopdef.args[1]
while Meta.isexpr(signature, :where)
# Get through any layers of `where`
signature = signature.args[1]
end
signature.args[1] = :(Latexify._getoperation)
return Expr(:block, funcdef, getopdef)
end
================================================
FILE: src/symbol_translations.jl
================================================
const functions = Dict{Symbol, String}(
## Greek alphabet
:alpha => "\\alpha",
:beta => "\\beta",
:gamma => "\\gamma",
:delta => "\\delta",
:epsilon => "\\epsilon",
:zeta => "\\zeta",
:eta => "\\eta",
:theta => "\\theta",
:iota => "\\iota",
:kappa => "\\kappa",
:lambda => "\\lambda",
:mu => "\\mu",
:nu => "\\nu",
:xi => "\\xi",
:pi => "\\pi",
:rho => "\\rho",
:sigma => "\\sigma",
:tau => "\\tau",
:upsilon => "\\upsilon",
:phi => "\\phi",
:chi => "\\chi",
:psi => "\\psi",
:omega => "\\omega",
:Gamma => "\\Gamma",
:Delta => "\\Delta",
:Theta => "\\Theta",
:Lambda => "\\Lambda",
:Xi => "\\Xi",
:Pi => "\\Pi",
:Sigma => "\\Sigma",
:Upsilon => "\\Upsilon",
:Phi => "\\Phi",
:Psi => "\\Psi",
:Omega => "\\Omega",
## Trinogometry
:sin => "\\sin",
:cos => "\\cos",
:tan => "\\tan",
:cot => "\\cot",
:sec => "\\sec",
:csc => "\\csc",
:sinh => "\\sinh",
:cosh => "\\cosh",
:tanh => "\\tanh",
:coth => "\\coth",
:asin => "\\arcsin",
:acos => "\\arccos",
:atan => "\\arctan",
:atan2 => "\\arctan",
:asinh => "\\mathrm{arcsinh}",
:sinc => "\\mathrm{sinc}",
:acosh => "\\mathrm{arccosh}",
:cosc => "\\mathrm{cosc}",
:atanh => "\\mathrm{arctanh}",
:acot => "\\mathrm{arccot}",
:acoth => "\\mathrm{arccoth}",
:asec => "\\mathrm{arcsec}",
:sech => "\\mathrm{sech}",
:asech => "\\mathrm{arcsech}",
:acsc => "\\mathrm{arccsc}",
:csch => "\\mathrm{csch}",
:acsch => "\\mathrm{arccsch}",
## Misc
:log => "\\log",
:log10 => "\\log_{10}",
:log2 => "\\log_{2}",
:slog => "\\log",
:gamma => "\\Gamma", # The Gamma function
)
const trigonometric_functions = [
:sin,
:cos,
:tan,
:cot,
:sec,
:csc,
:sinh,
:cosh,
:tanh,
:coth,
:asin,
:acos,
:atan,
:atan2,
:asinh,
:sinc,
:acosh,
:cosc,
:atanh,
:acot,
:acoth,
:asec,
:sech,
:asech,
:acsc,
:csch,
:acsch,
]
const comparison_operators = Dict(
:< => "<",
:> => ">",
:(==) => "=",
:≈ => "\\approx",
:(===) => "\\equiv",
:<= => "\\leq",
:≤ => "\\leq",
:>= => "\\geq",
:≥ => "\\geq",
:!= => "\\neq",
:≠ => "\\neq",
:!== => "\\not\\equiv",
:in => "\\in",
:∈ => "\\in",
:∉ => "\\notin",
:∋ => "\\ni",
:∌ => "\\not\\ni",
:issubset => "\\subseteq",
:⊆ => "\\subseteq",
:⊊ => "\\subsetneq",
:⊃ => "\\supset",
:⊅ => "\\not\\supset",
)
const bitwise_operators = Dict(
#:∀ => "\\forall",
:& => "\\wedge",
:| => "\\vee",
:⊻ => "\\veebar",
:⊼ => "\\bar{\\wedge}", # Not very good looking, but there is no builtin LaTeX symbol
:>>> => "\\ggg",
:>> => "\\gg",
:<< => "\\ll",
)
const arithmetic_operators = Dict(
:^ => "^",
:* => "*",
:/ => "/",
:% => "\\%",
:\ => "\\backslash",
:÷ => "\\div",
:+ => "+",
:- => "-",
:± => "\\pm",
:∓ => "\\mp",
)
const binary_operators = Dict(
comparison_operators...,
bitwise_operators...,
arithmetic_operators...,
:(=>) => "\\Rightarrow",
:⟹ => "\\Longrightarrow",
:(:) => "\\mathrel{\\ldotp\\mkern-2.5mu\\ldotp}"
)
const unary_operators = Dict(
:unaryminus => "-",
:unaryplus => "+",
:unaryplusminus => "\\pm",
:unaryminusplus => "\\mp"
)
const unary_opposites = Dict(
:unaryminus => "+",
:unaryplus => "-",
:unaryplusminus => "\\mp",
:unaryminusplus => "\\pm"
)
const special_symbols = Dict(
functions...,
binary_operators...,
:Inf => raw"\infty",
)
================================================
FILE: src/unicode2latex.jl
================================================
import OrderedCollections: OrderedDict
import Base.Unicode
mathup(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝐀'=#0x1d400 : #='A'=#0x0041) - #='A'=#0x0041 # Mathematical (Bold) Capital
elseif islowercase(c)
(bold ? #='𝐚'=#0x1d41a : #='a'=#0x0061) - #='a'=#0x0061 # Mathematical (Bold) Small
else
(bold ? #='𝟎'=#0x1d7ce : #='0'=#0x0030) - #='0'=#0x0030 # Mathematical (Bold) Digit
end
)
mathscr(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝓐'=#0x1d4d0 : #='𝒜'=#0x1d49c) - #='A'=#0x0041 # Mathematical (Bold) Script Capital
elseif islowercase(c)
(bold ? #='𝓪'=#0x1d4ea : #='𝒶'=#0x1d4b6) - #='a'=#0x0061 # Mathematical (Bold) Script Small
else
-UInt32(c)
end
)
mathit(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝑨'=#0x1d468 : #='𝐴'=#0x1d434) - #='A'=#0x0041 # Mathematical (Bold) Italic Capital
elseif islowercase(c)
(bold ? #='𝒂'=#0x1d482 : #='𝑎'=#0x1d44e) - #='a'=#0x0061 # Mathematical (Bold) Italic Small
else
-UInt32(c)
end
)
mathfrak(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝕬'=#0x1d56c : #='𝔄'=#0x1d504) - #='A'=#0x0041 # Mathematical (Bold) Fraktur Capital
elseif islowercase(c)
(bold ? #='𝖆'=#0x1d586 : #='𝔞'=#0x1d51e) - #='a'=#0x0061 # Mathematical (Bold) Fraktur Small
else
-UInt32(c)
end
)
mathsfup(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝗔'=#0x1d5d4 : #='𝖠'=#0x1d5a0) - #='A'=#0x0041 # Mathematical (Bold) Sans-Serif Capital
elseif islowercase(c)
(bold ? #='𝗮'=#0x1d5ee : #='𝖺'=#0x1d5ba) - #='a'=#0x0061 # Mathematical (Bold) Sans-Serif Small
else
(bold ? #='𝟬'=#0x1d7ec : #='𝟢'=#0x1d7e2) - #='0'=#0x0030 # Mathematical (Bold) Sans-Serif Digit
end
)
mathsfit(c::Char, bold) = Char(
UInt32(c) + if isuppercase(c)
(bold ? #='𝘼'=#0x1d63c : #='𝘈'=#0x1d608) - #='A'=#0x0041 # Mathematical (Bold) Sans-Serif Italic Capital
elseif islowercase(c)
(bold ? #='𝙖'=#0x1d656 : #='𝘢'=#0x1d622) - #='a'=#0x0061 # Mathematical (Bold) Sans-Serif Italic Small
else
-UInt32(c)
end
)
mathtt(c::Char) = Char(
UInt32(c) + if isuppercase(c)
#='𝙰'=#0x1d670 - #='A'=#0x0041 # Mathematical Monospace Capital
elseif islowercase(c)
#='𝚊'=#0x1d68a - #='a'=#0x0061 # Mathematical Monospace Small
else
#='𝟶'=#0x1d7f6 - #='0'=#0x0030 # Mathematical Monospace Digit
end
)
mathbb(c::Char) = Char(
UInt32(c) + if isuppercase(c)
#='𝔸'=#0x1d538 - #='A'=#0x0041 # Mathematical Double-Struck Capital
elseif islowercase(c)
#='𝕒'=#0x1d552 - #='a'=#0x0061 # Mathematical Double-Struck Small
else
#='𝟘'=#0x1d7d8 - #='0'=#0x0030 # Mathematical Double-Struck Digit
end
)
const greek_seq = ( # contiguous unicode sequence
raw"\Alpha",
raw"\Beta",
raw"\Gamma",
raw"\Delta",
raw"\Epsilon",
raw"\Zeta",
raw"\Eta",
raw"\Theta",
raw"\Iota",
raw"\Kappa",
raw"\Lambda",
raw"\Mu",
raw"\Nu",
raw"\Xi",
raw"\Omicron",
raw"\Pi",
raw"\Rho",
raw"\varTheta",
raw"\Sigma",
raw"\Tau",
raw"\Upsilon",
raw"\Phi",
raw"\Chi",
raw"\Psi",
raw"\Omega",
raw"\nabla",
raw"\alpha",
raw"\beta",
raw"\gamma",
raw"\delta",
raw"\varepsilon",
raw"\zeta",
raw"\eta",
raw"\theta",
raw"\iota",
raw"\kappa",
raw"\lambda",
raw"\mu",
raw"\nu",
raw"\xi",
raw"\omicron",
raw"\pi",
raw"\rho",
raw"\varsigma",
raw"\sigma",
raw"\tau",
raw"\upsilon",
raw"\varphi",
raw"\chi",
raw"\psi",
raw"\omega",
raw"\partial",
raw"\epsilon",
raw"\vartheta",
raw"\varkappa",
raw"\phi",
raw"\varrho",
raw"\varpi",
)
const emphases = (
# ("mathup", ("textup",)) => identity,
("", ("textnormal",)) => identity,
("mathbf", ("textbf",)) => c -> mathup(c, true),
("mathit", ("textit",)) => c -> mathit(c, false),
("mathbfit", ("textit", "textbf")) => c -> mathit(c, true),
("mathscr", ()) => c -> mathscr(c, false),
("mathbfscr", ()) => c -> mathscr(c, true),
("mathfrak", ()) => c -> mathfrak(c, false),
("mathbffrak", ()) => c -> mathfrak(c, true),
("mathsfup", ()) => c -> mathsfup(c, false),
("mathbfsfup", ()) => c -> mathsfup(c, true),
("mathsfit", ()) => c -> mathsfit(c, false),
("mathbfsfit", ()) => c -> mathsfit(c, true),
("mathbb", ()) => mathbb,
("mathtt", ("texttt",)) => mathtt,
)
"""
latex_diacritics(c::Char)
- generate latex escape codes for diacritics of the latin alphabet (upper and lower case), see https://en.wikibooks.org/wiki/LaTeX/Special_Characters#Escaped_codes
- also generate a subset of the following sequence, when the single char normalization is available:
- 'à' => "\\`{a}" # grave
- 'á' => "\\'{a}" # acute
- 'ä' => "\\"{a}" # umlaut (trema, dieresis)
- 'a̋' => "\\H{a}" # hungarian umlaut (double acute)
- 'a̧' => "\\c{a}" # cedilla
- 'ą' => "\\k{a}" # ogonek
- 'a̱' => "\\b{a}" # bar under
- 'ạ' => "\\d{a}" # dot under
- 'å' => "\\r{a}" # ring
- 'ă' => "\\u{a}" # breve
- 'ǎ' => "\\v{a}" # caron (háček)
- Some more diacritics are ignored, and rather treated like math modifiers:
- 'â' => "\\hat{a}" # rather than "\\^{a}", circumflex
- 'ã' => "\\tilde{a}" # rather than "\\~{a}", tilde
- 'ā' => "\\bar{a}" # rather than "\\={a}", macron (bar above)
- 'ȧ' => "\\dot{a}" # rather than "\\.{a}", dot above
"""
function latex_diacritics(chars::AbstractVector)
out = []
for c ∈ chars, (mod, mark) ∈ (
'`' => Char(0x300), # latex sequence \`{c} maps to 'c' * Char(0x300) := "c̀"
"'" => Char(0x301),
#'^' => Char(0x302),
#'~' => Char(0x303),
#'=' => Char(0x304),
'u' => Char(0x306),
#'.' => Char(0x307),
'"' => Char(0x308),
'r' => Char(0x30a),
'H' => Char(0x30b),
'v' => Char(0x30c),
'd' => Char(0x323),
'c' => Char(0x327),
'k' => Char(0x328),
'b' => Char(0x331),
)
for ((_, et), func) ∈ emphases
isempty(et) && continue
repl = "\\$mod{$c}"
for emph ∈ et
isempty(emph) && continue
repl = "\\$emph{$repl}"
end
dia = func(c) * mark
# e.g. ('y' * Char(0x30a) == "ẙ") != (Char(0x1e99) == 'ẙ'), although they look the same
push!(out, dia => repl)
alias = length(dia) == 1 ? dia : Unicode.normalize(dia)
if alias != dia
push!(out, (length(alias) == 1 ? first(alias) : alias) => repl)
end
end
end
out
end
function latex_emphasis(chars::AbstractVector)
out = []
for ((em, _), f) ∈ emphases
isempty(em) && continue
for c ∈ chars
push!(out, f(c) => isempty(em) ? c : "\\$em{$c}")
end
end
filter(p -> isprint(p.first), out)
end
# [`LaTeX`] https://tug.ctan.org/info/symbols/comprehensive/symbols-a4.pdf
# \mathrm: normal upright Roman font
# \mathnormal: normal math italic font
# \mathbf: upright Roman boldface letters
# \mathsf: upright sans serif letters
# [`unicode-math`] https://mirrors.ctan.org/macros/unicodetex/latex/unicode-math/unicode-math.pdf
# \mathup Upright serif ✘ regular text
# \mathbfup Bold upright serif ✘ \mathbf instead
# \mathit Italic serif ✔
# \mathbfit Bold italic serif ✔
# \mathsfup Upright sans-serif ✔
# \mathsfit Italic sans-serif ✔
# \mathbfsfup Bold upright sans-serif ✔
# \mathbfsfit Bold italic sans-serif ✔
# \mathtt Typewriter ✔
# \mathbb Blackboard bold ✔
# \mathbbit Blackboard bold italic ✔
# \mathscr Script ✔
# \mathbfscr Bold script ✔
# \mathcal Calligraphic ✘ \mathscr instead
# \mathbfcal Bold calligraphic ✘ \mathbfscr instead
# \mathfrak Fraktur ✔
# \mathbffrak Bold Fraktur ✔
# [`amssymb`] https://mirrors.ctan.org/fonts/amsfonts/doc/amssymb.pdf
const unicodedict = OrderedDict{Union{Char,String}, String}(
# ↓↓↓ unicode, in increasing order (see https://docs.julialang.org/en/v1/manual/unicode-input)
# commented lines are either unsupported in `LaTeX` (or only through a package such as `marvosym` for e.g. `\jupiter`)
# or don't make sense here (letter modifiers such as `\enclosecircle`)
'¡' => raw"\textnormal{\textexclamdown}", # \exclamdown
'£' => raw"\mathsterling", # \sterling
'¥' => raw"\mathyen", # \yen
'¦' => raw"\textnormal{\textbrokenbar}", # \brokenbar
'§' => raw"\S",
'©' => raw"\copyright",
'ª' => raw"\textnormal{\textordfeminine}", # \ordfeminine
'¬' => raw"\neg", # \lnot
'®' => raw"\circledR",
# '¯' => raw"\highminus",
'°' => raw"\textnormal{\textdegree}", # {^{\circ}}, \degree
'±' => raw"\pm",
'²' => raw"{^2}",
'³' => raw"{^3}",
'¶' => raw"\P",
'·' => raw"\cdotp",
'¹' => raw"{^1}",
'º' => raw"\textnormal{\textordmasculine}", # \ordmasculine
'¼' => raw"\tfrac{1}{4}",
'½' => raw"\tfrac{1}{2}",
'¾' => raw"\tfrac{3}{4}",
'¿' => raw"\textnormal{\textquestiondown}", # \questiondown
'Å' => raw"\textnormal{\AA}",
'Æ' => raw"\textnormal{\AE}",
'Ð' => raw"\textnormal{\DH}",
'×' => raw"\times",
'Ø' => raw"\textnormal{\O}",
'Þ' => raw"\textnormal{\TH}",
'ß' => raw"\textnormal{\ss}",
'å' => raw"\textnormal{\aa}",
'æ' => raw"\textnormal{\ae}",
'ð' => raw"\eth", # \dh
'÷' => raw"\div",
'ø' => raw"\emptyset",
'þ' => raw"\textnormal{\th}",
'Đ' => raw"\textnormal{\DJ}",
'đ' => raw"\textnormal{\dj}",
'ħ' => raw"\hslash", # \hbar
'ı' => raw"\imath",
'Ł' => raw"\textnormal{\L}",
'ł' => raw"\textnormal{\l}",
'Ŋ' => raw"\textnormal{\NG}",
'ŋ' => raw"\textnormal{\ng}",
'Œ' => raw"\textnormal{\OE}",
'œ' => raw"\textnormal{\oe}",
# 'ƕ' => raw"\hvlig",
# 'ƞ' => raw"\nrleg",
'Ƶ' => raw"\Zbar",
# 'ǂ' => raw"\doublepipe",
'ȷ' => raw"\jmath",
# 'ɐ' => raw"\trna",
# 'ɒ' => raw"\trnsa",
# 'ɔ' => raw"\openo",
# 'ɖ' => raw"\rtld",
# 'ə' => raw"\schwa",
# 'ɣ' => raw"\pgamma",
# 'ɤ' => raw"\pbgam",
# 'ɥ' => raw"\trnh",
# 'ɬ' => raw"\btdl",
# 'ɭ' => raw"\rtll",
# 'ɯ' => raw"\trnm",
# 'ɰ' => raw"\trnmlr",
# 'ɱ' => raw"\ltlmr",
# 'ɲ' => raw"\ltln",
# 'ɳ' => raw"\rtln",
# 'ɷ' => raw"\clomeg",
# 'ɸ' => raw"\ltphi",
# 'ɹ' => raw"\trnr",
# 'ɺ' => raw"\trnrl",
# 'ɻ' => raw"\rttrnr",
# 'ɼ' => raw"\rl",
# 'ɽ' => raw"\rtlr",
# 'ɾ' => raw"\fhr",
# 'ʂ' => raw"\rtls",
# 'ʃ' => raw"\esh",
# 'ʇ' => raw"\trnt",
# 'ʈ' => raw"\rtlt",
# 'ʊ' => raw"\pupsil",
# 'ʋ' => raw"\pscrv",
# 'ʌ' => raw"\invv",
# 'ʍ' => raw"\invw",
# 'ʎ' => raw"\trny",
# 'ʐ' => raw"\rtlz",
# 'ʒ' => raw"\yogh",
# 'ʔ' => raw"\glst",
# 'ʕ' => raw"\reglst",
# 'ʖ' => raw"\inglst",
# 'ʞ' => raw"\turnk",
# 'ʤ' => raw"\dyogh",
# 'ʧ' => raw"\tesh",
'ʰ' => raw"{^h}",
'ʲ' => raw"{^j}",
'ʳ' => raw"{^r}",
'ʷ' => raw"{^w}",
'ʸ' => raw"{^y}",
'ʼ' => raw"{'}", # \rasp
# 'ˈ' => raw"\sverts",
# 'ˌ' => raw"\verti",
# 'ː' => raw"\lmrk", # \textlengthmark
# 'ˑ' => raw"\hlmrk", # \texthalflength
# '˒' => raw"\sbrhr",
# '˓' => raw"\sblhr",
# '˔' => raw"\rais", # \textraised
# '˕' => raw"\low", # \textlowered
'˘' => raw"\textnormal{\u{}}",
'˜' => raw"\textnormal{\texttildelow}", # \tildelow
'ˡ' => raw"{^l}",
'ˢ' => raw"{^s}",
'ˣ' => raw"{^x}",
# '̀' => raw"\grave{}",
# '́' => raw"\acute{}",
# '̂' => raw"\hat{}",
# '̃' => raw"\tilde{}",
# '̄' => raw"\bar{}",
# '̅' => raw"\overbar{}",
# '̆' => raw"\breve{}",
# '̇' => raw"\dot{}",
# '̈' => raw"\ddot{}",
# '̉' => raw"\ovhook{}",
# '̊' => raw"\ocirc{}",
# '̋' => raw"\H{}",
# '̌' => raw"\check{}",
# '̐' => raw"\candra{}",
# '̒' => raw"\oturnedcomma{}",
# '̕' => raw"\ocommatopright{}",
# '̚' => raw"\droang{}",
# '̡' => raw"\palh{}",
# '̢' => raw"\rh{}",
# '̧' => raw"\c{}",
# '̨' => raw"\k{}",
# '̪' => raw"\sbbrg{}",
# '̰' => raw"\wideutilde{}",
# '̲' => raw"\underbar{}",
# '̶' => raw"\strike{}", # \sout
# '̸' => raw"\not{}",
# '͍' => raw"\underleftrightarrow{}",
# greek without emphasis
'Α' => raw"\Alpha",
'Β' => raw"\Beta",
'Γ' => raw"\Gamma",
'Δ' => raw"\Delta",
'Ε' => raw"\Epsilon",
'Ζ' => raw"\Zeta",
'Η' => raw"\Eta",
'Θ' => raw"\Theta",
'Ι' => raw"\Iota",
'Κ' => raw"\Kappa",
'Λ' => raw"\Lambda",
'Μ' => raw"\Mu", # \upMu
'Ν' => raw"\Nu", # \upNu
'Ξ' => raw"\Xi",
'Ο' => raw"\Omicron", # \upOmicron
'Π' => raw"\Pi",
'Ρ' => raw"\Rho",
'Σ' => raw"\Sigma",
'Τ' => raw"\Tau",
'Υ' => raw"\Upsilon",
'Φ' => raw"\Phi",
'Χ' => raw"\Chi",
'Ψ' => raw"\Psi",
'Ω' => raw"\Omega",
'α' => raw"\alpha",
'β' => raw"\beta",
'γ' => raw"\gamma",
'δ' => raw"\delta",
'ε' => raw"\varepsilon",
'ζ' => raw"\zeta",
'η' => raw"\eta",
'θ' => raw"\theta",
'ι' => raw"\iota",
'κ' => raw"\kappa",
'λ' => raw"\lambda",
'μ' => raw"\mu",
'ν' => raw"\nu",
'ξ' => raw"\xi",
'ο' => raw"\omicron", # \upomicron
'π' => raw"\pi",
'ρ' => raw"\rho",
'ς' => raw"\varsigma",
'σ' => raw"\sigma",
'τ' => raw"\tau",
'υ' => raw"\upsilon",
'φ' => raw"\varphi",
'χ' => raw"\chi",
'ψ' => raw"\psi",
'ω' => raw"\omega",
# 'ϐ' => raw"\varbeta",
'ϑ' => raw"\vartheta",
'ϕ' => raw"\phi",
'ϖ' => raw"\varpi",
# 'Ϙ' => raw"\oldKoppa",
# 'ϙ' => raw"\oldkoppa",
# 'Ϛ' => raw"\Stigma",
# 'ϛ' => raw"\upstigma",
'Ϝ' => raw"\upDigamma",
'ϝ' => raw"\updigamma",
# 'Ϟ' => raw"\Koppa",
# 'ϟ' => raw"\koppa",
# 'Ϡ' => raw"\Sampi",
# 'ϡ' => raw"\upsampi",
'ϰ' => raw"\varkappa",
'ϱ' => raw"\varrho",
'ϴ' => raw"\varTheta",
'ϵ' => raw"\epsilon",
'϶' => raw"\backepsilon",
'ᴬ' => raw"{^A}",
'ᴮ' => raw"{^B}",
'ᴰ' => raw"{^D}",
'ᴱ' => raw"{^E}",
'ᴳ' => raw"{^G}",
'ᴴ' => raw"{^H}",
'ᴵ' => raw"{^I}",
'ᴶ' => raw"{^J}",
'ᴷ' => raw"{^K}",
'ᴸ' => raw"{^L}",
'ᴹ' => raw"{^M}",
'ᴺ' => raw"{^N}",
'ᴼ' => raw"{^O}",
'ᴾ' => raw"{^P}",
'ᴿ' => raw"{^R}",
'ᵀ' => raw"{^T}",
'ᵁ' => raw"{^U}",
'ᵂ' => raw"{^W}",
'ᵃ' => raw"{^a}",
'ᵅ' => raw"{^\alpha}",
'ᵇ' => raw"{^b}",
'ᵈ' => raw"{^d}",
'ᵉ' => raw"{^e}",
'ᵋ' => raw"{^\epsilon}",
'ᵍ' => raw"{^g}",
'ᵏ' => raw"{^k}",
'ᵐ' => raw"{^m}",
'ᵒ' => raw"{^o}",
'ᵖ' => raw"{^p}",
'ᵗ' => raw"{^t}",
'ᵘ' => raw"{^u}",
'ᵛ' => raw"{^v}",
'ᵝ' => raw"{^\beta}",
'ᵞ' => raw"{^\gamma}",
'ᵟ' => raw"{^\delta}",
'ᵠ' => raw"{^\phi}",
'ᵡ' => raw"{^\chi}",
'ᵢ' => raw"{_i}",
'ᵣ' => raw"{_r}",
'ᵤ' => raw"{_u}",
'ᵥ' => raw"{_v}",
'ᵦ' => raw"{_\beta}",
'ᵧ' => raw"{_\gamma}",
'ᵨ' => raw"{_\rho}",
'ᵩ' => raw"{_\phi}",
'ᵪ' => raw"{_\chi}",
'ᶜ' => raw"{^c}",
'ᶠ' => raw"{^f}",
'ᶥ' => raw"{^\iota}",
'ᶲ' => raw"{^\phi}", # \ltphi
'ᶻ' => raw"{^z}",
'ᶿ' => raw"{^\theta}",
# see https://mirrors.ctan.org/macros/latex/contrib/uspace/uspace.pdf
' ' => raw"\enspace", # 0.5em
' ' => raw"\quad", # 1em
' ' => raw"\thickspace", # \;
' ' => raw"\thinspace", # \,
' ' => raw"\hspace{0.08333em}", # hair space
'–' => raw"\textnormal{\textendash}", # \endash
'—' => raw"\textnormal{\textemdash}", # \emdash
'‖' => raw"\Vert", # \|
'‘' => raw"\textnormal{\textquoteleft}", # \lq
'’' => raw"\textnormal{\textquoteright}", # \rq
# '‛' => raw"\reapos",
'“' => raw"\textnormal{\textquotedblleft}", # \ldq
'”' => raw"\textnormal{\textquotedblright}", # \rdq
'†' => raw"\dagger",
'‡' => raw"\ddagger",
'•' => raw"\bullet",
'…' => raw"\dots", # \ldots
'‰' => raw"\textnormal{\textperthousand}", # \perthousand
'‱' => raw"\textnormal{\textpertenthousand}", # \pertenthousand
'′' => raw"\prime",
'″' => raw"\dprime", # \pprime
'‴' => raw"\trprime", # \ppprime
'‵' => raw"\backprime",
'‶' => raw"\backdprime", # \backpprime
'‷' => raw"\backtrprime", # \backppprime
'‹' => raw"\textnormal{\guilsinglleft}",
'›' => raw"\textnormal{\guilsinglright}",
'⁀' => raw"\tieconcat",
'⁗' => raw"\qprime", # \pppprime
# '⁝' => raw"\tricolon",
'' => raw"\nolinebreak",
'⁰' => raw"{^0}",
'ⁱ' => raw"{^i}",
'⁴' => raw"{^4}",
'⁵' => raw"{^5}",
'⁶' => raw"{^6}",
'⁷' => raw"{^7}",
'⁸' => raw"{^8}",
'⁹' => raw"{^9}",
'⁺' => raw"{^+}",
'⁻' => raw"{^-}",
'⁼' => raw"{^=}",
'⁽' => raw"{^(}",
'⁾' => raw"{^)}",
'ⁿ' => raw"{^n}",
'₀' => raw"{_0}",
'₁' => raw"{_1}",
'₂' => raw"{_2}",
'₃' => raw"{_3}",
'₄' => raw"{_4}",
'₅' => raw"{_5}",
'₆' => raw"{_6}",
'₇' => raw"{_7}",
'₈' => raw"{_8}",
'₉' => raw"{_9}",
'₊' => raw"{_+}",
'₋' => raw"{_-}",
'₌' => raw"{_=}",
'₍' => raw"{_(}",
'₎' => raw"{_)}",
'ₐ' => raw"{_a}",
'ₑ' => raw"{_e}",
'ₒ' => raw"{_o}",
'ₓ' => raw"{_x}",
# 'ₔ' => raw"{_\schwa}",
'ₕ' => raw"{_h}",
'ₖ' => raw"{_k}",
'ₗ' => raw"{_l}",
'ₘ' => raw"{_m}",
'ₙ' => raw"{_n}",
'ₚ' => raw"{_p}",
'ₛ' => raw"{_s}",
'ₜ' => raw"{_t}",
# '₧' => raw"\pes",
'€' => raw"\euro",
# '⃐' => raw"\leftharpoonaccent{}",
# '⃑' => raw"\rightharpoonaccent{}",
# '⃒' => raw"\vertoverlay{}",
# '⃖' => raw"\overleftarrow{}",
# '⃗' => raw"\vec{}",
# '⃛' => raw"\dddot{}",
# '⃜' => raw"\ddddot{}",
# '⃝' => raw"\enclosecircle{}",
# '⃞' => raw"\enclosesquare{}",
# '⃟' => raw"\enclosediamond{}",
# '⃡' => raw"\overleftrightarrow{}",
# '⃤' => raw"\enclosetriangle{}",
# '⃧' => raw"\annuity{}",
# '⃨' => raw"\threeunderdot{}",
# '⃩' => raw"\widebridgeabove{}",
# '⃬' => raw"\underrightharpoondown{}",
# '⃭' => raw"\underleftharpoondown{}",
# '⃮' => raw"\underleftarrow{}",
# '⃯' => raw"\underrightarrow{}",
# '⃰' => raw"\asteraccent{}",
'ℂ' => raw"\mathbb{C}",
'ℇ' => raw"\Eulerconst", # \eulermascheroni
'ℊ' => raw"\mathscr{g}",
'ℋ' => raw"\mathscr{H}",
'ℌ' => raw"\mathfrak{H}",
'ℍ' => raw"\mathbb{H}",
'ℎ' => raw"\Planckconst", # \mathit{h}, \ith, \planck
'ℏ' => raw"\hslash",
'ℐ' => raw"\mathscr{I}",
'ℑ' => raw"\Im", # \mathfrak{I}
'ℒ' => raw"\mathscr{L}",
'ℓ' => raw"\ell",
'ℕ' => raw"\mathbb{N}",
'№' => raw"\textnormal{\textnumero}", # \numero
'℘' => raw"\wp",
'ℙ' => raw"\mathbb{P}",
'ℚ' => raw"\mathbb{Q}",
'ℛ' => raw"\mathscr{R}",
'ℜ' => raw"\Re", # \mathfrak{R}
'ℝ' => raw"\mathbb{R}",
'℞' => raw"\textnormal{\textrecipe}", # \xrat
'™' => raw"\textnormal{\texttrademark}", # \trademark
'ℤ' => raw"\mathbb{Z}",
'Ω' => raw"\Omega", # \ohm
'℧' => raw"\mho",
'ℨ' => raw"\mathfrak{Z}",
'℩' => raw"\turnediota",
'Å' => raw"\Angstrom",
'ℬ' => raw"\mathscr{B}",
'ℭ' => raw"\mathfrak{C}",
'ℯ' => raw"\mathscr{e}", # \euler
'ℰ' => raw"\mathscr{E}",
'ℱ' => raw"\mathscr{F}",
'Ⅎ' => raw"\Finv",
'ℳ' => raw"\mathscr{M}",
'ℴ' => raw"\mathscr{o}",
'ℵ' => raw"\aleph",
'ℶ' => raw"\beth",
'ℷ' => raw"\gimel",
'ℸ' => raw"\daleth",
'ℼ' => raw"\mathbb{\pi}",
'ℽ' => raw"\mathbb{\gamma}",
'ℾ' => raw"\mathbb{\Gamma}",
'ℿ' => raw"\mathbb{\Pi}",
'⅀' => raw"\mathbb{\sum}",
'⅁' => raw"\Game",
'⅂' => raw"\sansLturned",
'⅃' => raw"\sansLmirrored",
'⅄' => raw"\Yup",
'ⅅ' => raw"\mathbbit{D}",
'ⅆ' => raw"\mathbbit{d}",
'ⅇ' => raw"\mathbbit{e}",
'ⅈ' => raw"\mathbbit{i}",
'ⅉ' => raw"\mathbbit{j}",
'⅊' => raw"\PropertyLine",
'⅋' => raw"\upand",
'⅐' => raw"\tfrac{1}{7}",
'⅑' => raw"\tfrac{1}{9}",
'⅒' => raw"\tfrac{1}{10}",
'⅓' => raw"\tfrac{1}{3}",
'⅔' => raw"\tfrac{2}{3}",
'⅕' => raw"\tfrac{1}{5}",
'⅖' => raw"\tfrac{2}{5}",
'⅗' => raw"\tfrac{3}{5}",
'⅘' => raw"\tfrac{4}{5}",
'⅙' => raw"\tfrac{1}{6}",
'⅚' => raw"\tfrac{5}{6}",
'⅛' => raw"\tfrac{1}{8}",
'⅜' => raw"\tfrac{3}{8}",
'⅝' => raw"\tfrac{5}{8}",
'⅞' => raw"\tfrac{7}{8}",
'⅟' => raw"\tfrac{1}{}",
'↉' => raw"\tfrac{0}{3}",
'←' => raw"\leftarrow", # \gets
'↑' => raw"\uparrow",
'→' => raw"\rightarrow", # \to
'↓' => raw"\downarrow",
'↔' => raw"\leftrightarrow",
'↕' => raw"\updownarrow",
'↖' => raw"\nwarrow",
'↗' => raw"\nearrow",
'↘' => raw"\searrow",
'↙' => raw"\swarrow",
'↚' => raw"\nleftarrow",
'↛' => raw"\nrightarrow",
'↜' => raw"\leftwavearrow",
'↝' => raw"\rightwavearrow",
'↞' => raw"\twoheadleftarrow",
'↟' => raw"\twoheaduparrow",
'↠' => raw"\twoheadrightarrow",
'↡' => raw"\twoheaddownarrow",
'↢' => raw"\leftarrowtail",
'↣' => raw"\rightarrowtail",
'↤' => raw"\mapsfrom",
'↥' => raw"\mapsup",
'↦' => raw"\mapsto",
'↧' => raw"\mapsdown",
'↨' => raw"\updownarrowbar",
'↩' => raw"\hookleftarrow",
'↪' => raw"\hookrightarrow",
'↫' => raw"\looparrowleft",
'↬' => raw"\looparrowright",
'↭' => raw"\leftrightsquigarrow",
'↮' => raw"\nleftrightarrow",
'↯' => raw"\downzigzagarrow",
'↰' => raw"\Lsh",
'↱' => raw"\Rsh",
'↲' => raw"\Ldsh",
'↳' => raw"\Rdsh",
'↴' => raw"\linefeed",
'↵' => raw"\carriagereturn",
'↶' => raw"\curvearrowleft",
'↷' => raw"\curvearrowright",
'↸' => raw"\barovernorthwestarrow",
'↹' => raw"\barleftarrowrightarrowbar",
'↺' => raw"\circlearrowleft",
'↻' => raw"\circlearrowright",
'↼' => raw"\leftharpoonup",
'↽' => raw"\leftharpoondown",
'↾' => raw"\upharpoonright",
'↿' => raw"\upharpoonleft",
'⇀' => raw"\rightharpoonup",
'⇁' => raw"\rightharpoondown",
'⇂' => raw"\downharpoonright",
'⇃' => raw"\downharpoonleft",
'⇄' => raw"\rightleftarrows",
'⇅' => raw"\updownarrows", # \dblarrowupdown
'⇆' => raw"\leftrightarrows",
'⇇' => raw"\leftleftarrows",
'⇈' => raw"\upuparrows",
'⇉' => raw"\rightrightarrows",
'⇊' => raw"\downdownarrows",
'⇋' => raw"\leftrightharpoons",
'⇌' => raw"\rightleftharpoons",
'⇍' => raw"\nLeftarrow",
'⇎' => raw"\nLeftrightarrow",
'⇏' => raw"\nRightarrow",
'⇐' => raw"\Leftarrow",
'⇑' => raw"\Uparrow",
'⇒' => raw"\Rightarrow",
'⇓' => raw"\Downarrow",
'⇔' => raw"\Leftrightarrow",
'⇕' => raw"\Updownarrow",
'⇖' => raw"\Nwarrow",
'⇗' => raw"\Nearrow",
'⇘' => raw"\Searrow",
'⇙' => raw"\Swarrow",
'⇚' => raw"\Lleftarrow",
'⇛' => raw"\Rrightarrow",
'⇜' => raw"\leftsquigarrow",
'⇝' => raw"\rightsquigarrow",
'⇞' => raw"\nHuparrow",
'⇟' => raw"\nHdownarrow",
'⇠' => raw"\leftdasharrow",
'⇡' => raw"\updasharrow",
'⇢' => raw"\rightdasharrow",
'⇣' => raw"\downdasharrow",
'⇤' => raw"\barleftarrow",
'⇥' => raw"\rightarrowbar",
'⇦' => raw"\leftwhitearrow",
'⇧' => raw"\upwhitearrow",
'⇨' => raw"\rightwhitearrow",
'⇩' => raw"\downwhitearrow",
'⇪' => raw"\whitearrowupfrombar",
'⇴' => raw"\circleonrightarrow",
'⇵' => raw"\downuparrows", # \DownArrowUpArrow
'⇶' => raw"\rightthreearrows",
'⇷' => raw"\nvleftarrow",
'⇸' => raw"\nvrightarrow",
'⇹' => raw"\nvleftrightarrow",
'⇺' => raw"\nVleftarrow",
'⇻' => raw"\nVrightarrow",
'⇼' => raw"\nVleftrightarrow",
'⇽' => raw"\leftarrowtriangle",
'⇾' => raw"\rightarrowtriangle",
'⇿' => raw"\leftrightarrowtriangle",
'∀' => raw"\forall",
'∁' => raw"\complement",
'∂' => raw"\partial",
'∃' => raw"\exists",
'∄' => raw"\nexists",
'∅' => raw"\emptyset", # \O, \varnothing
'∆' => raw"\increment",
'∇' => raw"\nabla", # \del
'∈' => raw"\in",
'∉' => raw"\notin",
'∊' => raw"\smallin",
'∋' => raw"\ni",
'∌' => raw"\nni",
'∍' => raw"\smallni",
'∎' => raw"\QED",
'∏' => raw"\prod",
'∐' => raw"\coprod",
'∑' => raw"\sum",
'−' => raw"\minus",
'∓' => raw"\mp",
'∔' => raw"\dotplus",
'∖' => raw"\setminus",
'∗' => raw"\ast",
'∘' => raw"\circ",
'∙' => raw"\vysmblkcircle",
'√' => raw"\sqrt{}", # \surd
'∛' => raw"\cuberoot{}", # \cbrt
'∜' => raw"\fourthroot{}",
'∝' => raw"\propto",
'∞' => raw"\infty",
'∟' => raw"\rightangle",
'∠' => raw"\angle",
'∡' => raw"\measuredangle",
'∢' => raw"\sphericalangle",
'∣' => raw"\mid",
'∤' => raw"\nmid",
'∥' => raw"\parallel",
'∦' => raw"\nparallel",
'∧' => raw"\wedge",
'∨' => raw"\vee",
'∩' => raw"\cap",
'∪' => raw"\cup",
'∫' => raw"\int",
'∬' => raw"\iint",
'∭' => raw"\iiint",
'∮' => raw"\oint",
'∯' => raw"\oiint",
'∰' => raw"\oiiint",
'∱' => raw"\intclockwise", # \clwintegral
'∲' => raw"\varointclockwise",
'∳' => raw"\ointctrclockwise",
'∴' => raw"\therefore",
'∵' => raw"\because",
'∷' => raw"\Colon",
'∸' => raw"\dotminus",
'∺' => raw"\dotsminusdots",
'∻' => raw"\kernelcontraction",
'∼' => raw"\sim",
'∽' => raw"\backsim",
'∾' => raw"\invlazys", # \lazysinv
'∿' => raw"\sinewave",
'≀' => raw"\wr",
'≁' => raw"\nsim",
'≂' => raw"\eqsim",
"≂̸" => raw"\not\eqsim", # \neqsim
'≃' => raw"\simeq",
'≄' => raw"\nsime",
'≅' => raw"\cong",
'≆' => raw"\simneqq", # \approxnotequal
'≇' => raw"\ncong",
'≈' => raw"\approx",
'≉' => raw"\napprox",
'≊' => raw"\approxeq",
'≋' => raw"\approxident", # \tildetrpl
'≌' => raw"\backcong", # \allequal
'≍' => raw"\asymp",
'≎' => raw"\Bumpeq",
"≎̸" => raw"\not\Bumpeq", # \nBumpeq
'≏' => raw"\bumpeq",
"≏̸" => raw"\not\bumpeq", # \nbumpeq
'≐' => raw"\doteq",
'≑' => raw"\Doteq",
'≒' => raw"\fallingdotseq",
'≓' => raw"\risingdotseq",
'≔' => raw"\coloneq",
'≕' => raw"\eqcolon",
'≖' => raw"\eqcirc",
'≗' => raw"\circeq",
'≘' => raw"\arceq",
'≙' => raw"\wedgeq",
'≚' => raw"\veeeq",
'≛' => raw"\stareq", # \starequal
'≜' => raw"\triangleq",
'≝' => raw"\eqdef",
'≞' => raw"\measeq",
'≟' => raw"\questeq",
'≠' => raw"\ne",
'≡' => raw"\equiv",
'≢' => raw"\nequiv",
'≣' => raw"\Equiv",
'≤' => raw"\leq", # \les \le
'≥' => raw"\geq", # \le
'≦' => raw"\leqq",
'≧' => raw"\geqq",
'≨' => raw"\lneqq",
"≨︀" => raw"\lvertneqq",
'≩' => raw"\gneqq",
"≩︀" => raw"\gvertneqq",
'≪' => raw"\ll",
"≪̸" => raw"\not\ll", # \NotLessLess
'≫' => raw"\gg",
"≫̸" => raw"\not\gg", # \NotGreaterGreater
'≬' => raw"\between",
'≭' => raw"\nasymp",
'≮' => raw"\nless",
'≯' => raw"\ngtr",
'≰' => raw"\nleq",
'≱' => raw"\ngeq",
'≲' => raw"\lesssim",
'≳' => raw"\gtrsim",
'≴' => raw"\nlesssim",
'≵' => raw"\ngtrsim",
'≶' => raw"\lessgtr",
'≷' => raw"\gtrless",
'≸' => raw"\not\lessgtr", # \notlessgreater
'≹' => raw"\not\gtrless", # \notgreaterless
'≺' => raw"\prec",
'≻' => raw"\succ",
'≼' => raw"\preccurlyeq",
'≽' => raw"\succcurlyeq",
'≾' => raw"\precsim",
"≾̸" => raw"\not\precsim", # \nprecsim
'≿' => raw"\succsim",
"≿̸" => raw"\not\succsim", # \nsuccsim
'⊀' => raw"\nprec",
'⊁' => raw"\nsucc",
'⊂' => raw"\subset",
'⊃' => raw"\supset",
'⊄' => raw"\nsubset",
'⊅' => raw"\nsupset",
'⊆' => raw"\subseteq",
'⊇' => raw"\supseteq",
'⊈' => raw"\nsubseteq",
'⊉' => raw"\nsupseteq",
'⊊' => raw"\subsetneq",
"⊊︀" => raw"\varsubsetneqq",
'⊋' => raw"\supsetneq",
"⊋︀" => raw"\varsupsetneq",
'⊍' => raw"\cupdot",
'⊎' => raw"\uplus",
'⊏' => raw"\sqsubset",
"⊏̸" => raw"\not\sqsubset", # \NotSquareSubset
'⊐' => raw"\sqsupset",
"⊐̸" => raw"\not\sqsupset", # \NotSquareSuperset
'⊑' => raw"\sqsubseteq",
'⊒' => raw"\sqsupseteq",
'⊓' => raw"\sqcap",
'⊔' => raw"\sqcup",
'⊕' => raw"\oplus",
'⊖' => raw"\ominus",
'⊗' => raw"\otimes",
'⊘' => raw"\oslash",
'⊙' => raw"\odot",
'⊚' => raw"\circledcirc",
'⊛' => raw"\circledast",
'⊜' => raw"\circledequal",
'⊝' => raw"\circleddash",
'⊞' => raw"\boxplus",
'⊟' => raw"\boxminus",
'⊠' => raw"\boxtimes",
'⊡' => raw"\boxdot",
'⊢' => raw"\vdash",
'⊣' => raw"\dashv",
'⊤' => raw"\top",
'⊥' => raw"\bot",
'⊧' => raw"\models",
'⊨' => raw"\vDash",
'⊩' => raw"\Vdash",
'⊪' => raw"\Vvdash",
'⊫' => raw"\VDash",
'⊬' => raw"\nvdash",
'⊭' => raw"\nvDash",
'⊮' => raw"\nVdash",
'⊯' => raw"\nVDash",
'⊰' => raw"\prurel",
'⊱' => raw"\scurel",
'⊲' => raw"\vartriangleleft",
'⊳' => raw"\vartriangleright",
'⊴' => raw"\trianglelefteq",
'⊵' => raw"\trianglerighteq",
'⊶' => raw"\origof", # \original
'⊷' => raw"\imageof", # \image
'⊸' => raw"\multimap",
'⊹' => raw"\hermitmatrix", # \hermitconjmatrix
'⊺' => raw"\intercal",
'⊻' => raw"\veebar", # \xor
'⊼' => raw"\barwedge", # \nand
'⊽' => raw"\barvee",
'⊾' => raw"\measuredrightangle", # \rightanglearc
'⊿' => raw"\varlrtriangle",
'⋀' => raw"\bigwedge",
'⋁' => raw"\bigvee",
'⋂' => raw"\bigcap",
'⋃' => raw"\bigcup",
'⋄' => raw"\diamond",
'⋅' => raw"\cdot",
'⋆' => raw"\star",
'⋇' => raw"\divideontimes",
'⋈' => raw"\bowtie",
'⋉' => raw"\ltimes",
'⋊' => raw"\rtimes",
'⋋' => raw"\leftthreetimes",
'⋌' => raw"\rightthreetimes",
'⋍' => raw"\backsimeq",
'⋎' => raw"\curlyvee",
'⋏' => raw"\curlywedge",
'⋐' => raw"\Subset",
'⋑' => raw"\Supset",
'⋒' => raw"\Cap",
'⋓' => raw"\Cup",
'⋔' => raw"\pitchfork",
'⋕' => raw"\equalparallel",
'⋖' => raw"\lessdot",
'⋗' => raw"\gtrdot",
'⋘' => raw"\lll", # \verymuchless
'⋙' => raw"\ggg",
'⋚' => raw"\lesseqgtr",
'⋛' => raw"\gtreqless",
'⋜' => raw"\eqless",
'⋝' => raw"\eqgtr",
'⋞' => raw"\curlyeqprec",
'⋟' => raw"\curlyeqsucc",
'⋠' => raw"\npreccurlyeq",
'⋡' => raw"\nsucccurlyeq",
'⋢' => raw"\nsqsubseteq",
'⋣' => raw"\nsqsupseteq",
'⋤' => raw"\sqsubsetneq",
'⋥' => raw"\sqsupsetneq", # \sqspne
'⋦' => raw"\lnsim",
'⋧' => raw"\gnsim",
'⋨' => raw"\precnsim",
'⋩' => raw"\succnsim",
'⋪' => raw"\ntriangleleft",
'⋫' => raw"\ntriangleright",
'⋬' => raw"\ntrianglelefteq",
'⋭' => raw"\ntrianglerighteq",
'⋮' => raw"\vdots",
'⋯' => raw"\cdots",
'⋰' => raw"\adots",
'⋱' => raw"\ddots",
'⋲' => raw"\disin",
'⋳' => raw"\varisins",
'⋴' => raw"\isins",
'⋵' => raw"\isindot",
'⋶' => raw"\varisinobar",
'⋷' => raw"\isinobar",
'⋸' => raw"\isinvb",
'⋹' => raw"\isinE",
'⋺' => raw"\nisd",
'⋻' => raw"\varnis",
'⋼' => raw"\nis",
'⋽' => raw"\varniobar",
'⋾' => raw"\niobar",
'⋿' => raw"\bagmember",
'⌀' => raw"\diameter",
'⌂' => raw"\house",
'⌅' => raw"\varbarwedge",
'⌆' => raw"\vardoublebarwedge",
'⌈' => raw"\lceil",
'⌉' => raw"\rceil",
'⌊' => raw"\lfloor",
'⌋' => raw"\rfloor",
'⌐' => raw"\invnot",
'⌑' => raw"\sqlozenge",
'⌒' => raw"\profline",
'⌓' => raw"\profsurf",
# '⌕' => raw"\recorder",
'⌗' => raw"\viewdata",
'⌙' => raw"\turnednot",
'⌜' => raw"\ulcorner",
'⌝' => raw"\urcorner",
'⌞' => raw"\llcorner",
'⌟' => raw"\lrcorner",
'⌢' => raw"\frown",
'⌣' => raw"\smile",
'⌬' => raw"\varhexagonlrbonds",
'⌲' => raw"\conictaper",
'⌶' => raw"\topbot",
'⌽' => raw"\obar",
'⌿' => raw"\APLnotslash", # \notslash
'⍀' => raw"\APLnotbackslash", # \notbackslash
'⍓' => raw"\APLboxupcaret", # \boxupcaret
'⍰' => raw"\APLboxquestion", # \boxquestion
'⎔' => raw"\hexagon",
'⎣' => raw"\lbracklend", # \dlcorn
'⎰' => raw"\lmoustache",
'⎱' => raw"\rmoustache",
'⎴' => raw"\overbracket{}",
'⎵' => raw"\underbracket{}",
'⎶' => raw"\bbrktbrk",
'⎷' => raw"\sqrtbottom",
'⎸' => raw"\lvboxline",
'⎹' => raw"\rvboxline",
'⏎' => raw"\varcarriagereturn",
'⏞' => raw"\overbrace{}",
'⏟' => raw"\underbrace{}",
'⏢' => raw"\trapezium",
'⏣' => raw"\benzenr",
'⏤' => raw"\strns",
'⏥' => raw"\fltns",
'⏦' => raw"\accurrent",
'⏧' => raw"\elinters",
'␢' => raw"\blanksymbol",
'␣' => raw"\mathvisiblespace", # \visiblespace
'Ⓢ' => raw"\circledS",
'┆' => raw"\bdtriplevdash", # \dshfnc
# '┙' => raw"\sqfnw",
'╱' => raw"\diagup",
'╲' => raw"\diagdown",
'▀' => raw"\blockuphalf",
'▄' => raw"\blocklowhalf",
'█' => raw"\blockfull",
'▌' => raw"\blocklefthalf",
'▐' => raw"\blockrighthalf",
'░' => raw"\blockqtrshaded",
'▒' => raw"\blockhalfshaded",
'▓' => raw"\blockthreeqtrshaded",
'■' => raw"\blacksquare",
'□' => raw"\square",
'▢' => raw"\squoval",
'▣' => raw"\blackinwhitesquare",
'▤' => raw"\squarehfill",
'▥' => raw"\squarevfill",
'▦' => raw"\squarehvfill",
'▧' => raw"\squarenwsefill",
'▨' => raw"\squareneswfill",
'▩' => raw"\squarecrossfill",
'▪' => raw"\smblksquare",
'▫' => raw"\smwhtsquare",
'▬' => raw"\hrectangleblack",
'▭' => raw"\hrectangle",
'▮' => raw"\vrectangleblack",
'▯' => raw"\vrectangle", # \vrecto
'▰' => raw"\parallelogramblack",
'▱' => raw"\parallelogram",
'▲' => raw"\bigblacktriangleup",
'△' => raw"\bigtriangleup",
'▴' => raw"\blacktriangle",
'▵' => raw"\vartriangle",
'▶' => raw"\blacktriangleright",
'▷' => raw"\triangleright",
'▸' => raw"\smallblacktriangleright",
'▹' => raw"\smalltriangleright",
'►' => raw"\blackpointerright",
'▻' => raw"\whitepointerright",
'▼' => raw"\bigblacktriangledown",
'▽' => raw"\bigtriangledown",
'▾' => raw"\blacktriangledown",
'▿' => raw"\triangledown",
'◀' => raw"\blacktriangleleft",
'◁' => raw"\triangleleft",
'◂' => raw"\smallblacktriangleleft",
'◃' => raw"\smalltriangleleft",
'◄' => raw"\blackpointerleft",
'◅' => raw"\whitepointerleft",
'◆' => raw"\mdlgblkdiamond",
'◇' => raw"\mdlgwhtdiamond",
'◈' => raw"\blackinwhitediamond",
'◉' => raw"\fisheye",
'◊' => raw"\lozenge",
'○' => raw"\bigcirc",
'◌' => raw"\dottedcircle",
'◍' => raw"\circlevertfill",
'◎' => raw"\bullseye",
'●' => raw"\mdlgblkcircle",
'◐' => raw"\circlelefthalfblack", # \cirfl
'◑' => raw"\circlerighthalfblack", # \cirfr
'◒' => raw"\circlebottomhalfblack", # \cirfb
'◓' => raw"\circletophalfblack",
'◔' => raw"\circleurquadblack",
'◕' => raw"\blackcircleulquadwhite",
'◖' => raw"\blacklefthalfcircle",
'◗' => raw"\blackrighthalfcircle",
'◘' => raw"\inversebullet", # \rvbull
'◙' => raw"\inversewhitecircle",
'◚' => raw"\invwhiteupperhalfcircle",
'◛' => raw"\invwhitelowerhalfcircle",
'◜' => raw"\ularc",
'◝' => raw"\urarc",
'◞' => raw"\lrarc",
'◟' => raw"\llarc",
'◠' => raw"\topsemicircle",
'◡' => raw"\botsemicircle",
'◢' => raw"\lrblacktriangle",
'◣' => raw"\llblacktriangle",
'◤' => raw"\ulblacktriangle",
'◥' => raw"\urblacktriangle",
'◦' => raw"\smwhtcircle",
'◧' => raw"\squareleftblack", # \sqfl
'◨' => raw"\squarerightblack", # \sqfr
'◩' => raw"\squareulblack",
'◪' => raw"\squarelrblack", # \sqfse
'◫' => raw"\boxbar",
'◬' => raw"\trianglecdot",
'◭' => raw"\triangleleftblack",
'◮' => raw"\trianglerightblack",
'◯' => raw"\lgwhtcircle",
'◰' => raw"\squareulquad",
'◱' => raw"\squarellquad",
'◲' => raw"\squarelrquad",
'◳' => raw"\squareurquad",
'◴' => raw"\circleulquad",
'◵' => raw"\circlellquad",
'◶' => raw"\circlelrquad",
'◷' => raw"\circleurquad",
'◸' => raw"\ultriangle",
'◹' => raw"\urtriangle",
'◺' => raw"\lltriangle",
'◻' => raw"\mdwhtsquare",
'◼' => raw"\mdblksquare",
'◽' => raw"\mdsmwhtsquare",
'◾' => raw"\mdsmblksquare",
'◿' => raw"\lrtriangle",
'★' => raw"\bigstar",
'☆' => raw"\bigwhitestar",
'☉' => raw"\astrosun",
'☡' => raw"\danger",
'☻' => raw"\blacksmiley",
'☼' => raw"\sun",
'☽' => raw"\rightmoon",
'☾' => raw"\leftmoon",
# '☿' => raw"\mercury",
'♀' => raw"\female", # \venus
'♂' => raw"\male", # \mars
# '♃' => raw"\jupiter", # `marvosym` or `wasysym`
# '♄' => raw"\saturn",
# '♅' => raw"\uranus",
# '♆' => raw"\neptune",
# '♇' => raw"\pluto",
# '♈' => raw"\aries",
# '♉' => raw"\taurus",
# '♊' => raw"\gemini",
# '♋' => raw"\cancer",
# '♌' => raw"\leo",
# '♍' => raw"\virgo",
# '♎' => raw"\libra",
# '♏' => raw"\scorpio",
# '♐' => raw"\sagittarius",
# '♑' => raw"\capricornus",
# '♒' => raw"\aquarius",
# '♓' => raw"\pisces",
'♠' => raw"\spadesuit",
'♡' => raw"\heartsuit",
'♢' => raw"\diamondsuit",
'♣' => raw"\clubsuit",
'♤' => raw"\varspadesuit",
'♥' => raw"\varheartsuit",
'♦' => raw"\vardiamondsuit",
'♧' => raw"\varclubsuit",
'♩' => raw"\quarternote",
'♪' => raw"\eighthnote",
'♫' => raw"\twonotes",
'♭' => raw"\flat",
'♮' => raw"\natural",
'♯' => raw"\sharp",
'♾' => raw"\acidfree",
'⚀' => raw"\dicei",
'⚁' => raw"\diceii",
'⚂' => raw"\diceiii",
'⚃' => raw"\diceiv",
'⚄' => raw"\dicev",
'⚅' => raw"\dicevi",
'⚆' => raw"\circledrightdot",
'⚇' => raw"\circledtwodots",
'⚈' => raw"\blackcircledrightdot",
'⚉' => raw"\blackcircledtwodots",
'⚥' => raw"\Hermaphrodite", # \hermaphrodite
'⚪' => raw"\mdwhtcircle",
'⚫' => raw"\mdblkcircle",
'⚬' => raw"\mdsmwhtcircle",
'⚲' => raw"\neuter",
'✓' => raw"\checkmark",
'✠' => raw"\maltese",
'✪' => raw"\circledstar",
'✶' => raw"\varstar",
'✽' => raw"\dingasterisk",
'➛' => raw"\draftingarrow",
'⟀' => raw"\threedangle",
'⟁' => raw"\whiteinwhitetriangle",
'⟂' => raw"\perp",
'⟈' => raw"\bsolhsub",
'⟉' => raw"\suphsol",
'⟑' => raw"\wedgedot",
'⟒' => raw"\upin",
'⟕' => raw"\leftouterjoin",
'⟖' => raw"\rightouterjoin",
'⟗' => raw"\fullouterjoin",
'⟘' => raw"\bigbot",
'⟙' => raw"\bigtop",
'⟦' => raw"\lBrack", # \llbracket, \openbracketleft
'⟧' => raw"\rBrack", # \rrbracket, \openbracketright
'⟨' => raw"\langle",
'⟩' => raw"\rangle",
'⟰' => raw"\UUparrow",
'⟱' => raw"\DDownarrow",
'⟵' => raw"\longleftarrow",
'⟶' => raw"\longrightarrow",
'⟷' => raw"\longleftrightarrow",
'⟸' => raw"\Longleftarrow", # \impliedby
'⟹' => raw"\Longrightarrow", # \implies
'⟺' => raw"\Longleftrightarrow", # \iff
'⟻' => raw"\longmapsfrom",
'⟼' => raw"\longmapsto",
'⟽' => raw"\Longmapsfrom",
'⟾' => raw"\Longmapsto",
'⟿' => raw"\longrightsquigarrow",
'⤀' => raw"\nvtwoheadrightarrow",
'⤁' => raw"\nVtwoheadrightarrow",
'⤂' => raw"\nvLeftarrow",
'⤃' => raw"\nvRightarrow",
'⤄' => raw"\nvLeftrightarrow",
'⤅' => raw"\twoheadmapsto",
'⤆' => raw"\Mapsfrom",
'⤇' => raw"\Mapsto",
'⤈' => raw"\downarrowbarred",
'⤉' => raw"\uparrowbarred",
'⤊' => raw"\Uuparrow",
'⤋' => raw"\Ddownarrow",
'⤌' => raw"\leftbkarrow",
'⤍' => raw"\rightbkarrow", # \bkarow
'⤎' => raw"\leftdbkarrow",
'⤏' => raw"\dbkarow",
'⤐' => raw"\drbkarrow",
'⤑' => raw"\rightdotarrow",
'⤒' => raw"\baruparrow", # \UpArrowBar
'⤓' => raw"\downarrowbar", # \DownArrowBar
'⤔' => raw"\nvrightarrowtail",
'⤕' => raw"\nVrightarrowtail",
'⤖' => raw"\twoheadrightarrowtail",
'⤗' => raw"\nvtwoheadrightarrowtail",
'⤘' => raw"\nVtwoheadrightarrowtail",
'⤝' => raw"\diamondleftarrow",
'⤞' => raw"\rightarrowdiamond",
'⤟' => raw"\diamondleftarrowbar",
'⤠' => raw"\barrightarrowdiamond",
'⤥' => raw"\hksearow",
'⤦' => raw"\hkswarow",
'⤧' => raw"\tona",
'⤨' => raw"\toea",
'⤩' => raw"\tosa",
'⤪' => raw"\towa",
'⤫' => raw"\rdiagovfdiag",
'⤬' => raw"\fdiagovrdiag",
'⤭' => raw"\seovnearrow",
'⤮' => raw"\neovsearrow",
'⤯' => raw"\fdiagovnearrow",
'⤰' => raw"\rdiagovsearrow",
'⤱' => raw"\neovnwarrow",
'⤲' => raw"\nwovnearrow",
'⥂' => raw"\rightarrowshortleftarrow", # \Rlarr
'⥄' => raw"\leftarrowshortrightarrow", # \rLarr
'⥅' => raw"\rightarrowplus",
'⥆' => raw"\leftarrowplus",
'⥇' => raw"\rightarrowx", # \rarrx
'⥈' => raw"\leftrightarrowcircle",
'⥉' => raw"\twoheaduparrowcircle",
'⥊' => raw"\leftrightharpoonupdown",
'⥋' => raw"\leftrightharpoondownup",
'⥌' => raw"\updownharpoonrightleft",
'⥍' => raw"\updownharpoonleftright",
'⥎' => raw"\leftrightharpoonupup", # \LeftRightVector
'⥏' => raw"\updownharpoonrightright", # \RightUpDownVector
'⥐' => raw"\leftrightharpoondowndown", # \DownLeftRightVector
'⥑' => raw"\updownharpoonleftleft", # \LeftUpDownVector
'⥒' => raw"\barleftharpoonup", # \LeftVectorBar
'⥓' => raw"\rightharpoonupbar", # \RightVectorBar
'⥔' => raw"\barupharpoonright", # \RightUpVectorBar
'⥕' => raw"\downharpoonrightbar", # \RightDownVectorBar
'⥖' => raw"\barleftharpoondown", # \DownLeftVectorBar
'⥗' => raw"\rightharpoondownbar", # \DownRightVectorBar
'⥘' => raw"\barupharpoonleft", # \LeftUpVectorBar
'⥙' => raw"\downharpoonleftbar", # \LeftDownVectorBar
'⥚' => raw"\leftharpoonupbar", # \LeftTeeVector
'⥛' => raw"\barrightharpoonup", # \RightTeeVector
'⥜' => raw"\upharpoonrightbar", # \RightUpTeeVector
'⥝' => raw"\bardownharpoonright", # \RightDownTeeVector
'⥞' => raw"\leftharpoondownbar", # \DownLeftTeeVector
'⥟' => raw"\barrightharpoondown", # \DownRightTeeVector
'⥠' => raw"\upharpoonleftbar", # \LeftUpTeeVector
'⥡' => raw"\bardownharpoonleft", # \LeftDownTeeVector
'⥢' => raw"\leftharpoonsupdown",
'⥣' => raw"\upharpoonsleftright",
'⥤' => raw"\rightharpoonsupdown",
'⥥' => raw"\downharpoonsleftright",
'⥦' => raw"\leftrightharpoonsup",
'⥧' => raw"\leftrightharpoonsdown",
'⥨' => raw"\rightleftharpoonsup",
'⥩' => raw"\rightleftharpoonsdown",
'⥪' => raw"\leftharpoonupdash",
'⥫' => raw"\dashleftharpoondown",
'⥬' => raw"\rightharpoonupdash",
'⥭' => raw"\dashrightharpoondown",
'⥮' => raw"\updownharpoonsleftright", # \UpEquilibrium
'⥯' => raw"\downupharpoonsleftright", # \ReverseUpEquilibrium
'⥰' => raw"\rightimply", # \RoundImplies
'⦀' => raw"\Vvert",
'⦆' => raw"\rParen", # \Elroang
'⦙' => raw"\fourvdots", # \ddfnc
'⦛' => raw"\measuredangleleft",
'⦜' => raw"\rightanglesqr", # \Angle
'⦝' => raw"\rightanglemdot",
'⦞' => raw"\angles",
'⦟' => raw"\angdnr",
'⦠' => raw"\gtlpar", # \lpargt
'⦡' => raw"\sphericalangleup",
'⦢' => raw"\turnangle",
'⦣' => raw"\revangle",
'⦤' => raw"\angleubar",
'⦥' => raw"\revangleubar",
'⦦' => raw"\wideangledown",
'⦧' => raw"\wideangleup",
'⦨' => raw"\measanglerutone",
'⦩' => raw"\measanglelutonw",
'⦪' => raw"\measanglerdtose",
'⦫' => raw"\measangleldtosw",
'⦬' => raw"\measangleurtone",
'⦭' => raw"\measangleultonw",
'⦮' => raw"\measangledrtose",
'⦯' => raw"\measangledltosw",
'⦰' => raw"\revemptyset",
'⦱' => raw"\emptysetobar",
'⦲' => raw"\emptysetocirc",
'⦳' => raw"\emptysetoarr",
'⦴' => raw"\emptysetoarrl",
'⦷' => raw"\circledparallel",
'⦸' => raw"\obslash",
'⦼' => raw"\odotslashdot",
'⦾' => raw"\circledwhitebullet",
'⦿' => raw"\circledbullet",
'⧀' => raw"\olessthan",
'⧁' => raw"\ogreaterthan",
'⧄' => raw"\boxdiag",
'⧅' => raw"\boxbslash",
'⧆' => raw"\boxast",
'⧇' => raw"\boxcircle",
'⧊' => raw"\triangleodot", # \Lap
'⧋' => raw"\triangleubar", # \defas
'⧏' => raw"\ltrivb", # \LeftTriangleBar
"⧏̸" => raw"\not\ltrivb", # \NotLeftTriangleBar
'⧐' => raw"\vbrtri", # \RightTriangleBar
"⧐̸" => raw"\not\vbrtri", # \NotRightTriangleBar
'⧟' => raw"\dualmap",
'⧡' => raw"\lrtriangleeq",
'⧢' => raw"\shuffle",
'⧣' => raw"\eparsl",
'⧤' => raw"\smeparsl",
'⧥' => raw"\eqvparsl",
'⧫' => raw"\blacklozenge",
'⧴' => raw"\ruledelayed", # \RuleDelayed
'⧶' => raw"\dsol",
'⧷' => raw"\rsolbar",
'⧺' => raw"\doubleplus",
'⧻' => raw"\tripleplus",
'⨀' => raw"\bigodot",
'⨁' => raw"\bigoplus",
'⨂' => raw"\bigotimes",
'⨃' => raw"\bigcupdot",
'⨄' => raw"\biguplus",
'⨅' => raw"\bigsqcap",
'⨆' => raw"\bigsqcup",
'⨇' => raw"\conjquant",
'⨈' => raw"\disjquant",
'⨉' => raw"\bigtimes",
'⨊' => raw"\modtwosum",
'⨋' => raw"\sumint",
'⨌' => raw"\iiiint",
'⨍' => raw"\intbar",
'⨎' => raw"\intBar",
'⨏' => raw"\fint", # \clockoint
'⨐' => raw"\cirfnint",
'⨑' => raw"\awint",
'⨒' => raw"\rppolint",
'⨓' => raw"\scpolint",
'⨔' => raw"\npolint",
'⨕' => raw"\pointint",
'⨖' => raw"\sqint", # \sqrint
'⨘' => raw"\intx",
'⨙' => raw"\intcap",
'⨚' => raw"\intcup",
'⨛' => raw"\upint",
'⨜' => raw"\lowint",
'⨝' => raw"\Join", # \join
'⨟' => raw"\zcmp", # \bbsemi
'⨢' => raw"\ringplus",
'⨣' => raw"\plushat",
'⨤' => raw"\simplus",
'⨥' => raw"\plusdot",
'⨦' => raw"\plussim",
'⨧' => raw"\plussubtwo",
'⨨' => raw"\plustrif",
'⨩' => raw"\commaminus",
'⨪' => raw"\minusdot",
'⨫' => raw"\minusfdots",
'⨬' => raw"\minusrdots",
'⨭' => raw"\opluslhrim",
'⨮' => raw"\oplusrhrim",
'⨯' => raw"\vectimes", # \Times
'⨰' => raw"\dottimes",
'⨱' => raw"\timesbar",
'⨲' => raw"\btimes",
'⨳' => raw"\smashtimes",
'⨴' => raw"\otimeslhrim",
'⨵' => raw"\otimesrhrim",
'⨶' => raw"\otimeshat",
'⨷' => raw"\Otimes",
'⨸' => raw"\odiv",
'⨹' => raw"\triangleplus",
'⨺' => raw"\triangleminus",
'⨻' => raw"\triangletimes",
'⨼' => raw"\intprod",
'⨽' => raw"\intprodr",
'⨿' => raw"\amalg",
'⩀' => raw"\capdot",
'⩁' => raw"\uminus",
'⩂' => raw"\barcup",
'⩃' => raw"\barcap",
'⩄' => raw"\capwedge",
'⩅' => raw"\cupvee",
'⩊' => raw"\twocups",
'⩋' => raw"\twocaps",
'⩌' => raw"\closedvarcup",
'⩍' => raw"\closedvarcap",
'⩎' => raw"\Sqcap",
'⩏' => raw"\Sqcup",
'⩐' => raw"\closedvarcupsmashprod",
'⩑' => raw"\wedgeodot",
'⩒' => raw"\veeodot",
'⩓' => raw"\Wedge", # \And
'⩔' => raw"\Vee", # \Or
'⩕' => raw"\wedgeonwedge",
'⩖' => raw"\veeonvee", # \ElOr
'⩗' => raw"\bigslopedvee",
'⩘' => raw"\bigslopedwedge",
'⩚' => raw"\wedgemidvert",
'⩛' => raw"\veemidvert",
'⩜' => raw"\midbarwedge",
'⩝' => raw"\midbarvee",
'⩞' => raw"\doublebarwedge", # \perspcorrespond
'⩟' => raw"\wedgebar", # \minhat
'⩠' => raw"\wedgedoublebar",
'⩡' => raw"\varveebar",
'⩢' => raw"\doublebarvee",
'⩣' => raw"\veedoublebar",
'⩦' => raw"\eqdot",
'⩧' => raw"\dotequiv",
'⩪' => raw"\dotsim",
'⩫' => raw"\simrdots",
'⩬' => raw"\simminussim",
'⩭' => raw"\congdot",
'⩮' => raw"\asteq",
'⩯' => raw"\hatapprox",
'⩰' => raw"\approxeqq",
'⩱' => raw"\eqqplus",
'⩲' => raw"\pluseqq",
'⩳' => raw"\eqqsim",
'⩴' => raw"\Coloneq",
'⩵' => raw"\eqeq", # \Equal
'⩶' => raw"\eqeqeq",
'⩷' => raw"\ddotseq",
'⩸' => raw"\equivDD",
'⩹' => raw"\ltcir",
'⩺' => raw"\gtcir",
'⩻' => raw"\ltquest",
'⩼' => raw"\gtquest",
'⩽' => raw"\leqslant",
"⩽̸" => raw"\not\leqslant", # \nleqslant
'⩾' => raw"\geqslant",
"⩾̸" => raw"\not\geqslant", # \ngeqslant
'⩿' => raw"\lesdot",
'⪀' => raw"\gesdot",
'⪁' => raw"\lesdoto",
'⪂' => raw"\gesdoto",
'⪃' => raw"\lesdotor",
'⪄' => raw"\gesdotol",
'⪅' => raw"\lessapprox",
'⪆' => raw"\gtrapprox",
'⪇' => raw"\lneq",
'⪈' => raw"\gneq",
'⪉' => raw"\lnapprox",
'⪊' => raw"\gnapprox",
'⪋' => raw"\lesseqqgtr",
'⪌' => raw"\gtreqqless",
'⪍' => raw"\lsime",
'⪎' => raw"\gsime",
'⪏' => raw"\lsimg",
'⪐' => raw"\gsiml",
'⪑' => raw"\lgE",
'⪒' => raw"\glE",
'⪓' => raw"\lesges",
'⪔' => raw"\gesles",
'⪕' => raw"\eqslantless",
'⪖' => raw"\eqslantgtr",
'⪗' => raw"\elsdot",
'⪘' => raw"\egsdot",
'⪙' => raw"\eqqless",
'⪚' => raw"\eqqgtr",
'⪛' => raw"\eqqslantless",
'⪜' => raw"\eqqslantgtr",
'⪝' => raw"\simless",
'⪞' => raw"\simgtr",
'⪟' => raw"\simlE",
'⪠' => raw"\simgE",
'⪡' => raw"\Lt", # \NestedLessLess
"⪡̸" => raw"\not\Lt",
'⪢' => raw"\Gt", # \NestedGreaterGreater
"⪢̸" => raw"\not\Gt",
'⪣' => raw"\partialmeetcontraction",
'⪤' => raw"\glj",
'⪥' => raw"\gla",
'⪦' => raw"\ltcc",
'⪧' => raw"\gtcc",
'⪨' => raw"\lescc",
'⪩' => raw"\gescc",
'⪪' => raw"\smt",
'⪫' => raw"\lat",
'⪬' => raw"\smte",
'⪭' => raw"\late",
'⪮' => raw"\bumpeqq",
'⪯' => raw"\preceq",
"⪯̸" => raw"\not\preceq", # \npreceq
'⪰' => raw"\succeq",
"⪰̸" => raw"\not\nsucceq", # \nsucceq
'⪱' => raw"\precneq",
'⪲' => raw"\succneq",
'⪳' => raw"\preceqq",
'⪴' => raw"\succeqq",
'⪵' => raw"\precneqq",
'⪶' => raw"\succneqq",
'⪷' => raw"\precapprox",
'⪸' => raw"\succapprox",
'⪹' => raw"\precnapprox",
'⪺' => raw"\succnapprox",
'⪻' => raw"\Prec",
'⪼' => raw"\Succ",
'⪽' => raw"\subsetdot",
'⪾' => raw"\supsetdot",
'⪿' => raw"\subsetplus",
'⫀' => raw"\supsetplus",
'⫁' => raw"\submult",
'⫂' => raw"\supmult",
'⫃' => raw"\subedot",
'⫄' => raw"\supedot",
'⫅' => raw"\subseteqq",
"⫅̸" => raw"\not\subseteqq", # \subseteqq
'⫆' => raw"\supseteqq",
"⫆̸" => raw"\not\supseteqq", # \supseteqq
'⫇' => raw"\subsim",
'⫈' => raw"\supsim",
'⫉' => raw"\subsetapprox",
'⫊' => raw"\supsetapprox",
'⫋' => raw"\subsetneqq",
'⫌' => raw"\supsetneqq",
'⫍' => raw"\lsqhook",
'⫎' => raw"\rsqhook",
'⫏' => raw"\csub",
'⫐' => raw"\csup",
'⫑' => raw"\csube",
'⫒' => raw"\csupe",
'⫓' => raw"\subsup",
'⫔' => raw"\supsub",
'⫕' => raw"\subsub",
'⫖' => raw"\supsup",
'⫗' => raw"\suphsub",
'⫘' => raw"\supdsub",
'⫙' => raw"\forkv",
'⫛' => raw"\mlcp",
'⫝̸' => raw"\forks",
'⫝' => raw"\forksnot",
'⫣' => raw"\dashV",
'⫤' => raw"\Dashv",
'⫪' => raw"\barV", # \downvDash
'⫫' => raw"\Vbar", # \upvDash, \indep
'⫴' => raw"\interleave",
'⫶' => raw"\threedotcolon", # \tdcol
'⫷' => raw"\lllnest",
'⫸' => raw"\gggnest",
'⫹' => raw"\leqqslant",
'⫺' => raw"\geqqslant",
'⬒' => raw"\squaretopblack",
'⬓' => raw"\squarebotblack",
'⬔' => raw"\squareurblack",
'⬕' => raw"\squarellblack",
'⬖' => raw"\diamondleftblack",
'⬗' => raw"\diamondrightblack",
'⬘' => raw"\diamondtopblack",
'⬙' => raw"\diamondbotblack",
'⬚' => raw"\dottedsquare",
'⬛' => raw"\lgblksquare",
'⬜' => raw"\lgwhtsquare",
'⬝' => raw"\vysmblksquare",
'⬞' => raw"\vysmwhtsquare",
'⬟' => raw"\pentagonblack",
'⬠' => raw"\pentagon",
'⬡' => raw"\varhexagon",
'⬢' => raw"\varhexagonblack",
'⬣' => raw"\hexagonblack",
'⬤' => raw"\lgblkcircle",
'⬥' => raw"\mdblkdiamond",
'⬦' => raw"\mdwhtdiamond",
'⬧' => raw"\mdblklozenge",
'⬨' => raw"\mdwhtlozenge",
'⬩' => raw"\smblkdiamond",
'⬪' => raw"\smblklozenge",
'⬫' => raw"\smwhtlozenge",
'⬬' => raw"\blkhorzoval",
'⬭' => raw"\whthorzoval",
'⬮' => raw"\blkvertoval",
'⬯' => raw"\whtvertoval",
'⬰' => raw"\circleonleftarrow",
'⬱' => raw"\leftthreearrows",
'⬲' => raw"\leftarrowonoplus",
'⬳' => raw"\longleftsquigarrow",
'⬴' => raw"\nvtwoheadleftarrow",
'⬵' => raw"\nVtwoheadleftarrow",
'⬶' => raw"\twoheadmapsfrom",
'⬷' => raw"\twoheadleftdbkarrow",
'⬸' => raw"\leftdotarrow",
'⬹' => raw"\nvleftarrowtail",
'⬺' => raw"\nVleftarrowtail",
'⬻' => raw"\twoheadleftarrowtail",
'⬼' => raw"\nvtwoheadleftarrowtail",
'⬽' => raw"\nVtwoheadleftarrowtail",
'⬾' => raw"\leftarrowx",
'⬿' => raw"\leftcurvedarrow",
'⭀' => raw"\equalleftarrow",
'⭁' => raw"\bsimilarleftarrow",
'⭂' => raw"\leftarrowbackapprox",
'⭃' => raw"\rightarrowgtr",
'⭄' => raw"\rightarrowsupset",
'⭅' => raw"\LLeftarrow",
'⭆' => raw"\RRightarrow",
'⭇' => raw"\bsimilarrightarrow",
'⭈' => raw"\rightarrowbackapprox",
'⭉' => raw"\similarleftarrow",
'⭊' => raw"\leftarrowapprox",
'⭋' => raw"\leftarrowbsimilar",
'⭌' => raw"\rightarrowbsimilar",
'⭐' => raw"\medwhitestar",
'⭑' => raw"\medblackstar",
'⭒' => raw"\smwhitestar",
'⭓' => raw"\rightpentagonblack",
'⭔' => raw"\rightpentagon",
'ⱼ' => raw"{_j}",
'ⱽ' => raw"{^V}",
'〒' => raw"\postalmark",
'ꜛ' => raw"{^\uparrow}",
'ꜜ' => raw"{^\downarrow}",
'ꜝ' => raw"{^!}",
'𝚤' => raw"\mathit{\imath}",
'𝚥' => raw"\mathit{\jmath}",
latex_emphasis(vcat('A':'Z', 'a':'z', '0':'9'))...,
map(x -> x[2] => "\\mathbf{$(greek_seq[x[1]])}", enumerate('𝚨':'𝛡'))..., # greek with bold emphasis (x58)
map(x -> x[2] => "\\mathit{$(greek_seq[x[1]])}", enumerate('𝛢':'𝜛'))..., # greek with italic emphasis
map(x -> x[2] => "\\mathbfit{$(greek_seq[x[1]])}", enumerate('𝜜':'𝝕'))..., # greek with bold+italic emphasis
map(x -> x[2] => "\\mathbfsfup{$(greek_seq[x[1]])}", enumerate('𝝖':'𝞏'))..., # greek sans-serif with bold emphasis
map(x -> x[2] => "\\mathbfsfit{$(greek_seq[x[1]])}", enumerate('𝞐':'𝟉'))..., # greek sans-serif with bold+italic emphasis
'𝟊' => raw"\mbfDigamma", # \Digamma
'𝟋' => raw"\mbfdigamma", # \digamm
latex_diacritics(vcat('A':'Z', 'a':'z'))...,
)
unicode2latex(c::Char) = unicode2latex(string(c))
function unicode2latex(str::String; safescripts=false)
isascii(str) && return str
str = Unicode.normalize(str; decompose=true) # Get rid of pre-composed characters
c_or_s = sizehint!(Union{Char,String}[], length(str))
it = Iterators.Stateful(str)
while !isempty(it)
c = popfirst!(it)
push!(
c_or_s, # see en.wikipedia.org/wiki/Combining_character
if Unicode.category_code(something(peek(it), '0')) == Unicode.UTF8PROC_CATEGORY_MN
c * popfirst!(it)
else
c
end
)
end
str_array = map(k -> get(unicodedict, k, k), c_or_s)
it = Iterators.Stateful(str_array)
for (n, x) ∈ enumerate(it)
x isa String || continue
# Deal with math mode modifiers (\hat, \tilde, \bar, \dot, ..., \ddddot)
if endswith(x, Char(0x302))
x = "\\hat{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x303))
x = "\\tilde{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x304))
x = "\\bar{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x307))
x = "\\dot{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x308))
x = "\\ddot{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x20DB))
x = "\\dddot{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
elseif endswith(x, Char(0x20DC))
x = "\\ddddot{$(unicode2latex(x[begin:prevind(x, end)]))}"
str_array[n] = x
end
if (next = peek(it)) !== nothing && length(next) == 1
c = next isa Char ? next : first(next)
if isletter(c) || isdigit(c)
str_array[n] = "{$x}"
end
end
end
str = merge_subscripts(join(str_array); safescripts=safescripts)
return merge_superscripts(str; safescripts=safescripts)
end
"""
merge_superscripts(str; safescripts=false)
Merge sequential superscripts to a better representation.
Returns a string where sequences like "{^1}{^3}" are replaced by "^{1 3}".
If `safescripts` is `true`, makes `{^{1 3}}`, which is less aesthetic but might succeed with
certain combinations where `false` would not.
"""
function merge_superscripts(str; safescripts=false)
# pair {^q}{^q}{^q}{^q}{^q} --> {^{q q}}{^{q q}}{^q}
str = replace(str, r"{\^([^{}]*)}{\^([^{}]*)}" => s"{^{\1 \2}}")
# collect ends if needed {^{q q}}{^{q q}}{^q} --> {^{q q}}{^{q q q}}
str = replace(str, r"{\^{([^{}]*)}}{\^([^{}]*)}" => s"{^{\1 \2}}")
str = replace(str, r"{\^{([^{}]*)}}{{\^([^{}]*)}}" => s"{^{\1 \2}}") # if last one was protected by extra {}
# complete merge {^{q q}}{^{q q q}} --> {^{q q q q q}}
r = r"{\^{([^{}]*)}}{\^{([^{}]*)}}"
while match(r, str) !== nothing
str = replace(str, r => s"{^{\1 \2}}")
end
if ~safescripts
# remove external braces
str = replace(str, r"{\^{([^{}]*)}}" => s"^{\1}")
# deal with superscripts that did not need to be merged
str = replace(str, r"{{\^([^{}]*)}}" => s"^{\1}")
str = replace(str, r"{\^([^{}]*)}" => s"^\1")
end
return str
end
"""
merge_subscripts(str; safescripts=false)
Merge sequential subscripts to a better representation.
Returns a st
gitextract_q48ri8b3/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── CompatHelper.yml
│ ├── Invalidations.yml
│ ├── TagBot.yml
│ ├── benchmark.yml
│ └── ci.yml
├── .gitignore
├── LICENSE
├── Project.toml
├── README.md
├── assets/
│ ├── Project.toml
│ └── assets.jl
├── benchmark/
│ ├── Project.toml
│ └── benchmarks.jl
├── docs/
│ ├── .gitignore
│ ├── Project.toml
│ ├── make.jl
│ └── src/
│ ├── arguments.md
│ ├── functions/
│ │ ├── latexalign.md
│ │ ├── latexarray.md
│ │ ├── latexify.md
│ │ ├── latexoperation.md
│ │ └── latexraw.md
│ ├── index.md
│ ├── table_generator.jl
│ └── tutorials/
│ ├── Catalyst.md
│ ├── inner_workings.md
│ ├── latexalign.md
│ ├── latexarray.md
│ ├── latexify.md
│ ├── latexinline.md
│ ├── latextabular.md
│ ├── notebooks.md
│ ├── parameterizedfunctions.md
│ ├── recipes.md
│ └── rendering_latex.md
├── ext/
│ ├── DataFramesExt.jl
│ ├── SparseArraysExt.jl
│ ├── SymEngineExt.jl
│ └── TectonicExt.jl
├── paper/
│ ├── paper.bib
│ └── paper.md
├── src/
│ ├── Latexify.jl
│ ├── default_kwargs.jl
│ ├── error.jl
│ ├── internal_recipes.jl
│ ├── latexalign.jl
│ ├── latexarray.jl
│ ├── latexbracket.jl
│ ├── latexequation.jl
│ ├── latexify_function.jl
│ ├── latexinline.jl
│ ├── latexoperation.jl
│ ├── latexraw.jl
│ ├── latextabular.jl
│ ├── macros.jl
│ ├── md.jl
│ ├── mdtable.jl
│ ├── mdtext.jl
│ ├── numberformatters.jl
│ ├── recipes.jl
│ ├── symbol_translations.jl
│ ├── unicode2latex.jl
│ └── utils.jl
└── test/
├── cdot_test.jl
├── chemical_arrows_test.jl
├── latexalign_test.jl
├── latexarray_test.jl
├── latexbracket_test.jl
├── latexequation_test.jl
├── latexify_test.jl
├── latexinline_test.jl
├── latexraw_test.jl
├── latextabular_test.jl
├── macros.jl
├── manual_test.jl
├── mdtable_test.jl
├── numberformatters_test.jl
├── plugins/
│ ├── DataFrames_test.jl
│ ├── SparseArrays_test.jl
│ └── SymEngine_test.jl
├── recipe_test.jl
├── runtests.jl
├── unicode2latex.jl
└── utils_test.jl
Condensed preview — 85 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (290K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 15,
"preview": "github: korsbo\n"
},
{
"path": ".github/workflows/CompatHelper.yml",
"chars": 670,
"preview": "name: CompatHelper\non:\n schedule:\n - cron: 0 0 * * *\n workflow_dispatch:\njobs:\n CompatHelper:\n runs-on: ubuntu-"
},
{
"path": ".github/workflows/Invalidations.yml",
"chars": 1472,
"preview": "name: Invalidations\n\non:\n pull_request:\n\nconcurrency:\n # Skip intermediate builds: always.\n # Cancel intermediate bui"
},
{
"path": ".github/workflows/TagBot.yml",
"chars": 380,
"preview": "name: TagBot\non:\n issue_comment: # THIS BIT IS NEW\n types:\n - created\n workflow_dispatch:\njobs:\n TagBot:\n "
},
{
"path": ".github/workflows/benchmark.yml",
"chars": 855,
"preview": "name: Run benchmarks\n\non:\n pull_request:\n types: [labeled, opened, synchronize, reopened]\n\njobs:\n Benchmark:\n ru"
},
{
"path": ".github/workflows/ci.yml",
"chars": 2516,
"preview": "name: CI\non:\n pull_request:\n branches:\n - master\n push:\n branches:\n - master\n tags: '*'\n\nconcurrenc"
},
{
"path": ".gitignore",
"chars": 123,
"preview": "*.jl.cov\n*.jl.*.cov\n*.jl.mem\ndeps/deps.jl\n*Manifest.toml\n\ndocs/build\ndocs/site\n*.DS_Store\n\n/.benchmarkci\n/benchmark/*.js"
},
{
"path": "LICENSE",
"chars": 1070,
"preview": "MIT License\n\nCopyright (c) 2017 Niklas Korsbo\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
},
{
"path": "Project.toml",
"chars": 1650,
"preview": "name = \"Latexify\"\nuuid = \"23fbe1c1-3f47-55db-b15f-69d7ec21a316\"\nauthors = [\"Niklas Korsbo <niklas.korsbo@gmail.com>\"]\nre"
},
{
"path": "README.md",
"chars": 6271,
"preview": "[](https://korsbo.github.io/Latexify.jl/stable)\n[\nusing Latexify, LaTeXStrings, ParameterizedFunctions, Catalyst\n\nstruct Ket{T}\n "
},
{
"path": "benchmark/Project.toml",
"chars": 220,
"preview": "[deps]\nBenchmarkCI = \"20533458-34a3-403d-a444-e18f38190b5b\"\nBenchmarkTools = \"6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf\"\nLate"
},
{
"path": "benchmark/benchmarks.jl",
"chars": 714,
"preview": "using BenchmarkTools\nusing Latexify\n\nconst SUITE = BenchmarkGroup()\n\nchars = vcat(\n 'A':'Z',\n 'a"
},
{
"path": "docs/.gitignore",
"chars": 13,
"preview": "build/\nsite/\n"
},
{
"path": "docs/Project.toml",
"chars": 288,
"preview": "[deps]\nDocumenter = \"e30172f5-a6a5-5a46-863b-614d45cd2de4\"\nFormat = \"1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8\"\nLaTeXStrings "
},
{
"path": "docs/make.jl",
"chars": 1315,
"preview": "using Documenter\nusing Latexify\nusing LaTeXStrings\n\nBase.show(io::IO, ::MIME\"text/html\", l::LaTeXString) = l.s\nmakedocs("
},
{
"path": "docs/src/arguments.md",
"chars": 1330,
"preview": "# List of possible arguments\n\n## Align\n```@eval\nBase.include(@__MODULE__, \"src/table_generator.jl\")\nargs = [arg for arg "
},
{
"path": "docs/src/functions/latexalign.md",
"chars": 159,
"preview": "# `latexalign`\n\n```@meta\nDocTestSetup = quote\nusing Latexify\nusing DifferentialEquations\nend\n```\n\n```@docs\nlatexalign\n``"
},
{
"path": "docs/src/functions/latexarray.md",
"chars": 159,
"preview": "# `latexarray`\n\n```@meta\nDocTestSetup = quote\nusing Latexify\nusing DifferentialEquations\nend\n```\n\n```@docs\nlatexarray\n``"
},
{
"path": "docs/src/functions/latexify.md",
"chars": 127,
"preview": "# `latexify`\n\n```@meta\nDocTestSetup = quote\nusing Latexify\nend\n```\n\n```@docs\nlatexify\n```\n\n```@meta\nDocTestSetup = nothi"
},
{
"path": "docs/src/functions/latexoperation.md",
"chars": 208,
"preview": "# `latexoperation`\n\nThis function is not exported.\n\n```@meta\nDocTestSetup = quote\nusing Latexify\nusing DifferentialEquat"
},
{
"path": "docs/src/functions/latexraw.md",
"chars": 114,
"preview": "# `latexraw`\n\nFormats the input for ``\\LaTeX`` without surrounding it with an environment.\n\n```@docs\nlatexraw\n```\n"
},
{
"path": "docs/src/index.md",
"chars": 11726,
"preview": "# Latexify.jl\n\n[Latexify.jl](https://github.com/korsbo/Latexify.jl) is a package which supplies functions for producing "
},
{
"path": "docs/src/table_generator.jl",
"chars": 6196,
"preview": "#=\nIn the documents, there are tables of what keyword arguments can be passed\nto latexify for different outputs or input"
},
{
"path": "docs/src/tutorials/Catalyst.md",
"chars": 3553,
"preview": "# Use with @reaction_network from Catalyst.jl.\n\nLatexify.jl has methods for dealing with the `ReactionNetwork` models ge"
},
{
"path": "docs/src/tutorials/inner_workings.md",
"chars": 3531,
"preview": "# Inner workings\n\nThis package contains a large number of methods, but two of these are of special importance.\nThese are"
},
{
"path": "docs/src/tutorials/latexalign.md",
"chars": 1469,
"preview": "\n# [`latexalign`](@id latexalign_tutorial)\n\nThis function converts its input to ``\\LaTeX`` align environments.\nOne way o"
},
{
"path": "docs/src/tutorials/latexarray.md",
"chars": 835,
"preview": "# `latexarray`\n\n\nThis functions takes a 1 or 2D array and spits out a latex array environment.\nFor example:\n\n```julia-re"
},
{
"path": "docs/src/tutorials/latexify.md",
"chars": 1410,
"preview": "# `latexify`\n\nThis is a wrapper of some of the other `latexXXX` functions. It tries to infer a suitable output mode for "
},
{
"path": "docs/src/tutorials/latexinline.md",
"chars": 696,
"preview": "# `latexinline`\ntakes a Julia object `x` and returns a ``\\LaTeX`` formatted string.\nIt also surrounds the output in a si"
},
{
"path": "docs/src/tutorials/latextabular.md",
"chars": 874,
"preview": "# `latextabular`\n\n```julia\nusing Latexify\ncopy_to_clipboard(true)\narr = [\"x/y\" :(y^n); 1.0 :(alpha(x))]\nlatextabular(arr"
},
{
"path": "docs/src/tutorials/notebooks.md",
"chars": 1847,
"preview": "# Notebook workflows\n\nWhen working in a notebook (These tips assume Pluto, but will apply at least\nin part to other simi"
},
{
"path": "docs/src/tutorials/parameterizedfunctions.md",
"chars": 3452,
"preview": "# Use with ParameterizedFunctions\n\nIn the [latexalign tutorial](@ref latexalign_tutorial) I mentioned that one can use `"
},
{
"path": "docs/src/tutorials/recipes.md",
"chars": 4075,
"preview": "\n# Recipes\n\nRecipes provides a concise means of extending Latexify.jl to work with types of your own making or of other "
},
{
"path": "docs/src/tutorials/rendering_latex.md",
"chars": 747,
"preview": "# A note on rendering ``\\LaTeX``\nUsing the `print` function on a latexified object prints text which is suitable for cop"
},
{
"path": "ext/DataFramesExt.jl",
"chars": 333,
"preview": "module DataFramesExt\n\nusing Latexify\nisdefined(Base, :get_extension) ? (using DataFrames) : (using ..DataFrames)\n\n@latex"
},
{
"path": "ext/SparseArraysExt.jl",
"chars": 199,
"preview": "module SparseArraysExt\n\nusing Latexify\nisdefined(Base, :get_extension) ? (using SparseArrays) : (using ..SparseArrays)\n\n"
},
{
"path": "ext/SymEngineExt.jl",
"chars": 185,
"preview": "module SymEngineExt\n\nusing Latexify\nisdefined(Base, :get_extension) ? (using SymEngine) : (using ..SymEngine)\n\n@latexrec"
},
{
"path": "ext/TectonicExt.jl",
"chars": 530,
"preview": "module TectonicExt\nimport LaTeXStrings.LaTeXString\nimport Latexify.render, Latexify._compile\nisdefined(Base, :get_extens"
},
{
"path": "paper/paper.bib",
"chars": 1141,
"preview": "@article{julia,\n\tauthor = {Jeff Bezanson and Alan Edelman and Stefan Karpinski and Viral B. Shah},\n\ttitle = {Julia: A Fr"
},
{
"path": "paper/paper.md",
"chars": 3057,
"preview": "---\ntitle: 'Latexify.jl, translating mathematical Julia objects to renderable equations and tables.'\ntags:\n - Julia\n -"
},
{
"path": "src/Latexify.jl",
"chars": 3851,
"preview": "module Latexify\n\nif isdefined(Base, :Experimental) && isdefined(Base.Experimental, Symbol(\"@optlevel\"))\n @eval Base.E"
},
{
"path": "src/default_kwargs.jl",
"chars": 1073,
"preview": "const default_kwargs = Dict{Symbol, Any}()\n\n\"\"\"\n set_default(; kwargs...)\n\nSet default kwarg values for latexify. \n\nT"
},
{
"path": "src/error.jl",
"chars": 2387,
"preview": "abstract type LatexifyException <: Exception end\n\nstruct NoRecipeException <: LatexifyException\n type::Type\nend\nfunct"
},
{
"path": "src/internal_recipes.jl",
"chars": 624,
"preview": "\n@latexrecipe function f(x::UnitRange; expand_ranges=false)\n expand_ranges && return collect(x)\n return :($(x.star"
},
{
"path": "src/latexalign.jl",
"chars": 3080,
"preview": "\n@doc doc\"\"\"\n latexalign()\nGenerate a ``LaTeX`` align environment from an input.\n\n# Examples\n## use with arrays\n\n```j"
},
{
"path": "src/latexarray.jl",
"chars": 3491,
"preview": "\n\"\"\"\n latexarray{T}(arr::AbstractArray{T, 2})\nCreate a LaTeX array environment using [`latexraw`](@ref).\n\n# Examples\n"
},
{
"path": "src/latexbracket.jl",
"chars": 332,
"preview": "latexbracket(args...; kwargs...) = process_latexify(args...; kwargs..., env=:bracket)\n\nfunction _latexbracket(x; kwargs."
},
{
"path": "src/latexequation.jl",
"chars": 427,
"preview": "\nlatexequation(args...; kwargs...) = process_latexify(args...; kwargs..., env=:equation)\n\nfunction _latexequation(eq; st"
},
{
"path": "src/latexify_function.jl",
"chars": 3545,
"preview": "@doc doc\"\"\"\n latexify(args...; kwargs...)\n\nLatexify a string, an expression, an array or other complex types.\n\n```jul"
},
{
"path": "src/latexinline.jl",
"chars": 256,
"preview": "latexinline(args...;kwargs...) = process_latexify(args...;kwargs...,env=:inline)\n\nfunction _latexinline(x; kwargs...)\n "
},
{
"path": "src/latexoperation.jl",
"chars": 13365,
"preview": "\"\"\"\n latexoperation(ex::Expr, prevOp::AbstractArray)\n\nTranslate a simple operation given by `ex` to LaTeX maths synta"
},
{
"path": "src/latexraw.jl",
"chars": 6238,
"preview": "@doc doc\"\"\"\n latexraw(arg)\n\nGenerate LaTeX equations from `arg`.\n\nParses expressions, ParameterizedFunctions, SymEngi"
},
{
"path": "src/latextabular.jl",
"chars": 2211,
"preview": "latextabular(args...; kwargs...) = process_latexify(args...; kwargs..., env=:tabular)\n\nfunction _latextabular(arr::Abstr"
},
{
"path": "src/macros.jl",
"chars": 2676,
"preview": "\"\"\"\n @latexify expression\n\nCreate `LaTeXString` representing `expression`.\nVariables and expressions can be interpola"
},
{
"path": "src/md.jl",
"chars": 1215,
"preview": "\n\nfunction md(args...; env=:auto, kwargs...)\n md_function = infer_md_output(env, args...)\n\n m = md_function(args.."
},
{
"path": "src/mdtable.jl",
"chars": 3500,
"preview": "@doc doc\"\"\"\n mdtable(array; latex=true, head=[], side=[], transpose=false)\n\nLatexify the input and output a markdown-"
},
{
"path": "src/mdtext.jl",
"chars": 237,
"preview": "mdtext(args...; kwargs...) = latexify(args...; kwargs..., env=:mdtext)\n\nfunction _mdtext(s::String; escape_underscores ="
},
{
"path": "src/numberformatters.jl",
"chars": 2677,
"preview": "abstract type AbstractNumberFormatter end\n\n(::AbstractNumberFormatter)(x) = string(x)\n\nconst float_regex = r\"(?'mantissa"
},
{
"path": "src/recipes.jl",
"chars": 6298,
"preview": "# Much of this is copied/adapted from RecipesBase.jl. Cred to everyone who has\n# worked on that package!\n\nconst _debug_r"
},
{
"path": "src/symbol_translations.jl",
"chars": 8896,
"preview": "const functions = Dict{Symbol, String}(\n ## Greek alphabet\n "
},
{
"path": "src/unicode2latex.jl",
"chars": 59140,
"preview": "import OrderedCollections: OrderedDict\nimport Base.Unicode\n\nmathup(c::Char, bold) = Char(\n UInt32(c) + if isuppercase"
},
{
"path": "src/utils.jl",
"chars": 8418,
"preview": "\nadd_brackets(ex::Expr, vars) = postwalk(x -> x in vars ? \"\\\\left[ $(convert_subscript(x)) \\\\right]\" : x, ex)\nadd_bracke"
},
{
"path": "test/cdot_test.jl",
"chars": 2588,
"preview": "using Latexify\nusing Markdown\nusing Test\n\n\n#inline\n@test latexify(:(x * y); env=:inline, mult_symbol=\"\") == raw\"$x y$\"\n\n"
},
{
"path": "test/chemical_arrows_test.jl",
"chars": 2087,
"preview": "using DiffEqBiological\nusing Latexify\nusing Test\n\n@reaction_func hill2(x, v, k) = v*x^2/(k^2 + x^2)\n\nrn = @reaction_netw"
},
{
"path": "test/latexalign_test.jl",
"chars": 696,
"preview": "using Latexify\nusing Test\n\n\n@test latexify(((1.0, 2), (3, 4)); env=:align) == replace(\nraw\"\\begin{align}\n1.0 &= 3 \\\\\n2 &"
},
{
"path": "test/latexarray_test.jl",
"chars": 6232,
"preview": "using Latexify\nusing Test\n\narr = [1 2; 3 4]\n\n@test latexify(:([x])) == replace(\nraw\"$\\left[\n\\begin{array}{c}\nx \\\\\n\\end{a"
},
{
"path": "test/latexbracket_test.jl",
"chars": 417,
"preview": "using Latexify, Test\n\n# Latexify.@generate_test latexify(:(x = [1, 2]), env=:bracket)\n@test latexify(:(x = [1, 2]), env "
},
{
"path": "test/latexequation_test.jl",
"chars": 202,
"preview": "\n@test latexify(\"x/y\"; env=:eq) == \nraw\"\\begin{equation}\n\\frac{x}{y}\n\\end{equation}\n\"\n\n@test latexify(\"x = a^x/b\"; env=:"
},
{
"path": "test/latexify_test.jl",
"chars": 807,
"preview": "using Latexify\nusing LaTeXStrings\nusing Test\n\n\ntest_array = [\"x/y * d\" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ]\n\n \n#### Te"
},
{
"path": "test/latexinline_test.jl",
"chars": 355,
"preview": "using Latexify\nusing LaTeXStrings\n\ntest_array = [\"x/y * d\" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ]\n@test latexinline.(tes"
},
{
"path": "test/latexraw_test.jl",
"chars": 19083,
"preview": "\nusing Latexify\nusing Test\nusing Markdown\n\nstr = \"2*x^2 - y/c_2\"\nex = :(2*x^2 - y/c_2)\n\ndesired_output = \"2 \\\\cdot x^{2}"
},
{
"path": "test/latextabular_test.jl",
"chars": 3210,
"preview": "using DataFrames: DataFrame\nusing Latexify\nusing Test\nd = DataFrame(A = 11:13, B = [:X, :Y, :Z])\n\n@test latexify(d; env="
},
{
"path": "test/macros.jl",
"chars": 1662,
"preview": "l = @latexify dummyfunc(x; y=1, z=3) = x^2/y + z\n@test l == raw\"$\\mathrm{dummyfunc}\\left( x; y = 1, z = 3 \\right) = \\fra"
},
{
"path": "test/manual_test.jl",
"chars": 325,
"preview": "using Revise\nusing Latexify\nusing DifferentialEquations\nusing DataFrames\nusing LaTeXStrings\n\nrevise()\n\nex = :(min(1,2))\n"
},
{
"path": "test/mdtable_test.jl",
"chars": 4350,
"preview": "using Markdown\nusing Latexify\n\narr = [\"x/(y-1)\", 1.0, 3//2, :(x-y), :symb]\n\nM = vcat(reduce(hcat, arr), reduce(hcat, arr"
},
{
"path": "test/numberformatters_test.jl",
"chars": 1624,
"preview": "using Latexify\nusing Test\nimport Latexify: PlainNumberFormatter, PrintfNumberFormatter\n\n@test FancyNumberFormatter() == "
},
{
"path": "test/plugins/DataFrames_test.jl",
"chars": 1275,
"preview": "using DataFrames\nusing LaTeXStrings\n\n\ndf = DataFrame(A = 'x':'z', B = [\"α/β\", 1//2, 8])\n\n@test mdtable(df) ==\nMarkdown.m"
},
{
"path": "test/plugins/SparseArrays_test.jl",
"chars": 195,
"preview": "using SparseArrays\n\nx = sparse([1,2,3],[1,3,2],[0,1,2])\n@test latexraw(x) == replace(raw\"\"\"\\left[\n\\begin{array}{ccc}\n0 &"
},
{
"path": "test/plugins/SymEngine_test.jl",
"chars": 108,
"preview": "using SymEngine\n\n@vars x y\nsymExpr = x + x + x*y*y\n@test latexraw(symExpr) == \"2 \\\\cdot x + x \\\\cdot y^{2}\"\n"
},
{
"path": "test/recipe_test.jl",
"chars": 4380,
"preview": "using Latexify\nusing Test\n\n\nmodule MyModule\nusing Latexify\nstruct MyType\n vec1\n vec2\nend\n\nmy_reverse(x) = x[end:-1"
},
{
"path": "test/runtests.jl",
"chars": 1308,
"preview": "#!/usr/bin/env julia\n\n#Start Test Script\nusing Latexify\nusing LaTeXStrings\nusing Test\n\n# Run tests\n\n@testset \"macro test"
},
{
"path": "test/unicode2latex.jl",
"chars": 3284,
"preview": "@test latexify(\"α\"; convert_unicode=false) == raw\"$α$\"\n\n@test latexify(['α', :β, \"γ/η\"], transpose=true, convert_unicode"
},
{
"path": "test/utils_test.jl",
"chars": 6757,
"preview": "xdoty_tex = L\"x \\cdot y\"\n\n#= This test fails after updating dvisvgm, can this functionality be tested in a less dependan"
}
]
About this extraction
This page contains the full source code of the korsbo/Latexify.jl GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 85 files (256.1 KB), approximately 89.5k tokens. 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.