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