diff --git a/CHANGELOG.md b/CHANGELOG.md index 246f1880b5b..7655137abf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ * ![Enhancement][badge-enhancement] Docstrings from `@docs`-blocks are now included in the rendered docs even if some part(s) of the block failed. ([#928][github-928], [#935][github-935]) +* ![Enhancement][badge-enhancement] The Markdown and LaTeX output writers can now handle multimedia + output, such as images, from `@example` blocks. ([#938][github-938]) + ## Version `v0.21.1` * ![Bugfix][badge-bugfix] `@repl` blocks now work correctly together with quoted @@ -200,6 +203,7 @@ [github-928]: https://github.com/JuliaDocs/Documenter.jl/pull/928 [github-929]: https://github.com/JuliaDocs/Documenter.jl/pull/929 [github-935]: https://github.com/JuliaDocs/Documenter.jl/pull/935 +[github-938]: https://github.com/JuliaDocs/Documenter.jl/pull/938 [documenterlatex]: https://github.com/JuliaDocs/DocumenterLaTeX.jl [documentermarkdown]: https://github.com/JuliaDocs/DocumenterMarkdown.jl diff --git a/src/Documents.jl b/src/Documents.jl index 2fd37f719b3..7e80b829b9b 100644 --- a/src/Documents.jl +++ b/src/Documents.jl @@ -138,6 +138,10 @@ struct RawNode text::String end +struct MultiOutput + content::Vector +end + # Navigation # ---------------------- diff --git a/src/Expanders.jl b/src/Expanders.jl index 55ed3c53a29..809a6d0fe66 100644 --- a/src/Expanders.jl +++ b/src/Expanders.jl @@ -562,28 +562,14 @@ function Selectors.runner(::Type{ExampleBlocks}, x, page, doc) content = [] input = droplines(x.code) - # Special-case support for displaying SVG and PNG graphics. TODO: make this more general. - output = if showable(MIME"text/html"(), result) - Documents.RawHTML(Base.invokelatest(stringmime, MIME"text/html"(), result)) - elseif showable(MIME"image/svg+xml"(), result) - Documents.RawHTML(Base.invokelatest(stringmime, MIME"image/svg+xml"(), result)) - elseif showable(MIME"image/png"(), result) - Documents.RawHTML(string("")) - elseif showable(MIME"image/webp"(), result) - Documents.RawHTML(string("")) - elseif showable(MIME"image/gif"(), result) - Documents.RawHTML(string("")) - elseif showable(MIME"image/jpeg"(), result) - Documents.RawHTML(string("")) - else - Markdown.Code(Documenter.DocTests.result_to_string(buffer, result)) - end + # Generate different in different formats and let each writer select + output = Base.invokelatest(Utilities.display_dict, result) # Only add content when there's actually something to add. isempty(input) || push!(content, Markdown.Code("julia", input)) - isempty(output.code) || push!(content, output) + isempty(output) || push!(content, output) # ... and finally map the original code block to the newly generated ones. - page.mapping[x] = Markdown.MD(content) + page.mapping[x] = Documents.MultiOutput(content) end # @repl diff --git a/src/Utilities/Utilities.jl b/src/Utilities/Utilities.jl index 27d52209c5e..08c7f22df1e 100644 --- a/src/Utilities/Utilities.jl +++ b/src/Utilities/Utilities.jl @@ -635,6 +635,20 @@ function mdparse(s::AbstractString; mode=:single) end end +# Capturing output in different representations similar to IJulia.jl +import Base64: stringmime +function display_dict(x) + out = Dict{MIME,Any}() + # Always generate text/plain + out[MIME"text/plain"()] = stringmime(MIME"text/plain"(), x) + for m in [MIME"text/html"(), MIME"image/svg+xml"(), MIME"image/png"(), + MIME"image/webp"(), MIME"image/gif"(), MIME"image/jpeg"(), + MIME"text/latex"()] + showable(m, x) && (out[m] = stringmime(m, x)) + end + return out +end + include("DOM.jl") include("MDFlatten.jl") include("TextDiff.jl") diff --git a/src/Writers/HTMLWriter.jl b/src/Writers/HTMLWriter.jl index 0c456e7a22b..01a82e40c5a 100644 --- a/src/Writers/HTMLWriter.jl +++ b/src/Writers/HTMLWriter.jl @@ -1105,6 +1105,31 @@ end mdconvert(html::Documents.RawHTML, parent; kwargs...) = Tag(Symbol("#RAW#"))(html.code) +# Select the "best" representation for HTML output. +mdconvert(mo::Documents.MultiOutput, parent; kwargs...) = + Base.invokelatest(mdconvert, mo.content, parent; kwargs...) +function mdconvert(d::Dict{MIME,Any}, parent; kwargs...) + if haskey(d, MIME"text/html"()) + out = Documents.RawHTML(d[MIME"text/html"()]) + elseif haskey(d, MIME"image/svg+xml"()) + out = Documents.RawHTML(d[MIME"image/svg+xml"()]) + elseif haskey(d, MIME"image/png"()) + out = Documents.RawHTML(string("")) + elseif haskey(d, MIME"image/webp"()) + out = Documents.RawHTML(string("")) + elseif haskey(d, MIME"image/gif"()) + out = Documents.RawHTML(string("")) + elseif haskey(d, MIME"image/jpeg"()) + out = Documents.RawHTML(string("")) + elseif haskey(d, MIME"text/latex"()) + out = Utilities.mdparse(d[MIME"text/latex"()]; mode = :single) + elseif haskey(d, MIME"text/plain"()) + out = Markdown.Code(d[MIME"text/plain"()]) + else + error("this should never happen.") + end + return mdconvert(out, parent; kwargs...) +end # fixlinks! # ------------------------------------------------------------------------------ diff --git a/src/Writers/LaTeXWriter.jl b/src/Writers/LaTeXWriter.jl index 4831762cf63..131ac3b1010 100644 --- a/src/Writers/LaTeXWriter.jl +++ b/src/Writers/LaTeXWriter.jl @@ -316,6 +316,39 @@ function latex(io::IO, node::Documents.EvalNode, page, doc) node.result === nothing ? nothing : latex(io, node.result, page, doc) end +# Select the "best" representation for LaTeX output. +using Base64: base64decode +function latex(io::IO, mo::Documents.MultiOutput) + foreach(x->Base.invokelatest(latex, io, x), mo.content) +end +function latex(io::IO, d::Dict{MIME,Any}) + filename = String(rand('a':'z', 7)) + if haskey(d, MIME"image/png"()) + write("$(filename).png", base64decode(d[MIME"image/png"()])) + _println(io, """ + \\begin{figure}[H] + \\centering + \\includegraphics{$(filename)} + \\end{figure} + """) + elseif haskey(d, MIME"image/jpeg"()) + write("$(filename).jpeg", base64decode(d[MIME"image/jpeg"()])) + _println(io, """ + \\begin{figure}[H] + \\centering + \\includegraphics{$(filename)} + \\end{figure} + """) + elseif haskey(d, MIME"text/latex"()) + latex(io, Utilities.mdparse(d[MIME"text/latex"()]; mode = :single)) + elseif haskey(d, MIME"text/plain"()) + latex(io, Markdown.Code(d[MIME"text/plain"()])) + else + error("this should never happen.") + end + return nothing +end + ## Basic Nodes. AKA: any other content that hasn't been handled yet. diff --git a/src/Writers/MarkdownWriter.jl b/src/Writers/MarkdownWriter.jl index 3c8d570677f..e2b8dec92c1 100644 --- a/src/Writers/MarkdownWriter.jl +++ b/src/Writers/MarkdownWriter.jl @@ -138,6 +138,49 @@ function render(io::IO, mime::MIME"text/plain", node::Documents.EvalNode, page, node.result === nothing ? nothing : render(io, mime, node.result, page, doc) end +# Select the "best" representation for Markdown output. +using Base64: base64decode +function render(io::IO, mime::MIME"text/plain", d::Documents.MultiOutput, page, doc) + foreach(x -> Base.invokelatest(render, io, mime, x, page, doc), d.content) +end +function render(io::IO, mime::MIME"text/plain", d::Dict{MIME,Any}, page, doc) + filename = String(rand('a':'z', 7)) + if haskey(d, MIME"text/html"()) + println(io, d[MIME"text/html"()]) + elseif haskey(d, MIME"image/svg+xml"()) + # NOTE: It seems that we can't simply save the SVG images as a file and include them + # as browsers seem to need to have the xmlns attribute set in the tag if you + # want to include it with . However, setting that attribute is up to the code + # creating the SVG image. + println(io, d[MIME"image/svg+xml"()]) + elseif haskey(d, MIME"image/png"()) + write(joinpath(dirname(page.build), "$(filename).png"), base64decode(d[MIME"image/png"()])) + println(io, """ + ![]($(filename).png) + """) + elseif haskey(d, MIME"image/webp"()) + write(joinpath(dirname(page.build), "$(filename).webp"), base64decode(d[MIME"image/webp"()])) + println(io, """ + ![]($(filename).webp) + """) + elseif haskey(d, MIME"image/jpeg"()) + write(joinpath(dirname(page.build), "$(filename).jpeg"), base64decode(d[MIME"image/jpeg"()])) + println(io, """ + ![]($(filename).jpeg) + """) + elseif haskey(d, MIME"image/gif"()) + write(joinpath(dirname(page.build), "$(filename).gif"), base64decode(d[MIME"image/gif"()])) + println(io, """ + ![]($(filename).gif) + """) + elseif haskey(d, MIME"text/plain"()) + render(io, mime, MarkdownStdlib.Code(d[MIME"text/plain"()]), page, doc) + else + error("this should never happen.") + end + return nothing +end + ## Basic Nodes. AKA: any other content that hasn't been handled yet. diff --git a/test/examples/mkdocs.yml b/test/examples/mkdocs.yml index 087f324fc1c..5ad6068e832 100644 --- a/test/examples/mkdocs.yml +++ b/test/examples/mkdocs.yml @@ -23,7 +23,7 @@ extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?config=TeX-AMS_HTML - assets/mathjaxhelper.js -docs_dir: 'build' +docs_dir: 'builds/markdown' pages: - Home: index.md