diff --git a/src/show.jl b/src/show.jl index f9b0afe..a51b0ef 100644 --- a/src/show.jl +++ b/src/show.jl @@ -3,38 +3,118 @@ function Base.show(io::IO, A::AbstractStarAlgebra) return print(ioc, "*-algebra of ", object(A)) end -__prints_with_minus(::Any) = false -__prints_with_minus(x::Real) = x < 0 __needs_parens(::Any) = false __needs_parens(a::AlgebraElement) = true -function _coeff_elt_print(io, c, elt) - print(io, c, '·') +# `print_coefficient` is inspired from MultivariatePolynomials.jl + +# `Int`, `Float64` don't support MIME"text/latex". +# We could add a check with `showable` if a `Real` subtype supports it and +# the feature is requested. +print_coefficient(io::IO, ::MIME, coeff::Real) = print(io, coeff) +# Scientific notation does not display well in LaTeX so we rewrite it +function print_coefficient(io::IO, ::MIME"text/latex", coeff::AbstractFloat) + s = string(coeff) + if occursin('e', s) + s = replace(s, 'e' => " \\cdot 10^{") * '}' + end + return print(io, s) +end + +trim_LaTeX(_, s::AbstractString) = s + +function trim_LaTeX(::MIME"text/latex", s::AbstractString) + i = firstindex(s) + j = lastindex(s) + while true + if i < j && isspace(s[i]) + i = nextind(s, i) + elseif i < j && isspace(s[j]) + j = prevind(s, j) + elseif i < j && s[i] == '$' && s[j] == '$' + i = nextind(s, i) + j = prevind(s, j) + elseif i < j && ( + (s[i:nextind(s, i)] == "\\(" && s[prevind(s, j):j] == "\\)") || + (s[i:nextind(s, i)] == "\\[" && s[prevind(s, j):j] == "\\]") + ) + i = nextind(s, i, 2) + j = prevind(s, j, 2) + else + return s[i:j] + end + end +end + +# JuMP expressions supports LaTeX output so `showable` will return `true` +# for them. It is important for anonymous variables to display properly as well: +# https://github.com/jump-dev/SumOfSquares.jl/issues/256 +# Since they add `$$` around it, we need to trim it with `trim_LaTeX` +function print_coefficient(io::IO, mime, coeff) + print(io, "(") + print_mime(io, mime, coeff) + print(io, ")") + return +end + +function print_mime(io::IO, mime, x) + if showable(mime, x) + print(io, trim_LaTeX(mime, sprint(show, mime, x))) + else + show(io, x) + end +end + +isnegative(x::Real) = x < 0 +isnegative(x) = false + +_print_dot(io, ::MIME"text/latex") = print(io, " \\cdot ") +_print_dot(io, ::MIME) = print(io, '·') + +function _coeff_elt_print(io, mime, c, elt) + print_coefficient(io, mime, c) + _print_dot(io, mime) __needs_parens(elt) && print(io, '(') - print(io, elt) + print_mime(io, mime, elt) __needs_parens(elt) && print(io, ')') return end -function Base.show(io::IO, a::AlgebraElement) +Base.print(io::IO, a::AlgebraElement) = show(io, MIME"text/print"(), a) +Base.show(io::IO, a::AlgebraElement) = show(io, MIME"text/plain"(), a) + +function Base.show(io::IO, mime::MIME"text/latex", a::AlgebraElement) + print(io, "\$\$ ") + _show(io, mime, a) + return print(io, " \$\$") +end + +# If the MIME is not specified, IJulia thinks that it supports images, ... +# and then use the result of show and tries to interpret it as an svg, ... +# We need the two methods to avoid ambiguity +function Base.show(io::IO, mime::MIME"text/plain", a::AlgebraElement) + return _show(io, mime, a) +end +function Base.show(io::IO, mime::MIME"text/print", a::AlgebraElement) + return _show(io, mime, a) +end + +function _show(io::IO, mime, a::AlgebraElement) A = parent(a) if iszero(a) T = valtype(coeffs(a)) - _coeff_elt_print(io, zero(T), first(basis(A))) + _coeff_elt_print(io, mime, zero(T), first(basis(A))) else _first = true for (idx, value) in nonzero_pairs(coeffs(a)) c, elt = value, basis(A)[idx] if _first - _coeff_elt_print(io, c, elt) + _coeff_elt_print(io, mime, c, elt) _first = false else - if __prints_with_minus(c) - print(io, ' ') - else - print(io, ' ', '+') - end - _coeff_elt_print(io, c, elt) + neg = isnegative(c) + print(io, ' ', neg ? '-' : '+', ' ') + _coeff_elt_print(io, mime, neg ? abs(c) : c, elt) end end end diff --git a/test/constructors.jl b/test/constructors.jl index 9f5025f..51a516c 100644 --- a/test/constructors.jl +++ b/test/constructors.jl @@ -1,3 +1,13 @@ +struct CustomLaTeXPrint + s::String +end + +Base.:-(s::CustomLaTeXPrint) = s +Base.iszero(::CustomLaTeXPrint) = false +function Base.show(io::IO, ::MIME"text/latex", s::CustomLaTeXPrint) + return print(io, s.s) +end + @testset "Algebra and Elements" begin alph = [:a, :b, :c] A★ = FreeWords(alph) @@ -74,12 +84,17 @@ # @test SA.supp_ind(aa) == [b[s]] == SA.supp_ind(dense_aa) @test SA.supp(aa) == [s] - @test sprint(show, a) == "2·(id) +1·b·c" - @test sprint(show, -a) == "-2·(id) -1·b·c" + @test sprint(show, a) == "2·(id) + 1·b·c" + @test sprint(show, -a) == "-2·(id) - 1·b·c" Z = AlgebraElement{Float64}(a) @test Z == a - @test sprint(show, Z) == "2.0·(id) +1.0·b·c" - @test sprint(show, 2one(RG) - RG(p)) == "2·(id) -1·b·c" + @test sprint(show, Z) == "2.0·(id) + 1.0·b·c" + @test sprint(show, 2one(RG) - RG(p)) == "2·(id) - 1·b·c" + @test sprint(show, (2 + im) * one(RG) - (3im) * RG(p)) == "(2 + 1im)·(id) + (0 - 3im)·b·c" + + @test sprint(print, (2 + im) * one(RG) - (3im) * RG(p)) == "(2 + 1im)·(id) + (0 - 3im)·b·c" + @test sprint(show, 1e-9 * one(RG)) == "1.0e-9·(id)" + @test sprint((io, x) -> show(io, "text/latex", x), 1e-9 * one(RG)) == "\$\$ 1.0 \\cdot 10^{-9} \\cdot (id) \$\$" @test LinearAlgebra.norm(a, 1) == 3 @@ -92,4 +107,14 @@ @test deepcopy(a) !== a @test coeffs(deepcopy(a)) !== coeffs(a) @test parent(deepcopy(a)) === parent(a) + + latex = CustomLaTeXPrint(" \$\$ \\[\\(α_β∀ \\) \\]\t \$\$") + @test sprint((io, x) -> show(io, "text/latex", x), + SA.AlgebraElement(SA.SparseCoefficients([p], [latex]), RG)) == + "\$\$ (α_β∀) \\cdot b·c \$\$" + # Test that the check for `\\)` handles unicode well + latex = CustomLaTeXPrint("\\(β∀") + @test sprint((io, x) -> show(io, "text/latex", x), + SA.AlgebraElement(SA.SparseCoefficients([p], [latex]), RG)) == + "\$\$ (\\(β∀) \\cdot b·c \$\$" end