Full Code of korsbo/Latexify.jl for AI

master 8b633ca3bbad cached
85 files
256.1 KB
89.5k tokens
1 requests
Download .txt
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://img.shields.io/badge/docs-stable-blue.svg)](https://korsbo.github.io/Latexify.jl/stable)
[![](https://img.shields.io/badge/docs-latest-blue.svg)](https://korsbo.github.io/Latexify.jl/latest)
[![codecov](https://codecov.io/gh/korsbo/Latexify.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/korsbo/Latexify.jl)
[![Coverage Status](https://coveralls.io/repos/github/korsbo/Latexify.jl/badge.svg)](https://coveralls.io/github/korsbo/Latexify.jl)
[![Downloads](https://img.shields.io/badge/dynamic/json?url=http%3A%2F%2Fjuliapkgstats.com%2Fapi%2Fv1%2Fmonthly_downloads%2FLatexify&query=total_requests&suffix=%2Fmonth&label=Downloads)](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:

![fraction](/assets/demo_fraction.png)


#### 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)
```
![matrix](/assets/demo_matrix.png)

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))))
```

![ket](/assets/demo_ket.png)

### 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:

![positiveFeedback](/assets/ode_positive_feedback.png)


[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)
```
![positiveFeedback](/assets/demo_rn.png)

Or you can output the arrow notation directly to latex:

```julia
latexify(rn; env=:arrow)
```
![positiveFeedback](/assets/demo_rn_arrow.png)

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
Download .txt
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://img.shields.io/badge/docs-stable-blue.svg)](https://korsbo.github.io/Latexify.jl/stable)\n[![](https://img.s"
  },
  {
    "path": "assets/Project.toml",
    "chars": 225,
    "preview": "[deps]\nCatalyst = \"479239e8-5488-4da2-87a7-35f2df7eef83\"\nLaTeXStrings = \"b964fa9f-0449-5b57-a5c2-d3ea65f4040f\"\nLatexify "
  },
  {
    "path": "assets/assets.jl",
    "chars": 1592,
    "preview": "# Generate the assets (pngs for README)\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.

Copied to clipboard!