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 "] 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 = "" )=# ================================================ 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 `(, , )`, 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 \\mathrm{\g}{\g\g}") (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 \\cdot 10^{\g\g}") = new(fmt, Format.generate_formatter(fmt), exponent_format) end FancyNumberFormatter(fmt::String, mult_symbol) = FancyNumberFormatter(fmt, SubstitutionString("\\g $(escape_string(mult_symbol)) 10^{\\g\\g}")) 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) # # 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 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_subscripts(str; safescripts=false) # pair str = replace(str, r"{_([^{}]*)}{_([^{}]*)}" => s"{_{\1 \2}}") # collect ends if needed str = replace(str, r"{_{([^{}]*)}}{_([^{}]*)}" => s"{_{\1 \2}}") str = replace(str, r"{_{([^{}]*)}}{{_([^{}]*)}}" => s"{_{\1 \2}}") # if last one was protected by extra {} # complete merge 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 subscripts that did not need to be merged str = replace(str, r"{{_([^{}]*)}}" => s"_{\1}") str = replace(str, r"{_([^{}]*)}" => s"_\1") end return str end ================================================ FILE: src/utils.jl ================================================ add_brackets(ex::Expr, vars) = postwalk(x -> x in vars ? "\\left[ $(convert_subscript(x)) \\right]" : x, ex) add_brackets(arr::AbstractArray, vars) = [add_brackets(element, vars) for element in arr] add_brackets(s::Any, vars) = s default_packages(s) = vcat( ["amssymb", "amsmath", "unicode-math"], occursin("\\ce{", s) ? ["mhchem"] : [], any(x->occursin(prod(x), s), Iterators.product(["\\si", "\\SI", "\\num", "\\qty"], ["{", "range{", "list{", "product{"])) ? ["siunitx"] : [], ) function _writetex(s::LaTeXString; name=tempname(), command="\\Large", documentclass=("standalone", "varwidth=true"), packages=default_packages(s), preamble="" ) doc = """ \\documentclass$(_packagename(documentclass)) """ * prod(map(p -> "\\usepackage$(_packagename(p))\n", packages)) * preamble * """ \\begin{document} { $command $s } \\end{document} """ doc = replace(doc, "\\begin{align}"=>"\\[\n\\begin{aligned}") doc = replace(doc, "\\end{align}"=>"\\end{aligned}\n\\]") doc = replace(doc, "\\require{mhchem}\n"=>"") texfile = name * ".tex" write(texfile, doc) texfile end function _compile(s::LaTeXString, cmd::Cmd, ext::String; debug=false, name=tempname(), open=true, use_tectonic=false, kw... ) use_tectonic && throw(ArgumentError("`use_tectonic` requires the `tectonic_jll` package")) name = abspath(name) mktempdir() do source_dir cd(source_dir) do _writetex(s; name="main", kw...) debug || (cmd = pipeline(cmd, devnull)) try run(cmd) catch err isa(err, ProcessFailedException) || rethrow(err) isfile("$source_dir/main.log") || rethrow(LatexifyRenderError("")) mv("$source_dir/main.log", "$name.log"; force=true) rethrow(LatexifyRenderError("$name.log")) end end mv("$source_dir/main.$ext", "$name.$ext"; force=true) end if open _openfile(name; ext=ext) end return "$name.$ext" end function _openfile(name; ext="pdf") if Sys.iswindows() run(`cmd /c "start $name.$ext"`, wait=false) elseif Sys.islinux() run(`xdg-open $name.$ext`, wait=false) elseif Sys.isapple() run(`open $name.$ext`, wait=false) elseif Sys.isbsd() run(`xdg-open $name.$ext`, wait=false) end return nothing end """ render(::LaTeXString[, ::MIME"mime"]; debug=false, name=tempname(), command="\\Large", callshow=true, open=true) Display a standalone document with the given input. Supported MIME-type strings are "application/pdf" (default), "application/x-dvi", "image/png" and "image/svg". """ render(s::LaTeXString; kwargs...) = render(s, best_displayable(); kwargs...) function best_displayable() priority_list = [ MIME("juliavscode/html"), MIME("application/pdf"), MIME("application/x-dvi"), MIME("image/svg"), MIME("image/png"), ] for mime_type in priority_list displayable(mime_type) && return mime_type end return MIME("application/pdf") end function html_wrap(s::LaTeXString; scale=1.1, kwargs...) import_str = """ """ return import_str * s.s end # render(s::LaTeXString, mime::MIME"juliavscode/html"; kwargs...) = render(stdout, mime; kwargs...) render(s::LaTeXString, mime::MIME"juliavscode/html"; kwargs...) = display(mime, html_wrap(s; kwargs...)) # `display(MIME("application/pdf")` is generally not defined even though # `displayable(MIME("application/pdf")` returns `true`. # # if callshow && displayable(MIME("application/pdf")) # display(MIME("application/pdf"), read("$name.pdf")) # end render(s::LaTeXString, ::MIME"application/pdf"; lualatex_flags=``, kw...) = _compile(s, `lualatex --interaction=nonstopmode $lualatex_flags main.tex`, "pdf"; kw...) # `display(MIME("application/x-dvi")` is generally not defined even though # `displayable(MIME("application/x-dvi")` returns `true`. # # if callshow && displayable(MIME("application/x-dvi")) # display(MIME("application/x-dvi"), read("$name.dvi")) # end render(s::LaTeXString, ::MIME"application/x-dvi"; dvilualatex_flags=``, kw...) = _compile(s, `dvilualatex --interaction=batchmode $dvilualatex_flags main.tex`, "dvi"; kw...) function render(s::LaTeXString, mime::MIME"image/png"; debug=false, convert = :gs, name=tempname(), callshow=true, open=true, dpi=DEFAULT_DPI[], ghostscript_flags=`-sDEVICE=pngalpha -dTextAlphaBits=4 -r$dpi`, dvipng_flags=`-bg Transparent -D $dpi -T tight`, kw... ) ext = "png" mktemp() do aux_name, _ # tex -> dvi -> png is notoriously bad for fonts (not OTF support), see e.g. tex.stackexchange.com/q/537281 # prefer tex -> pdf -> png instead if convert === :gs aux_mime = MIME("application/pdf") ghostscript_command = Ghostscript_jll.gs() cmd = `$ghostscript_command $ghostscript_flags -o $name.$ext $aux_name.pdf` elseif convert === :dvipng aux_mime = MIME("application/x-dvi") deb = debug ? [] : ["-q"] cmd = `dvipng $(deb...) $dvipng_flags $aux_name.dvi -o $name.$ext` else throw(ArgumentError("$convert program not understood")) end render(s, aux_mime; debug=debug, name=aux_name, open=false, kw...) debug || (cmd = pipeline(cmd, devnull)) run(cmd) end if callshow && displayable(mime) display(mime, read("$name.$ext")) elseif open _openfile(name; ext=ext) end return nothing end function render(s::LaTeXString, mime::MIME"image/svg"; debug=false, convert = :dvisvgm, name=tempname(), callshow=true, open=true, dvisvgm_flags=``, pdf2svg_flags=``, kw... ) ext="svg" mktemp() do aux_name, _ aux_mime = MIME("application/pdf") if convert === :dvisvgm verb = debug ? 7 : 0 cmd = `dvisvgm --no-fonts --pdf -v $verb $dvisvgm_flags $aux_name.pdf -o $name.$ext` elseif convert === :pdf2svg cmd = `pdf2svg $pdf2svg_flags $aux_name.pdf $name.$ext` else throw(ArgumentError("$convert program not understood")) end render(s, aux_mime; debug=debug, name=aux_name, open=false, kw...) debug || (cmd = pipeline(cmd, devnull)) run(cmd) end # `displayable(MIME("image/svg"))` returns `true` even in a textual # context (e.g., in the REPL), but `display(MIME("image/svg+xml"), ...)` # is the one normally defined. if callshow && displayable(mime) display(MIME("image/svg+xml"), read("$name.$ext")) elseif open _openfile(name; ext=ext) end return nothing end safereduce(f, args) = length(args) == 1 ? f(args[1]) : reduce(f, args) function expr_to_array(ex) ex.head === :typed_vcat && (ex = Expr(:vcat, ex.args[2:end]...)) ex.head === :typed_hcat && (ex = Expr(:hcat, ex.args[2:end]...)) ex.head === :ref && (ex = Expr(:vect, ex.args[2:end]...)) ## If it is a matrix if Meta.isexpr(ex.args[1], :row) return eval(ex.head)(map(y -> permutedims(y.args), ex.args)...) else if ex.head == :hcat return safereduce(hcat, ex.args) elseif ex.head in (:vcat, :vect) return safereduce(vcat, ex.args) end end end _packagename(x::AbstractString) = "{$x}" _packagename(x::Tuple) = "[$(join(x[2:end], ", "))]{$(first(x))}" ================================================ FILE: test/cdot_test.jl ================================================ using Latexify using Markdown using Test #inline @test latexify(:(x * y); env=:inline, mult_symbol="") == raw"$x y$" @test latexify(:(x * y); env=:inline, mult_symbol="\\cdot") == raw"$x \cdot y$" @test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, mult_symbol="") == raw"$x \left( y + z \right) y \left( z + a \right) \left( z + b \right)$" @test latexify(:(x*(y+z)*y*(z+a)*(z+b)); env=:inline, mult_symbol="\\cdot") == raw"$x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right)$" # raw @test latexify(:(x * y); env=:raw, mult_symbol="") == raw"x y" @test latexify(:(x * y); env=:raw, mult_symbol="\\cdot") == raw"x \cdot y" @test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, mult_symbol="") == raw"x \left( y + z \right) y \left( z + a \right) \left( z + b \right)" @test latexify(:(x * (y + z) * y * (z + a) * (z + b)); env=:raw, mult_symbol="\\cdot") == raw"x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right)" # array @test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, mult_symbol="") == replace( raw"\begin{equation} \left[ \begin{array}{cc} x y & x \left( y + z \right) y \left( z + a \right) \left( z + b \right) \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify( [:(x*y), :(x*(y+z)*y*(z+a)*(z+b))]; env=:equation, transpose=true, mult_symbol="\\cdot") == replace( raw"\begin{equation} \left[ \begin{array}{cc} x \cdot y & x \cdot \left( y + z \right) \cdot y \cdot \left( z + a \right) \cdot \left( z + b \right) \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") # mdtable arr = ["x*(y-1)", 1.0, 3*2, :(x-2y), :symb] @test latexify(arr; env=:mdtable, mult_symbol="") == Markdown.md"| $x \left( y - 1 \right)$ | | ------------------------:| | $1.0$ | | $6$ | | $x - 2 y$ | | $symb$ | " @test latexify(arr; env=:mdtable, mult_symbol="\\cdot") == Markdown.md"| $x \cdot \left( y - 1 \right)$ | | ------------------------------:| | $1.0$ | | $6$ | | $x - 2 \cdot y$ | | $symb$ | " # Deprecation of cdot = ... in favor of mult_symbol = ... # (cdot takes precedence over mult_symbol) output = @test_deprecated latexify(:(x * y); mult_symbol="garbage", cdot=false) @test output == latexify(:(x * y); mult_symbol="") output = @test_deprecated latexify(:(x * y); mult_symbol="garbage", cdot=true) @test output == latexify(:(x * y); mult_symbol="\\cdot") ================================================ FILE: test/chemical_arrows_test.jl ================================================ using DiffEqBiological using Latexify using Test @reaction_func hill2(x, v, k) = v*x^2/(k^2 + x^2) rn = @reaction_network MyRnType 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 v_x k_x p_y d_x d_y r_b r_u @test latexify(rn; env=:chem) == raw"\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} " @test latexify(rn; env=:chem, expand=false) == raw"\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} " @test md(rn; env=:chem) == raw"\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} " @test md(rn; env=:chem, expand=false, mathjax=false, starred=true, double_linebreak=true) == raw"\begin{align*} \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*} " ode = @reaction_network InducedDegradation begin (d_F, d_Ff, d_R), (F, Ff, R) --> 0 # degradations (p_F, Ff), 0 --> (F, R) # productions (r_b * i, r_u), F ↔ Ff # bindin/unbinding end i p_F d_F r_b r_u d_Ff d_R # @Latexify.generate_test md(ode; env=:chem) @test md(ode; env=:chem) == raw"\begin{align} \require{mhchem} \ce{ F &->[d_{F}] \varnothing}\\ \ce{ Ff &->[d_{Ff}] \varnothing}\\ \ce{ R &->[d_{R}] \varnothing}\\ \ce{ \varnothing &->[p_{F}] F}\\ \ce{ \varnothing &->[Ff] R}\\ \ce{ F &<=>[r_{b} \cdot i][r_{u}] Ff} \end{align} " # @test_throws MethodError latexify(rn; env=:arrow, bad_kwarg="should error") ================================================ FILE: test/latexalign_test.jl ================================================ using Latexify using Test @test latexify(((1.0, 2), (3, 4)); env=:align) == replace( raw"\begin{align} 1.0 &= 3 \\ 2 &= 4 \end{align} ", "\r\n"=>"\n") @test latexify(((1.0, 2), (3, 4)); separator = [" &= ", " &∈ "], env = :align) == replace( raw"\begin{align} 1.0 &= 3 \\ 2 &∈ 4 \end{align} ", "\r\n"=>"\n") lhs = [:a, :b] rhs = [1, 2] @test latexify(lhs, rhs; env = :aligned) == replace( raw"\[ \begin{aligned} a &= 1 \\ b &= 2 \end{aligned} \] ", "\r\n"=>"\n") # @test_throws MethodError latexify(rn; bad_kwarg="should error") # Latexify.@generate_test latexify(["a=1"], env=:align) @test latexify(["a=1"], env = :align) == replace( raw"\begin{align} a &= 1 \end{align} ", "\r\n"=>"\n") ================================================ FILE: test/latexarray_test.jl ================================================ using Latexify using Test arr = [1 2; 3 4] @test latexify(:([x])) == replace( raw"$\left[ \begin{array}{c} x \\ \end{array} \right]$", "\r\n"=>"\n") @test latexify(arr) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:inline) @test latexify(arr; env = :inline) == replace( raw"$\left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right]$", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:equation) @test latexify(arr; env = :equation) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:bracket) @test latexify(arr; env = :bracket) == replace( raw"\[ \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right]\] ", "\r\n"=>"\n") # Latexify.@generate_test latexify(arr; env=:raw) @test latexify(arr; env = :raw) == replace( raw"\left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right]", "\r\n"=>"\n") @test latexify(arr; adjustment=:r) == replace( raw"\begin{equation} \left[ \begin{array}{rr} 1 & 2 \\ 3 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(arr; adjustment=[:l, :r]) == replace( raw"\begin{equation} \left[ \begin{array}{lr} 1 & 2 \\ 3 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") using OffsetArrays @test latexify(OffsetArray(arr, -1:0, 3:4)) == latexify(arr) @test latexify(arr; arraystyle = :array) == replace( raw"\begin{equation} \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :square) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :round) == replace( raw"\begin{equation} \left( \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right) \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :curly) == replace( raw"\begin{equation} \left\{ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right\} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :matrix) == replace( raw"\begin{equation} \begin{matrix} 1 & 2 \\ 3 & 4 \\ \end{matrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :pmatrix) == replace( raw"\begin{equation} \begin{pmatrix} 1 & 2 \\ 3 & 4 \\ \end{pmatrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :bmatrix) == replace( raw"\begin{equation} \begin{bmatrix} 1 & 2 \\ 3 & 4 \\ \end{bmatrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :Bmatrix) == replace( raw"\begin{equation} \begin{Bmatrix} 1 & 2 \\ 3 & 4 \\ \end{Bmatrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :vmatrix) == replace( raw"\begin{equation} \begin{vmatrix} 1 & 2 \\ 3 & 4 \\ \end{vmatrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = :Vmatrix) == replace( raw"\begin{equation} \begin{Vmatrix} 1 & 2 \\ 3 & 4 \\ \end{Vmatrix} \end{equation} ", "\r\n"=>"\n") @test latexify(arr; arraystyle = "smatrix") == replace( raw"\begin{equation} \begin{smatrix}{cc} 1 & 2 \\ 3 & 4 \\ \end{smatrix} \end{equation} ", "\r\n"=>"\n") arr = [1,2,:(x/y),4] @test latexify(arr) == replace( raw"\begin{equation} \left[ \begin{array}{c} 1 \\ 2 \\ \frac{x}{y} \\ 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(arr; transpose=true) == replace( raw"\begin{equation} \left[ \begin{array}{cccc} 1 & 2 & \frac{x}{y} & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify((1.0, 2), (3, 4)) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.0 & 3 \\ 2 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(((1.0, 2), (3, 4))) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.0 & 3 \\ 2 & 4 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(:(x = [1 2] * [1, 2] * [1 2; 3 4])) == replace( raw"$x = \left[ \begin{array}{cc} 1 & 2 \\ \end{array} \right] \cdot \left[ \begin{array}{c} 1 \\ 2 \\ \end{array} \right] \cdot \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4 \\ \end{array} \right]$", "\r\n"=>"\n") @test latexify(:(x = $arr)) == replace( raw"$x = \left[ \begin{array}{c} 1 \\ 2 \\ \frac{x}{y} \\ 4 \\ \end{array} \right]$", "\r\n"=>"\n") tensor = rand(3,3,3) @test_throws Latexify.UnrepresentableException("n-dimensional tensors with n≠1,2") latexify(tensor) @test latexify(tensor; sentinel="\\mathrm{NA}") == replace(raw"\begin{equation} \mathrm{NA} \end{equation} ", "\r\n"=>"\n") tensor = fill(42) @test_throws Latexify.UnrepresentableException("n-dimensional tensors with n≠1,2") latexify(tensor) undefarr = Array{Any,2}(undef, 2, 2) @test latexify(undefarr) == replace( raw"\begin{equation} \left[ \begin{array}{cc} \cdot & \cdot \\ \cdot & \cdot \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") undefarr[1,1] = "x" @test latexify(undefarr) == replace( raw"\begin{equation} \left[ \begin{array}{cc} x & \cdot \\ \cdot & \cdot \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(:((1:3) .+ (1:2:5) .+ 3*(1:3))) == raw"$\left( 1 \mathrel{\ldotp\mkern-2.5mu\ldotp} 3 \right) + \left( 1 \underset{2}{\mathrel{\ldotp\mkern-2.5mu\ldotp}} 5 \right) + 3 \cdot \left( 1 \mathrel{\ldotp\mkern-2.5mu\ldotp} 3 \right)$" @test latexify(:($(1:3) .+ $(1:2:5) .+ $(3*(1:3)))) == replace(raw"$1 \mathrel{\ldotp\mkern-2.5mu\ldotp} 3 + \left[ \begin{array}{c} 1 \\ 3 \\ 5 \\ \end{array} \right] + \left[ \begin{array}{c} 3 \\ 6 \\ 9 \\ \end{array} \right]$", "\r\n"=>"\n") @test latexify(:($(1:3) .+ $(1:2:5) .+ $(3*(1:3))), expand_step_ranges=false) == raw"$1 \mathrel{\ldotp\mkern-2.5mu\ldotp} 3 + 1 \underset{2}{\mathrel{\ldotp\mkern-2.5mu\ldotp}} 5 + 3 \underset{3}{\mathrel{\ldotp\mkern-2.5mu\ldotp}} 9$" @test latexify(:($(1:3) .+ $(1:2:5) .+ $(3*(1:3))), expand_ranges=true) == latexify(:($(1:3) .+ $(1:2:5) .+ $(3*(1:3))), expand_ranges=true, expand_step_ranges=false) == replace(raw"$\left[ \begin{array}{c} 1 \\ 2 \\ 3 \\ \end{array} \right] + \left[ \begin{array}{c} 1 \\ 3 \\ 5 \\ \end{array} \right] + \left[ \begin{array}{c} 3 \\ 6 \\ 9 \\ \end{array} \right]$", "\r\n"=>"\n") ================================================ FILE: test/latexbracket_test.jl ================================================ using Latexify, Test # Latexify.@generate_test latexify(:(x = [1, 2]), env=:bracket) @test latexify(:(x = [1, 2]), env = :bracket) == replace( raw"\[ x = \left[ \begin{array}{c} 1 \\ 2 \\ \end{array} \right]\] ", "\r\n"=>"\n") # Latexify.@generate_test latexify([1, 2], env=:bracket) @test latexify([1, 2], env = :bracket) == replace( raw"\[ \left[ \begin{array}{c} 1 \\ 2 \\ \end{array} \right]\] ", "\r\n"=>"\n") ================================================ FILE: test/latexequation_test.jl ================================================ @test latexify("x/y"; env=:eq) == raw"\begin{equation} \frac{x}{y} \end{equation} " @test latexify("x = a^x/b"; env=:eq, starred=true) == raw"\begin{equation*} x = \frac{a^{x}}{b} \end{equation*} " ================================================ FILE: test/latexify_test.jl ================================================ using Latexify using LaTeXStrings using Test test_array = ["x/y * d" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ] #### Test the setting of default keyword arguments. @test latexify("x * y") == raw"$x \cdot y$" set_default(mult_symbol = "") @test latexify("x * y") == raw"$x y$" @test get_default() == Dict{Symbol,Any}(:mult_symbol => "") set_default(mult_symbol = "\\cdot", transpose = true) @test get_default() == Dict{Symbol,Any}(:mult_symbol => "\\cdot",:transpose => true) @test get_default(:mult_symbol) == "\\cdot" @test get_default(:mult_symbol, :transpose) == ("\\cdot", true) @test get_default([:mult_symbol, :transpose]) == ["\\cdot", true] reset_default() @test get_default() == Dict{Symbol,Any}() @test latexify("x * y") == raw"$x \cdot y$" @test latexify("Plots.jl") isa LaTeXString ================================================ FILE: test/latexinline_test.jl ================================================ using Latexify using LaTeXStrings test_array = ["x/y * d" :x ; :( (t_sub_sub - x)^(2*p) ) 3//4 ] @test latexinline.(test_array) == [ L"$\frac{x}{y} \cdot d$" L"$x$" ; L"$\left( t_{sub\_sub} - x \right)^{2 \cdot p}$" L"$\frac{3}{4}$" ] # @test_throws ErrorException latexify("x/y"; bad_kwarg="should error") ================================================ FILE: test/latexraw_test.jl ================================================ using Latexify using Test using Markdown str = "2*x^2 - y/c_2" ex = :(2*x^2 - y/c_2) desired_output = "2 \\cdot x^{2} - \\frac{y}{c_{2}}" @test latexify(:(a = [x / y; 3; 4])) == replace( raw"$a = \left[ \begin{array}{c} \frac{x}{y} \\ 3 \\ 4 \\ \end{array} \right]$", "\r\n"=>"\n") @test latexify(:(a = [x / y 2 3 4])) == replace( raw"$a = \left[ \begin{array}{cccc} \frac{x}{y} & 2 & 3 & 4 \\ \end{array} \right]$", "\r\n"=>"\n") @test latexify(:(a = [x / y 2; 3 4])) == replace( raw"$a = \left[ \begin{array}{cc} \frac{x}{y} & 2 \\ 3 & 4 \\ \end{array} \right]$", "\r\n"=>"\n") @test latexraw(str) == latexraw(ex) @test latexraw(ex) == desired_output @test latexify(:(u[1, 2]); index = :bracket) == raw"$u\left[1, 2\right]$" @test latexify(:(u[1, 2]); index = :subscript) == raw"$u_{1,2}$" @test latexify(:(u[1][1]); index = :bracket) == raw"$u\left[1\right]\left[1\right]$" @test latexify(:(u[1][1]); index = :subscript) == raw"$\left( u_{1} \right)_{1}$" @test latexify(:(u_x[1][1]); index=:subscript, snakecase=true) == raw"$\left( u\_x_{1} \right)_{1}$" @test latexify(:([1 2 3][2]); index=:bracket) == replace(raw"""$\left[ \begin{array}{ccc} 1 & 2 & 3 \\ \end{array} \right]\left[2\right]$""", "\r\n"=>"\n") @test latexify(:([1 2 3][2]); index=:subscript) == replace(raw"""$\left[ \begin{array}{ccc} 1 & 2 & 3 \\ \end{array} \right]_{2}$""", "\r\n"=>"\n") array_test = [ex, str] @test all(latexraw.(array_test) .== desired_output) @test latexraw(:(@__dot__(x / y))) == raw"\frac{x}{y}" @test latexraw(:(@. x / y)) == raw"\frac{x}{y}" @test latexraw(:(eps())) == raw"\mathrm{eps}()" @test latexraw(:y_c_a) == raw"y_{c\_a}" @test latexraw(1.0) == "1.0" @test latexraw(1.00) == "1.0" @test latexraw(1) == "1" @test latexraw(:(log10(x))) == raw"\log_{10}\left( x \right)" @test latexraw(:(log(x))) == raw"\log\left( x \right)" @test latexraw(:(slog(x))) == raw"\log\left( x \right)" @test latexraw(:(sin(x))) == raw"\sin\left( x \right)" @test latexraw(:(sin(x)^2)) == raw"\sin^{2}\left( x \right)" @test latexraw(:(asin(x))) == raw"\arcsin\left( x \right)" @test latexraw(:(asinh(x))) == raw"\mathrm{arcsinh}\left( x \right)" @test latexraw(:(sinc(x))) == raw"\mathrm{sinc}\left( x \right)" @test latexraw(:(acos(x))) == raw"\arccos\left( x \right)" @test latexraw(:(acosh(x))) == raw"\mathrm{arccosh}\left( x \right)" @test latexraw(:(cosc(x))) == raw"\mathrm{cosc}\left( x \right)" @test latexraw(:(atan(x))) == raw"\arctan\left( x \right)" @test latexraw(:(atan2(x))) == raw"\arctan\left( x \right)" @test latexraw(:(atanh(x))) == raw"\mathrm{arctanh}\left( x \right)" @test latexraw(:(acot(x))) == raw"\mathrm{arccot}\left( x \right)" @test latexraw(:(acoth(x))) == raw"\mathrm{arccoth}\left( x \right)" @test latexraw(:(asec(x))) == raw"\mathrm{arcsec}\left( x \right)" @test latexraw(:(sech(x))) == raw"\mathrm{sech}\left( x \right)" @test latexraw(:(asech(x))) == raw"\mathrm{arcsech}\left( x \right)" @test latexraw(:(acsc(x))) == raw"\mathrm{arccsc}\left( x \right)" @test latexraw(:(csch(x))) == raw"\mathrm{csch}\left( x \right)" @test latexraw(:(acsch(x))) == raw"\mathrm{arccsch}\left( x \right)" @test latexraw(:(x ± y)) == raw"x \pm y" @test latexraw(:(f(x))) == raw"f\left( x \right)" @test latexraw(:(f_a(x))) == raw"f_{a}\left( x \right)" @test latexraw(:(fun_a(x))) == raw"\mathrm{fun}_{a}\left( x \right)" @test latexraw(:(fun_a_b(x))) == raw"\mathrm{fun}_{a\_b}\left( x \right)" @test latexraw(:(fun_ab_c(x))) == raw"\mathrm{fun}_{ab\_c}\left( x \right)" @test latexraw("x = 4*y") == raw"x = 4 \cdot y" @test latexraw(:(sqrt(x))) == raw"\sqrt{x}" @test latexraw(:(ssqrt(x))) == raw"\sqrt{x}" @test latexraw(:(√(x))) == raw"\sqrt{x}" @test latexraw(:(√(π + 1))) == raw"\sqrt{\pi + 1}" @test latexraw(:(cbrt(x))) == raw"\sqrt[3]{x}" @test latexraw(:(scbrt(x))) == raw"\sqrt[3]{x}" @test latexraw(:(∛(x))) == raw"\sqrt[3]{x}" @test latexraw(:(∛(π + 1))) == raw"\sqrt[3]{\pi + 1}" @test latexraw(:(fourthroot(x))) == raw"\sqrt[4]{x}" @test latexraw(:(∜(x))) == raw"\sqrt[4]{x}" @test latexraw(:(∜(π + 1))) == raw"\sqrt[4]{\pi + 1}" @test latexraw(complex(1,-1)) == raw"1-1\mathit{i}" @test latexraw(im) == raw"\mathit{i}" @test latexraw(-im) == raw"-\mathit{i}" @test latexraw(2im) == raw"2\mathit{i}" @test latexraw(1//2) == raw"\frac{1}{2}" @test latexraw(missing) == raw"\textrm{NA}" @test latexraw("x[2]") == raw"x\left[2\right]" @test latexraw("x[2, 3]") == raw"x\left[2, 3\right]" @test latexraw("α") == raw"\alpha" @test latexraw("α + 1") == raw"\alpha + 1" @test latexraw("α₁") == raw"\alpha_1" @test latexraw("γ³") == raw"\gamma^3" @test_broken latexraw("β₃_hello") == raw"\beta{_3}_{hello}" @test latexraw("β₃₄") == raw"\beta_{3 4}" @test latexraw("β₃₄¹⁴") == raw"\beta_{3 4}^{1 4}" @test latexraw("β₃¹⁴") == raw"\beta_3^{1 4}" @test latexraw("β¹⁴₃") == raw"\beta^{1 4}_3" @test latexraw("β¹⁴") == raw"\beta^{1 4}" @test latexraw("β⁴") == raw"\beta^4" @test latexraw("Aᵢⱼᵝᵞ") == raw"A_{i j}^{\beta \gamma}" @test latexraw("Aᵪₒ¹²") == raw"A_{\chi o}^{1 2}" @test latexraw(Inf) == raw"\infty" @test latexraw(:Inf) == raw"\infty" @test latexraw("Inf") == raw"\infty" @test latexraw(-Inf) == raw"-\infty" @test latexraw(:($(3+4im)*a)) == raw"\left( 3+4\mathit{i} \right) \cdot a" @test latexraw(:(a*$(3+4im))) == raw"a \cdot \left( 3+4\mathit{i} \right)" @test latexraw(:(abs(x))) == raw"\left|x\right|" @test latexraw(:(abs2(x))) == raw"\left|x\right|^{2}" @test latexraw(:(norm(x))) == raw"\left\|x\right\|" @test latexraw(:(norm(x, 1.5))) == raw"\left\|x\right\|_{1.5}" @test latexraw(:(ceil(x))) == raw"\left\lceil x\right\rceil " @test latexraw(:(ceil(Int64, x))) == raw"\left\lceil x\right\rceil " @test latexraw(:(floor(x))) == raw"\left\lfloor x\right\rfloor " @test latexraw(:(floor(Int64, x))) == raw"\left\lfloor x\right\rfloor " @test latexraw(:(round(x))) == raw"\left\lfloor x\right\rceil " @test latexraw(:(round(Int64, x))) == raw"\left\lfloor x\right\rceil " @test latexraw(:(1*(1 .+ 1))) == raw"1 \cdot \left( 1 + 1 \right)" @test latexraw(:(1*(1 .- 1))) == raw"1 \cdot \left( 1 - 1 \right)" @test latexraw(:(1-(1 .+ 1))) == raw"1 - \left( 1 + 1 \right)" @test latexraw(:(1-(1 .- 1))) == raw"1 - \left( 1 - 1 \right)" @test latexraw(:(-1*1)) == raw"-1 \cdot 1" @test latexraw(:(-x*y)) == raw" - x \cdot y" @test latexraw(:(sum(x_n))) == raw"\sum x_{n}" @test latexraw(:(sum(x_n for n in _))) == raw"\sum_{n} x_{n}" @test latexraw(:(sum(x_n for n in :))) == raw"\sum_{n} x_{n}" @test latexraw(:(sum(x_n for n in N))) == raw"\sum_{n \in N} x_{n}" @test latexraw(:(sum(x_n for n in n_0:N))) == raw"\sum_{n = n_{0}}^{N} x_{n}" @test latexraw(:(prod(x_n))) == raw"\prod x_{n}" @test latexraw(:(prod(x_n for n in _))) == raw"\prod_{n} x_{n}" @test latexraw(:(prod(x_n for n in :))) == raw"\prod_{n} x_{n}" @test latexraw(:(prod(x_n for n in N))) == raw"\prod_{n \in N} x_{n}" @test latexraw(:(prod(x_n for n in n_0:N))) == raw"\prod_{n = n_{0}}^{N} x_{n}" @test latexraw(:(binomial(13, 27))) == raw"\binom{13}{27}" @test latexraw("Aᵢᵏ"; safescripts=true) == raw"A{_i}{^k}" @test latexraw("Aᵢⱼᵏˡ"; safescripts=true) == raw"A{_{i j}}{^{k l}}" @test latexraw("hello_world"; snakecase=true) == raw"hello\_world" @test latexify(:((-1) ^ 2)) == replace( raw"$\left( -1 \right)^{2}$", "\r\n"=>"\n") @test latexify(:($(1 + 2im) ^ 2)) == replace( raw"$\left( 1+2\mathit{i} \right)^{2}$", "\r\n"=>"\n") @test latexify(:($(3 // 2) ^ 2)) == replace( raw"$\left( \frac{3}{2} \right)^{2}$", "\r\n"=>"\n") ### Test broadcasting @test latexraw(:(fun.((a, b)))) == raw"\mathrm{fun}\left( a, b \right)" ### Test field/property extraction @test latexraw(:(Foo.foo)) == raw"Foo.foo" @test latexraw(:(Foo.fun(a, b))) == raw"\mathrm{Foo.fun}\left( a, b \right)" ### Test combined broadcasting and field extraction @test latexraw(:(Foo.fun.((a, b)))) == raw"\mathrm{Foo.fun}\left( a, b \right)" ### Test for correct signs in nested sums/differences. @test latexraw("-(-1)") == raw" + 1" @test latexraw("+(-1)") == raw"-1" @test latexraw("-(+1)") == raw" - 1" @test latexraw("-(1+1)") == raw" - \left( 1 + 1 \right)" @test latexraw("1-(-2)") == raw"1 + 2" @test latexraw("1 + (-(2))") == raw"1 - 2" @test latexraw("1 + (-2 -3 -4)") == raw"1 -2 - 3 - 4" @test latexraw("1 - 2 - (- 3 - 4)") == raw"1 - 2 - \left( - 3 - 4 \right)" @test latexraw("1 - 2 - (- 3 -(2) + 4)") == raw"1 - 2 - \left( - 3 - 2 + 4 \right)" @test latexraw("1 - 2 - (- 3 -(2 - 8) + 4)") == raw"1 - 2 - \left( - 3 - \left( 2 - 8 \right) + 4 \right)" @test latexraw(:(-$(3+5im))) == raw" - \left( 3+5\mathit{i} \right)" @test latexraw(:($(3+4im)-$(3+5im))) == raw"3+4\mathit{i} - \left( 3+5\mathit{i} \right)" # @test_throws ErrorException latexify("x/y"; env=:raw, bad_kwarg="should error") @test latexraw(:(a .< b .<= c < d <= e > f <= g .<= h .< i == j .== k != l .!= m)) == raw"a < b \leq c < d \leq e > f \leq g \leq h < i = j = k \neq l \neq m" @test latexraw(:(3 * (a .< b .<= c < d <= e > f <= g .<= h .< i == j .== k != l .!= m))) == raw"3 \cdot \left( a < b \leq c < d \leq e > f \leq g \leq h < i = j = k \neq l \neq m \right)" @test latexraw(:(2+3 : b)) == raw"2 + 3 \mathrel{\ldotp\mkern-2.5mu\ldotp} b" @test latexraw(:(a:3*4)) == raw"a \mathrel{\ldotp\mkern-2.5mu\ldotp} 3 \cdot 4" #### Test the imaginary_unit keyword option @test latexraw(5im; imaginary_unit="\\textit{i}") == raw"5\textit{i}" #### Test the fmt keyword option @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:align, fmt="%.2e") == replace( raw"\begin{align} 3.29e+07 &= 1.23e+00 &= P_{1} \\ \frac{x}{y} &= 1.00e+10 &= 1.29e+03 \end{align} ", "\r\n"=>"\n") @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:equation, fmt="%.2e") == replace( raw"\begin{equation} \left[ \begin{array}{ccc} 3.29e+07 & 1.23e+00 & P_{1} \\ \frac{x}{y} & 1.00e+10 & 1.29e+03 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:table, fmt="%.2e") == replace( raw"\begin{tabular}{ccc} $3.29e+07$ & $1.23e+00$ & $P_{1}$\\ $\frac{x}{y}$ & $1.00e+10$ & $1.29e+03$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify([32894823 1.232212 :P_1; :(x / y) 1.0e10 1289.1]; env=:mdtable, fmt="%.2e") == Markdown.md"| $3.29e+07$ | $1.23e+00$ | $P_{1}$ | | -------------:| ----------:| ----------:| | $\frac{x}{y}$ | $1.00e+10$ | $1.29e+03$ | " @test latexify(1234.2234; env=:inline, fmt="%.2e") == raw"$1.23e+03$" @test latexify(1234.2234; env=:raw, fmt="%.2e") == raw"1.23e+03" test_functions = [:sinh, :alpha, :Theta, :cosc, :acoth, :acot, :asech, :lambda, :asinh, :sinc, :eta, :kappa, :nu, :asin, :epsilon, :sigma, :upsilon, :phi, :tanh, :iota, :Psi, :acosh, :log, :zeta, :mu, :csc, :xi, :tau, :beta, :Lambda, :Xi, :Phi, :acsc, :atan, :sech, :atanh, :gamma, :Delta, :rho, :sec, :log10, :delta, :pi, :cot, :log2, :cos, :Omega, :psi, :atan2, :Gamma, :cosh, :acos, :Pi, :Upsilon, :omega, :coth, :chi, :tan, :csch, :acsch, :theta, :asec, :Sigma, :sin] @test latexify(["3*$(func)(x)^2/4 -1" for func = test_functions]) == replace( raw"\begin{equation} \left[ \begin{array}{c} \frac{3 \cdot \sinh^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \alpha\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Theta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{cosc}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccoth}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccot}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsech}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \lambda\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsinh}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{sinc}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \eta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \kappa\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \nu\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \arcsin^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \epsilon\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \sigma\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \upsilon\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \phi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \tanh^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \iota\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Psi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arccosh}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \log\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \zeta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \mu\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \csc^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \xi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \tau\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \beta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Lambda\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Xi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Phi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arccsc}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \arctan^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{sech}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arctanh}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \Gamma\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Delta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \rho\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \sec^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \log_{10}\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \delta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \pi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cot^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \log_{2}\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cos^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \Omega\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \psi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \arctan^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \Gamma\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \cosh^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \arccos^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \Pi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \Upsilon\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \left( \omega\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \coth^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \chi\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \tan^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{csch}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \mathrm{arccsch}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \theta\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \mathrm{arcsec}^{2}\left( x \right)}{4} - 1 \\ \frac{3 \cdot \left( \Sigma\left( x \right) \right)^{2}}{4} - 1 \\ \frac{3 \cdot \sin^{2}\left( x \right)}{4} - 1 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") ## Test Expr containing function (note function vs symbol) @test latexraw(Expr(:call, sin, 3)) == latexraw(Expr(:call, :sin, 3)) == raw"\sin\left( 3 \right)" @test latexraw(cos) == latexraw(:cos) == raw"\cos" ## Test logical operators @test latexraw(:(x && y)) == "x \\wedge y" @test latexraw(:(x || y)) == "x \\vee y" @test latexraw(:(x || !y)) == "x \\vee \\neg y" ## Test anonymous function @test latexraw(:(x -> x^2)) == "x \\mapsto x^{2}" @test latexraw(:(f(p) = x -> p*x)) == "f\\left( p \\right) = x \\mapsto p \\cdot x" ## Test {cases} enviroment @test latexraw(:(R(p,e,d) = e ? 0 : log(p) - d)) == replace( raw"R\left( p, e, d \right) = \begin{cases} 0 & \text{if } e\\ \log\left( p \right) - d & \text{otherwise} \end{cases}", "\r\n"=>"\n") @test latexraw(:(R(p,e,d,t) = if (t && e); 0 elseif (t && !e); d else log(p) end)) == replace( raw"R\left( p, e, d, t \right) = \begin{cases} 0 & \text{if } t \wedge e\\ d & \text{if } t \wedge \neg e\\ \log\left( p \right) & \text{otherwise} \end{cases}", "\r\n"=>"\n") @test latexraw(:(function reward(p,e,d,t) if t && e return 0 elseif t && !e return -1d elseif 2t && e return -2d elseif 3t && e return -3d else return log(p) end end)) == replace( raw"\mathrm{reward}\left( p, e, d, t \right) = \begin{cases} 0 & \text{if } t \wedge e\\ -1 \cdot d & \text{if } t \wedge \neg e\\ -2 \cdot d & \text{if } 2 \cdot t \wedge e\\ -3 \cdot d & \text{if } 3 \cdot t \wedge e\\ \log\left( p \right) & \text{otherwise} \end{cases}", "\r\n"=>"\n") # Test infix comparison operators @testset "Infix" begin @test latexraw(:(x ⊊ y)) == raw"x \subsetneq y" @test latexraw(:(x .⊆ y)) == raw"x \subseteq y" @test latexraw(:(x .∌ y)) == raw"x \not\ni y" @test latexraw(:(x .>= y)) == raw"x \geq y" @test latexraw(:(x .== y)) == raw"x = y" @test latexraw(:(issubset(x, y))) == raw"x \subseteq y" @test latexraw(:(x .∉ y)) == raw"x \notin y" @test latexraw(:(x < y)) == raw"x < y" @test latexraw(:(x !== y)) == raw"x \not\equiv y" @test latexraw(:(x ≈ y)) == raw"x \approx y" @test latexraw(:(x .⊅ y)) == raw"x \not\supset y" @test latexraw(:(x ≥ y)) == raw"x \geq y" @test latexraw(:(x >= y)) == raw"x \geq y" @test latexraw(:(x .∈ y)) == raw"x \in y" @test latexraw(:(x > y)) == raw"x > y" @test latexraw(:(x <= y)) == raw"x \leq y" @test latexraw(:(x .⊊ y)) == raw"x \subsetneq y" @test latexraw(:(x .<= y)) == raw"x \leq y" @test latexraw(:(x .≤ y)) == raw"x \leq y" @test latexraw(:(x ⊃ y)) == raw"x \supset y" @test latexraw(:(x == y)) == raw"x = y" @test latexraw(:(x in y)) == raw"x \in y" @test latexraw(:(x ≠ y)) == raw"x \neq y" @test latexraw(:(x ⊆ y)) == raw"x \subseteq y" @test latexraw(:(x .≥ y)) == raw"x \geq y" @test latexraw(:(x .< y)) == raw"x < y" @test latexraw(:(x != y)) == raw"x \neq y" @test latexraw(:(x .!== y)) == raw"x \not\equiv y" @test latexraw(:(x === y)) == raw"x \equiv y" @test latexraw(:(x ⊅ y)) == raw"x \not\supset y" @test latexraw(:(x .∋ y)) == raw"x \ni y" @test latexraw(:(x ∉ y)) == raw"x \notin y" @test latexraw(:(x .≠ y)) == raw"x \neq y" @test latexraw(:(x .!= y)) == raw"x \neq y" @test latexraw(:(x ∈ y)) == raw"x \in y" @test latexraw(:(x .⊃ y)) == raw"x \supset y" @test latexraw(:(x .≈ y)) == raw"x \approx y" @test latexraw(:(x ∋ y)) == raw"x \ni y" @test latexraw(:(x .> y)) == raw"x > y" @test latexraw(:(x .=== y)) == raw"x \equiv y" @test latexraw(:(x ≤ y)) == raw"x \leq y" @test latexraw(:(x ∌ y)) == raw"x \not\ni y" @test latexraw(:(x < 3 + 1)) == raw"x < 3 + 1" @test latexraw(:((x < 3) + 1)) == raw"\left( x < 3 \right) + 1" #@test latexraw(:(∀(x, y))) == raw"x \forall y" @test latexraw(:(a => b)) == raw"a \Rightarrow b" end ================================================ FILE: test/latextabular_test.jl ================================================ using DataFrames: DataFrame using Latexify using Test d = DataFrame(A = 11:13, B = [:X, :Y, :Z]) @test latexify(d; env=:table, side=1:3, latex=false) == replace( raw"\begin{tabular}{ccc} & A & B\\ 1 & 11 & X\\ 2 & 12 & Y\\ 3 & 13 & Z\\ \end{tabular} ", "\r\n"=>"\n") # Latexify.@generate_test latexify([1.]; env=:table) @test latexify([1.0]; env = :table) == replace( raw"\begin{tabular}{c} $1.0$\\ \end{tabular} ", "\r\n"=>"\n") arr = ["x/(y-1)", 1.0, 3//2, :(x-y), :symb] M = vcat(reduce(hcat, arr), reduce(hcat, arr)) head = ["col$i" for i in 1:size(M, 2)] side = ["row$i" for i in 1:size(M, 1)] @test latexify(M; env=:table, head=1:2, adjustment=:l, latex=false, transpose=true) == replace( raw"\begin{tabular}{ll} 1 & 2\\ x/(y-1) & x/(y-1)\\ 1.0 & 1.0\\ 3//2 & 3//2\\ x - y & x - y\\ symb & symb\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:2, adjustment=[:c, :r], latex=false, transpose=true) == replace( raw"\begin{tabular}{cr} 1 & 2\\ x/(y-1) & x/(y-1)\\ 1.0 & 1.0\\ 3//2 & 3//2\\ x - y & x - y\\ symb & symb\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:2, adjustment=:l, transpose=true) == replace( raw"\begin{tabular}{ll} $1$ & $2$\\ $\frac{x}{y - 1}$ & $\frac{x}{y - 1}$\\ $1.0$ & $1.0$\\ $\frac{3}{2}$ & $\frac{3}{2}$\\ $x - y$ & $x - y$\\ $symb$ & $symb$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:5) == replace( raw"\begin{tabular}{ccccc} $1$ & $2$ & $3$ & $4$ & $5$\\ $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, side=[1, 2]) == replace( raw"\begin{tabular}{cccccc} $1$ & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ $2$ & $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, booktabs=true) == replace( raw"\begin{tabular}{ccccc} \toprule $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ \bottomrule \end{tabular} ", "\r\n"=>"\n") @test latexify(M; env=:table, head=1:5, booktabs=true) == replace( raw"\begin{tabular}{ccccc} \toprule $1$ & $2$ & $3$ & $4$ & $5$\\ \midrule $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ $\frac{x}{y - 1}$ & $1.0$ & $\frac{3}{2}$ & $x - y$ & $symb$\\ \bottomrule \end{tabular} ", "\r\n"=>"\n") D = Dict(:a=>"x/(k+x)", :b=>"x - y") @test latexify(D; env=:tabular) == replace( raw"\begin{tabular}{cc} $a$ & $\frac{x}{k + x}$\\ $b$ & $x - y$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(D; env=:tabular, head=["Keys", "Values"]) == replace( raw"\begin{tabular}{cc} $Keys$ & $Values$\\ $a$ & $\frac{x}{k + x}$\\ $b$ & $x - y$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(d; env=:tabular, latex=false, fmt=SiunitxNumberFormatter()) == replace( raw"\begin{tabular}{cc} A & B\\ \num{11} & X\\ \num{12} & Y\\ \num{13} & Z\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(d; env=:tabular, latex=false, fmt=SiunitxNumberFormatter(), adjustment=:S) == replace( raw"\begin{tabular}{SS} A & B\\ 11 & X\\ 12 & Y\\ 13 & Z\\ \end{tabular} ", "\r\n"=>"\n") ================================================ FILE: test/macros.jl ================================================ l = @latexify dummyfunc(x; y=1, z=3) = x^2/y + z @test l == raw"$\mathrm{dummyfunc}\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" @test_throws UndefVarError dummyfunc(1.) l2 = @latexrun dummyfunc2(x; y=1, z=3) = x^2/y + z @test l2 == raw"$\mathrm{dummyfunc2}\left( x; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" @test dummyfunc2(1.) == 4 l3 = @latexify dummyfunc2(x::Number; y=1, z=3) = x^2/y + z @test l3 == raw"$\mathrm{dummyfunc2}\left( x::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" l4 = @latexify dummyfunc2(::Number; y=1, z=3) = x^2/y + z @test l4 == raw"$\mathrm{dummyfunc2}\left( ::Number; y = 1, z = 3 \right) = \frac{x^{2}}{y} + z$" l5 = @latexify x = abs2(-3) @test l5 == raw"$x = \left|-3\right|^{2}$" l6 = @latexify x = $(abs2(-3)) @test l6 == raw"$x = 9$" l7 = @latexrun x = abs2(-3) @test l7 == raw"$x = \left|-3\right|^{2}$" @test x == 9 l8 = @latexrun x = $(abs2(-3)) @test l8 == raw"$x = 9$" @test x == 9 l9 = @latexdefine x = abs2(-2) @test l9 == raw"$x = \left|-2\right|^{2} = 4$" @test x == 4 l10 = @latexdefine x = $(abs2(-2)) @test l10 == raw"$x = 4 = 4$" @test x == 4 l11 = @latexify x = 1 env=:raw @test l11 == raw"x = 1" l12 = @latexrun x = 1 env=:raw @test l12 == raw"x = 1" l13 = @latexdefine y = x env=:raw @test l13 == raw"y = x = 1" env = :raw l14 = @latexdefine y env @test l14 == raw"y = 1" l15 = @latexdefine y = x post=float @test l15 == raw"$y = x = 1.0$" @test y isa Integer post = x->round(x; sigdigits=3) l16 = @latexdefine y = π post @test l16 == raw"$y = \pi = 3.14$" @test y == π @test latexify(:(@hi(x / y))) == replace( raw"$\mathrm{@hi}\left( \frac{x}{y} \right)$", "\r\n"=>"\n") ================================================ FILE: test/manual_test.jl ================================================ using Revise using Latexify using DifferentialEquations using DataFrames using LaTeXStrings revise() ex = :(min(1,2)) latexify(ex) "x/y" |> latexify l = latexify("x/y") join(1, ",") f = @ode_def feedback begin dx = y/c_1 - x dy = x^c_2 - y end c_1 c_2 latexalign(f.syms, f.funcs) latexify(f.syms, f.funcs) ================================================ FILE: test/mdtable_test.jl ================================================ using Markdown using Latexify arr = ["x/(y-1)", 1.0, 3//2, :(x-y), :symb] M = vcat(reduce(hcat, arr), reduce(hcat, arr)) head = ["col$i" for i in 1:size(M, 2)] side = ["row$i" for i in 1:size(M, 1)] @test mdtable(arr) == Markdown.md" | $\frac{x}{y - 1}$ | | -----------------:| | $1.0$ | | $\frac{3}{2}$ | | $x - y$ | | $symb$ | " @test mdtable(arr; head = ["head"]) == Markdown.md" | head | | -----------------:| | $\frac{x}{y - 1}$ | | $1.0$ | | $\frac{3}{2}$ | | $x - y$ | | $symb$ | " @test mdtable(arr; head = ["head"], side=1:length(arr)) == Markdown.md" | ∘ | head | | ---:| -----------------:| | 1 | $\frac{x}{y - 1}$ | | 2 | $1.0$ | | 3 | $\frac{3}{2}$ | | 4 | $x - y$ | | 5 | $symb$ | " @test mdtable(arr; head = ["head"], side=1:length(arr)+1) == Markdown.md" | 1 | head | | ---:| -----------------:| | 2 | $\frac{x}{y - 1}$ | | 3 | $1.0$ | | 4 | $\frac{3}{2}$ | | 5 | $x - y$ | | 6 | $symb$ | " @test mdtable(arr, arr) == Markdown.md" | $\frac{x}{y - 1}$ | $\frac{x}{y - 1}$ | | -----------------:| -----------------:| | $1.0$ | $1.0$ | | $\frac{3}{2}$ | $\frac{3}{2}$ | | $x - y$ | $x - y$ | | $symb$ | $symb$ | " @test mdtable(arr, arr; head = ["col1", "col2"]) == Markdown.md" | col1 | col2 | | -----------------:| -----------------:| | $\frac{x}{y - 1}$ | $\frac{x}{y - 1}$ | | $1.0$ | $1.0$ | | $\frac{3}{2}$ | $\frac{3}{2}$ | | $x - y$ | $x - y$ | | $symb$ | $symb$ | " @test mdtable(M) == Markdown.md" | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | | -----------------:| -----:| -------------:| -------:| ------:| | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | " @test mdtable(M; adjustment=:c) == Markdown.md" | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | | :----------------:| :----:| :------------:| :------:| :-----:| | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | " @test mdtable(M; adjustment=[:l :c :r :l nothing]) == Markdown.md" | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | | :-----------------| :----:| -------------:| :-------| ------:| | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | " @test mdtable(M, head=head) == Markdown.md" | col1 | col2 | col3 | col4 | col5 | | -----------------:| -----:| -------------:| -------:| ------:| | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | " @test mdtable(M, head=head, side=side) == Markdown.md" | ∘ | col1 | col2 | col3 | col4 | col5 | | ----:| -----------------:| -----:| -------------:| -------:| ------:| | row1 | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | | row2 | $\frac{x}{y - 1}$ | $1.0$ | $\frac{3}{2}$ | $x - y$ | $symb$ | " @test mdtable(M, head=side, side=head, transpose=true) == Markdown.md" | ∘ | row1 | row2 | | ----:| -----------------:| -----------------:| | col1 | $\frac{x}{y - 1}$ | $\frac{x}{y - 1}$ | | col2 | $1.0$ | $1.0$ | | col3 | $\frac{3}{2}$ | $\frac{3}{2}$ | | col4 | $x - y$ | $x - y$ | | col5 | $symb$ | $symb$ | " m = ["one_two_tree"; "four_five_six"; "seven_eight"] @test latexify(m; env=:mdtable, latex=false, escape_underscores=true) == Markdown.md" | one\_two\_tree | | -------------:| | four\_five\_six | | seven_eight | " using DataFrames d = DataFrame(A = 11:13, B = [:X, :Y, :Z]) @test latexify(d; env=:mdtable, side=1:3) == Markdown.md" | ∘ | A | B | | ---:| ----:| ---:| | 1 | $11$ | $X$ | | 2 | $12$ | $Y$ | | 3 | $13$ | $Z$ | " @test latexify(d; env=:mdtable) == Markdown.md" | A | B | | ----:| ---:| | $11$ | $X$ | | $12$ | $Y$ | | $13$ | $Z$ | " @test latexify(((1.0, 2), (3, 4)); env=:mdtable) == Markdown.md" | $1.0$ | $3$ | | -----:| ---:| | $2$ | $4$ | " # @test_throws MethodError mdtable(M; bad_kwarg="should error") ================================================ FILE: test/numberformatters_test.jl ================================================ using Latexify using Test import Latexify: PlainNumberFormatter, PrintfNumberFormatter @test FancyNumberFormatter() == FancyNumberFormatter(4) == FancyNumberFormatter("%.4g", "\\cdot") == FancyNumberFormatter("%.4g", s"\g \\cdot 10^{\g\g}") x = -23.4728979e7 @test PlainNumberFormatter()(x) == "-2.34728979e8" @test PrintfNumberFormatter("%.4g")(x) == "-2.347e+08" @test StyledNumberFormatter()(x) == "-2.347 \\mathrm{e}{8}" @test FancyNumberFormatter()(x) == "-2.347 \\cdot 10^{8}" @test FancyNumberFormatter("%.5E", s"\g,\g,\g,\g,\g,\g,\g,\g,\g")(x) == "-2.34729,-2,-,2,34729,E,+08,,8" @test StyledNumberFormatter(4) == StyledNumberFormatter() xne = -23.4728979e-7 @test FancyNumberFormatter("%.5E", s"\g,\g,\g,\g,\g,\g,\g,\g,\g")(xne) == "-2.34729,-2,-,2,34729,E,-06,-,6" y = 0xf43 @test StyledNumberFormatter()(y) == FancyNumberFormatter()(y) == "\\mathtt{0x0f43}" @test SiunitxNumberFormatter()(x) == "\\num{-2.34728979e8}" @test SiunitxNumberFormatter(version=2)(x) == "\\num{-2.34728979e8}" @test SiunitxNumberFormatter(format_options="something")(x) == "\\num[something]{-2.34728979e8}" @test SiunitxNumberFormatter(format_options="[something]")(x) == "\\num[something]{-2.34728979e8}" @test SiunitxNumberFormatter()([1,2,4]) == "\\numlist{1;2;4}" @test SiunitxNumberFormatter()(1:4) == "\\numrange{1}{4}" @test !SiunitxNumberFormatter().simple @test SiunitxNumberFormatter(simple=true).simple ================================================ FILE: test/plugins/DataFrames_test.jl ================================================ using DataFrames using LaTeXStrings df = DataFrame(A = 'x':'z', B = ["α/β", 1//2, 8]) @test mdtable(df) == Markdown.md"| A | B | | ---:| ----------------------:| | $x$ | $\frac{\alpha}{\beta}$ | | $y$ | $\frac{1}{2}$ | | $z$ | $8$ | " @test latexify(df, latex=true) == Markdown.md"| A | B | | ---:| ----------------------:| | $x$ | $\frac{\alpha}{\beta}$ | | $y$ | $\frac{1}{2}$ | | $z$ | $8$ | " @test_broken latexify(df; env=:array) == replace( L"\begin{equation} \left[ \begin{array}{cc} A & B \\ x & \frac{\alpha}{\beta} \\ y & \frac{1}{2} \\ z & 8 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(df; env=:table, latex=true) == replace( raw"\begin{tabular}{cc} $A$ & $B$\\ $x$ & $\frac{\alpha}{\beta}$\\ $y$ & $\frac{1}{2}$\\ $z$ & $8$\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(df; env=:table, latex=false) == replace( raw"\begin{tabular}{cc} A & B\\ x & α/β\\ y & 1//2\\ z & 8\\ \end{tabular} ", "\r\n"=>"\n") @test latexify(df; head=["x", "y"]) == Markdown.md"| x | y | | ---:| ----------------------:| | $x$ | $\frac{\alpha}{\beta}$ | | $y$ | $\frac{1}{2}$ | | $z$ | $8$ | " ================================================ FILE: test/plugins/SparseArrays_test.jl ================================================ using SparseArrays x = sparse([1,2,3],[1,3,2],[0,1,2]) @test latexraw(x) == replace(raw"""\left[ \begin{array}{ccc} 0 & 0 & 0 \\ 0 & 0 & 1 \\ 0 & 2 & 0 \\ \end{array} \right]""", "\r\n"=>"\n") ================================================ FILE: test/plugins/SymEngine_test.jl ================================================ using SymEngine @vars x y symExpr = x + x + x*y*y @test latexraw(symExpr) == "2 \\cdot x + x \\cdot y^{2}" ================================================ FILE: test/recipe_test.jl ================================================ using Latexify using Test module MyModule using Latexify struct MyType vec1 vec2 end my_reverse(x) = x[end:-1:1] @latexrecipe function f(t::MyType; reverse=false) env --> :align fmt := "%.2f" return reverse ? (t.vec2, t.vec1) : (t.vec1, t.vec2) end struct MyVec vec::AbstractVector end @latexrecipe function f(v::MyVec; reverse=false) env --> :equation fmt := "%.2f" return reverse ? v.vec[end, -1, 1] : v.vec end struct MyTup tup::Tuple end @latexrecipe function f(v::MyTup; reverse=false) env --> :equation fmt := "%.2f" return reverse ? my_reverse(v.tup) : v.tup end struct MyDoubleTup tup1::Tuple tup2::Tuple end @latexrecipe function f(t::MyDoubleTup) return t.tup1, t.tup2 end struct MyFloat x::Float64 end @latexrecipe function f(m::MyFloat) fmt --> "%.6e" return m.x*m.x end @latexrecipe function f(vec::Vector{T}) where T <: MyFloat fmt --> "%.4e" return [myfloat.x for myfloat in vec] end struct MyDoubleVec{T} vec1::AbstractVector{T} vec2::AbstractVector{T} end @latexrecipe function f(vec::MyDoubleVec{T}) where T <: MyFloat return [myfloat.x for myfloat in vec.vec1], [myfloat.x for myfloat in vec.vec2] end struct MySum x y end @latexrecipe function f(s::MySum) operation --> :+ return :($(s.x) + $(s.y)) end end using .MyModule t = MyModule.MyType([:A, :B, 3.], [1., 2, 3]) t2 = MyModule.MyType([:X, :Y, :(x/y)], Number[1.23434534, 232423.42345, 12//33]) m = MyModule.MyFloat(3) @test latexify(m) == raw"$9.000000e+00$" @test latexify(:(2+$m)) == raw"$2 + 9.000000e+00$" vec = [MyModule.MyFloat(x) for x in 1:4] @test latexify(vec; transpose=true) == replace( raw"\begin{equation} \left[ \begin{array}{cccc} 1.0000e+00 & 2.0000e+00 & 3.0000e+00 & 4.0000e+00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") double_vec = MyModule.MyDoubleVec([MyModule.MyFloat(x) for x in 1:4], [MyModule.MyFloat(x) for x in 8:11]) @test latexify(double_vec) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.0 & 8.0 \\ 2.0 & 9.0 \\ 3.0 & 10.0 \\ 4.0 & 11.0 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(t2, fmt="%.8f") == replace( raw"\begin{align} X &= 1.23 \\ Y &= 232423.42 \\ \frac{x}{y} &= \frac{4.00}{11.00} \end{align} ", "\r\n"=>"\n") @test latexify(t) == replace( raw"\begin{align} A &= 1.00 \\ B &= 2.00 \\ 3.00 &= 3.00 \end{align} ", "\r\n"=>"\n") @test latexify(t; reverse=true) == replace( raw"\begin{align} 1.00 &= A \\ 2.00 &= B \\ 3.00 &= 3.00 \end{align} ", "\r\n"=>"\n") @test latexify(t; env=:equation) == replace( raw"\begin{equation} \left[ \begin{array}{cc} A & 1.00 \\ B & 2.00 \\ 3.00 & 3.00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(t; env=:equation, reverse=true) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.00 & A \\ 2.00 & B \\ 3.00 & 3.00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") vec = MyModule.MyVec([1., 2.]) @test latexify(vec, transpose=true) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.00 & 2.00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") tup = MyModule.MyTup((1., 2.)) @test latexify(tup, transpose=true) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.00 & 2.00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify(tup, reverse=true, transpose=true) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 2.00 & 1.00 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") tup2 = MyModule.MyDoubleTup((1., 3), (2., 4.)) @test latexify(tup2) == replace( raw"\begin{equation} \left[ \begin{array}{cc} 1.0 & 2.0 \\ 3 & 4.0 \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") sum1 = MyModule.MySum(3, 4) @test latexify(:(2 + $(sum1)^2)) == raw"$2 + \left( 3 + 4 \right)^{2}$" @test latexify(:(2 - $(sum1))) == raw"$2 - \left( 3 + 4 \right)$" struct NothingThing end @test_throws Latexify.NoRecipeException(NothingThing) latexify(NothingThing()) @test latexify(NothingThing(); sentinel="this") == raw"$this$" @latexrecipe function f(::NothingThing; keyword=nothing) if isnothing(keyword) return L"a" elseif keyword == :nothing return L"b" end end @test latexify(NothingThing()) == raw"$a$" @test latexify(NothingThing(); keyword=nothing) == raw"$a$" @test latexify(NothingThing(); keyword=:nothing) == raw"$b$" ================================================ FILE: test/runtests.jl ================================================ #!/usr/bin/env julia #Start Test Script using Latexify using LaTeXStrings using Test # Run tests @testset "macro test" begin include("macros.jl") end @testset "recipe test" begin include("recipe_test.jl") end @testset "latexify tests" begin include("latexify_test.jl") end @testset "latexraw tests" begin include("latexraw_test.jl") end @testset "latexalign tests" begin include("latexalign_test.jl") end @testset "latexarray tests" begin include("latexarray_test.jl") end @testset "latexequation tests" begin include("latexequation_test.jl") end @testset "latexbracket tests" begin include("latexbracket_test.jl") end @testset "latexinline tests" begin include("latexinline_test.jl") end @testset "latextabular tests" begin include("latextabular_test.jl") end @testset "mdtable tests" begin include("mdtable_test.jl") end @testset "DataFrames Plugin" begin include("plugins/DataFrames_test.jl") end @testset "SymEngine Plugin" begin include("plugins/SymEngine_test.jl") end @testset "SparseArrays Plugin" begin include("plugins/SparseArrays_test.jl") end @testset "unicode2latex" begin include("unicode2latex.jl") end @testset "cdot test" begin include("cdot_test.jl") end @testset "numberformatters" begin include("numberformatters_test.jl") end @testset "utils test" begin include("utils_test.jl") end ================================================ FILE: test/unicode2latex.jl ================================================ @test latexify("α"; convert_unicode=false) == raw"$α$" @test latexify(['α', :β, "γ/η"], transpose=true, convert_unicode=false) == replace( raw"\begin{equation} \left[ \begin{array}{ccc} α & β & \frac{γ}{η} \\ \end{array} \right] \end{equation} ", "\r\n"=>"\n") @test latexify("αaβ") == raw"${\alpha}a\beta$" @test latexify("αaβ").s == raw"${\alpha}a\beta$" @test latexify("ÀéÜ"; parse=false).s == raw"$\textnormal{\`{A}}\textnormal{\'{e}}\textnormal{\\\"{U}}$" @test latexify("w̋Ṽî"; parse=false).s == raw"$\textnormal{\H{w}}\tilde{V}\hat{i}$" @test latexify("çĘf̄"; parse=false).s == raw"$\textnormal{\c{c}}\textnormal{\k{E}}\bar{f}$" @test latexify("ṞȯX̣"; parse=false).s == raw"$\textnormal{\b{R}}\dot{o}\textnormal{\d{X}}$" @test latexify("ẙĞž"; parse=false).s == raw"$\textnormal{\r{y}}\textnormal{\u{G}}\textnormal{\v{z}}$" @test latexify("τ̇"; parse=false).s == raw"$\dot{\tau}$" @test latexify("τ̈"; parse=false).s == raw"$\ddot{\tau}$" @test latexify("τ⃛"; parse=false).s == raw"$\dddot{\tau}$" @test latexify("τ⃜"; parse=false).s == raw"$\ddddot{\tau}$" s = 'y' * Char(0x30a) * 'x' * Char(0x302) * 'a' * Char(0x331) @test latexify(s; parse=false).s == raw"$\textnormal{\r{y}}\hat{x}\textnormal{\b{a}}$" s = 'Y' * Char(0x30a) * 'X' * Char(0x302) * 'A' * Char(0x331) @test latexify(s; parse=false).s == raw"$\textnormal{\r{Y}}\hat{X}\textnormal{\b{A}}$" s = 'i' * Char(0x308) * 'z' * Char(0x304) * 'e' * Char(0x306) @test latexify(s; parse=false).s == raw"$\textnormal{\\\"{i}}\bar{z}\textnormal{\u{e}}$" s = 'I' * Char(0x308) * 'Z' * Char(0x304) * 'E' * Char(0x306) @test latexify(s; parse=false).s == raw"$\textnormal{\\\"{I}}\bar{Z}\textnormal{\u{E}}$" @test latexify("ẋ + ω̂") == raw"$\dot{x} + \hat{\omega}$" @test latexify(:(iħ * (∂Ψ(𝐫, t) / ∂t) = -ħ^2 / 2m * ΔΨ(𝐫, t) + V * Ψ(𝐫, t))).s == raw"$i\hslash \cdot \frac{\partial\Psi\left( \mathbf{r}, t \right)}{{\partial}t} = " * raw"\frac{ - \hslash^{2}}{2 \cdot m} \cdot \Delta\Psi\left( \mathbf{r}, t \right) + V \cdot \Psi\left( \mathbf{r}, t \right)$" if Sys.islinux() mktempdir() do dn name = tempname() str = map( chunk -> string("\\[", join(chunk, " "), "\\]\n"), Iterators.partition(values(Latexify.unicodedict), 40) ) |> prod Latexify._writetex( LaTeXString(str), name=name, documentclass="article", preamble="\\usepackage[margin=2cm]{geometry}" ) # should compile, even if some glyphs aren't found in the default font face @test pipeline(`latexmk -output-directory=$dn -quiet -pdflatex=lualatex -pdf $name.tex`, stdout=devnull) |> run |> success end # not checked for correctness, only for rendering without errors eq = latexify(:(sin(α) / (β + 1))) name = tempname() render(eq, MIME("application/pdf"); name=name, open=false) @test filesize("$name.pdf") > 100 name = tempname() render(eq, MIME("application/x-dvi"); name=name, open=false) @test filesize("$name.dvi") > 100 name = tempname() render(eq, MIME("image/png"); name=name, open=false) @test filesize("$name.png") > 100 name = tempname() render(eq, MIME("image/svg"); name=name, open=false) @test filesize("$name.svg") > 100 end ================================================ FILE: test/utils_test.jl ================================================ xdoty_tex = L"x \cdot y" #= This test fails after updating dvisvgm, can this functionality be tested in a less dependant way? if Sys.islinux() render(xdoty_tex, MIME("image/svg"); name=name, open=false) svg = open("$(name).svg") do f read(f, String) end @test svg == raw""" """ end # =# filename = Latexify._writetex(xdoty_tex) tex = read(filename, String) @test tex == replace(raw""" \documentclass[varwidth=true]{standalone} \usepackage{amssymb} \usepackage{amsmath} \usepackage{unicode-math} \begin{document} { \Large $x \cdot y$ } \end{document} """, "\r\n"=>"\n") filename = Latexify._writetex(L"\ce{ 2 P_1 &<=>[k_{+}][k_{-}] D_{1}}") tex = read(filename, String) @test tex == replace(raw""" \documentclass[varwidth=true]{standalone} \usepackage{amssymb} \usepackage{amsmath} \usepackage{unicode-math} \usepackage{mhchem} \begin{document} { \Large $\ce{ 2 P_1 &<=>[k_{+}][k_{-}] D_{1}}$ } \end{document} """, "\r\n"=>"\n") filename = Latexify._writetex(L"\qty{135}{nm}"; documentclass=("article", "a4paper"), packages=("siunitx",)) tex = read(filename, String) @test tex == replace(raw""" \documentclass[a4paper]{article} \usepackage{siunitx} \begin{document} { \Large $\qty{135}{nm}$ } \end{document} """, "\r\n"=>"\n") @test Latexify._packagename("hi") == "{hi}" @test Latexify._packagename(("hi", "x=5")) == "[x=5]{hi}" @test Latexify._packagename(("hi", "x=5", "y")) == "[x=5, y]{hi}" @test occursin("MathJax", Latexify.html_wrap(latexify(:(sin(α))))) @test Latexify.best_displayable() isa MIME #@test_throws Latexify.LatexifyRenderError render(L"x^2^3") # Does not run on Windows and Mac CI logfile = tempname() open(logfile, "w") do io println(io, raw""" This is LuaHBTeX, Version 1.18.0 (TeX Live 2024/Arch Linux) (format=lualatex 2024.4.3) 7 AUG 2024 14:19 restricted system commands enabled. ** Skipping many files ** LaTeX Font Info: Trying to load font information for U+msb on input line 5. (/usr/share/texmf-dist/tex/latex/amsfonts/umsb.fd File: umsb.fd 2013/01/14 v3.01 AMS symbols B ) ! Double superscript. l.8 $x^2^ 3$ I treat `x^1^2' essentially like `x^1{}^2'. ** More lines skipped ** """) end e = Latexify.LatexifyRenderError(logfile) @test sprint(showerror, e) == """ an error occured while rendering LaTeX: \n\tDouble superscript. \tl.8 \$x^2^ Check the log file at $logfile for more information""" @test_throws ArgumentError render(L"x^2"; use_tectonic=true) using tectonic_jll pdf_file = render(L"x^2"; open=false) # should now not throw @test isfile(pdf_file) @test_throws Latexify.LatexifyRenderError render(L"x^2^3"; open=false) # Check that Ghostscript render works. If it did, no error png_return = render(L"x^2", MIME("image/png"); open=false) @test isnothing(png_return) # unlike PDF output, returns nothing