Skip to content

Instantly share code, notes, and snippets.

@Pangoraw
Created July 7, 2021 17:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pangoraw/1be19ac127a56f3cdfc6b9b6b384a613 to your computer and use it in GitHub Desktop.
Save Pangoraw/1be19ac127a56f3cdfc6b9b6b384a613 to your computer and use it in GitHub Desktop.
Katex hyper fun
### A Pluto.jl notebook ###
# v0.15.0
using Markdown
using InteractiveUtils
# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
quote
local el = $(esc(element))
global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : missing
el
end
end
# ╔═╡ c7fd078d-862f-42a2-a515-221ed411b4fb
using PlutoUI
# ╔═╡ 899f5d16-7018-4297-9939-0a4b5eb30620
using HypertextLiteral
# ╔═╡ 1a6943bf-ede7-4690-80de-e9ec23cc66da
katex_base_code = @htl("""
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.css" integrity="sha384-Um5gpz1odJg5Z4HAmzPtgZKdTBHZdw8S29IecapCSB31ligYPhHQZMIlWLYQGVoc" crossorigin="anonymous">
<style>
.katex .base,
.katex .strut {
/*display: inline-flex !important;*/
pointer-events: none;
}
.SlottedLaTeX {
font-size: .75em;
}
.SlottedLaTeX .slot {
pointer-events: initial;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/katex@0.13.11/dist/katex.min.js" integrity="sha384-YNHdsYkH6gMx9y3mRkmcJ2mFUjTd0qNQQvY9VYZgQd7DcN7env35GzlmFaZ23JGp" crossorigin="anonymous"></script>
""")
# ╔═╡ b0ce1300-df3c-11eb-30ca-e7fee566afae
begin
Base.@kwdef struct SlottedLaTeX
parts::Vector{String}
slots::Vector{Any}
defined_commands::Vector=[]
used_commands::Vector=[]
used_commands_defs::Vector=[]
end
function Base.show(io::IO, m::MIME"text/html", sl::SlottedLaTeX)
defined_command_names = map(c -> c[1], sl.defined_commands)
commands_defs = join(map(comm -> "\\newcommand\\$(comm[1])$(comm[2])\n",
filter(x -> x[1] ∉ defined_command_names, collect(zip(sl.used_commands, sl.used_commands_defs)))
))
parts = map(part -> "$commands_defs $part", sl.parts)
h = @htl("""
$katex_base_code
<span class="SlottedLaTeX-slots" style="display: none;">
$(
map(sl.slots) do s
@htl("<span class='slot'>$(s)</span>")
end
)
</span>
<script>
// https://unicode-table.com/en/#2800
const braille_start = 10240
// https://unicode-table.com/en/#03B1
const greek_start = 945
const placeholder = (i) => String.fromCodePoint(braille_start + i)
const placeholder_index = (s) => s.codePointAt(0) - braille_start
const k = (segments, ...slots) => {
const mock = [...slots.flatMap((_, i) => [segments[i], placeholder(i)]), segments[segments.length-1]].join("")
const el = html`<span class='SlottedLaTeX'></span>`
katex.render(mock, el, {
displayMode: currentScript.closest("p") == null,
})
Array.from(el.querySelectorAll("span")).forEach(span => {
const t = span.innerText
if(t.length === 1) {
const i = placeholder_index(t)
if(0 <= i && i < slots.length) {
span.replaceWith(slots[i])
}
}
})
return el
}
const parts = $(parts)
console.log(parts)
const slots = Array.from(currentScript.previousElementSibling.children)
console.log(slots)
return k(parts, ...slots)
</script>
""")
Base.show(io, m, h)
end
end
# ╔═╡ aab42b5e-3d81-454f-97c7-e39bbb0ba425
begin
latex_name(s) = Symbol("__LATEX_$s")
latex_name(vec::Vector) = latex_name(vec[1])
end
# ╔═╡ 3edb1f40-3cb0-4ef9-a917-047fbdfc7c33
begin
tex(s::String) = tex(Expr(:string, s)) #SlottedLaTeX(parts=[s], slots=[])
function tex(ex::Expr)
@assert ex.head === :string "$(ex.head)"
if ex.args[1] isa String
parts = String[ex.args[1]]
slots = Any[]
else
parts = ["\\hspace{0pt}"]
slots = [ex.args[1]]
end
for x in ex.args[2:end]
if x isa String
all(==(' '), x) ? push!(parts, "\\hspace{0pt}") : push!(parts, x)
else
length(parts) != length(slots) + 1 && push!(parts, "\\hspace{0pt}")
push!(slots, x)
end
end
if length(slots) == length(parts)
push!(parts, "\\hspace{0pt}")
end
used_commands = collect(Iterators.flatten(map(parts) do part
matches = eachmatch(r"\\([a-zA-Z]+)", part)
map(match -> match.captures, matches)
end))
defined_commands = collect(Iterators.flatten(map(parts) do part
matches = eachmatch(r"\\newcommand\\([a-zA-Z]+)(.+)\n", part)
map(match -> match.captures, matches)
end))
defined_exprs = map(defined_commands) do command
Expr(:(=), esc(latex_name(command[1])), command[2])
end
usage_name_symbols = filter(name -> isdefined(@__MODULE__, name[2]), map(x -> (x[1], latex_name(x[1])), used_commands))
symbols = map(x -> x[2], usage_name_symbols)
names = map(x -> x[1], usage_name_symbols)
quote
$(defined_exprs...)
commands = [$(symbols...)]
SlottedLaTeX(
parts = $parts,
slots = [$(esc.(slots)...)],
defined_commands=$defined_commands,
used_commands=$names,
used_commands_defs=commands,
)
end
end
end
# ╔═╡ 4d007aa4-831f-4933-a96d-38e9ed3e8d46
macro tex_str(s)
ex = Meta.parse("\"" * s * "\"")
tex(ex)
end
# ╔═╡ 3bbc1d6d-86c6-4ad8-b8a8-e2154bd11352
macro tex(exp)
:(tex($exp))
end
# ╔═╡ bc8a2c67-f944-4999-87c9-1cc17467cbe8
jjj = tex"""
\\newcommand\\Par[1]{{ \\left( {#1} \\right] }}
\\frac {xxx}{\\Par{N} + $(@bind x Scrubbable(10))}
"""
# ╔═╡ c7581a61-f600-40f1-960f-127e9de2c2e8
jjj
# ╔═╡ acaa17f6-9e56-4ed1-8545-ea2f0653f3b7
tex"""
\\frac 1{\\Par{N}}
"""
# ╔═╡ 4c06d6b3-8802-4e48-b926-0512203e345e
__LATEX_Par
# ╔═╡ 00000000-0000-0000-0000-000000000001
PLUTO_PROJECT_TOML_CONTENTS = """
[compat]
HypertextLiteral = "~0.8.0"
PlutoUI = "~0.7.9"
[deps]
HypertextLiteral = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
"""
# ╔═╡ 00000000-0000-0000-0000-000000000002
PLUTO_MANIFEST_TOML_CONTENTS = """
# This file is machine-generated - editing it directly is not advised
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
[[Dates]]
deps = ["Printf"]
uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
[[HypertextLiteral]]
git-tree-sha1 = "1e3ccdc7a6f7b577623028e0095479f4727d8ec1"
uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2"
version = "0.8.0"
[[InteractiveUtils]]
deps = ["Markdown"]
uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
[[JSON]]
deps = ["Dates", "Mmap", "Parsers", "Unicode"]
git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"
[[Logging]]
uuid = "56ddb016-857b-54e1-b83d-db4d58db5568"
[[Markdown]]
deps = ["Base64"]
uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.0"
[[PlutoUI]]
deps = ["Base64", "Dates", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "Suppressor"]
git-tree-sha1 = "44e225d5837e2a2345e69a1d1e01ac2443ff9fcb"
uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8"
version = "0.7.9"
[[Printf]]
deps = ["Unicode"]
uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
[[Random]]
deps = ["Serialization"]
uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
[[Reexport]]
git-tree-sha1 = "5f6c21241f0f655da3952fd60aa18477cf96c220"
uuid = "189a3867-3050-52da-a836-e630ba90ab69"
version = "1.1.0"
[[Serialization]]
uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
[[Suppressor]]
git-tree-sha1 = "a819d77f31f83e5792a76081eee1ea6342ab8787"
uuid = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
version = "0.2.0"
[[Unicode]]
uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
"""
# ╔═╡ Cell order:
# ╠═1a6943bf-ede7-4690-80de-e9ec23cc66da
# ╠═b0ce1300-df3c-11eb-30ca-e7fee566afae
# ╠═aab42b5e-3d81-454f-97c7-e39bbb0ba425
# ╠═3edb1f40-3cb0-4ef9-a917-047fbdfc7c33
# ╠═4d007aa4-831f-4933-a96d-38e9ed3e8d46
# ╠═3bbc1d6d-86c6-4ad8-b8a8-e2154bd11352
# ╠═bc8a2c67-f944-4999-87c9-1cc17467cbe8
# ╠═c7581a61-f600-40f1-960f-127e9de2c2e8
# ╠═acaa17f6-9e56-4ed1-8545-ea2f0653f3b7
# ╠═4c06d6b3-8802-4e48-b926-0512203e345e
# ╠═c7fd078d-862f-42a2-a515-221ed411b4fb
# ╠═899f5d16-7018-4297-9939-0a4b5eb30620
# ╟─00000000-0000-0000-0000-000000000001
# ╟─00000000-0000-0000-0000-000000000002
@Pangoraw
Copy link
Author

Pangoraw commented Jul 7, 2021

use with pluto#1032

(@v1.6) pkg> activate --temp
  Activating new environment at `/tmp/jl_pesYut/Project.toml`

(jl_pesYut) pkg> add https://github.com/Pangoraw/Pluto.jl#macros
    Updating git-repo `https://github.com/Pangoraw/Pluto.jl`
    Updating registry at `~/.julia/registries/General`

reactive_latex

original source: https://github.com/fonsp/disorganised-mess/blob/main/katex%20fun.jl

@kapple19
Copy link

This is awesome stuff!

May I ask what the justification is for having double backslashes everywhere within a tex_str block? Is it not possible to parse escapes/tex commands with single backslashes?

@Pangoraw
Copy link
Author

This is caused by ex = Meta.parse("\"" * s * "\"") to support interpolation of Julia objects.
Using the following should not require double backslashes:

macro tex_str(s)
	tex(s)
end

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment