Skip to content

Commit

Permalink
Merge pull request #62 from bauglir/enable-showable-overrides-per-dia…
Browse files Browse the repository at this point in the history
…gram-type

Enable showable overrides per diagram type
  • Loading branch information
bauglir authored Apr 25, 2024
2 parents 8bc567a + 1840f44 commit ec2de56
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 29 deletions.
4 changes: 4 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ Diagram(::Symbol, ::AbstractString)
Diagram(::Symbol; ::Union{Nothing,AbstractString}, ::Union{Nothing,AbstractString})
SUPPORTED_TEXT_PLAIN_SHOW_MIME_TYPES
TEXT_PLAIN_SHOW_MIME_TYPE
overrideShowable
render
resetShowableOverrides
```

### Service Management
Expand Down Expand Up @@ -41,8 +43,10 @@ DiagramTypeMetadata
DIAGRAM_TYPE_METADATA
LIMITED_DIAGRAM_SUPPORT
MIME_TO_RENDER_ARGUMENT_MAP
SHOWABLE_OVERRIDES
UriSafeBase64Payload
getDiagramTypeMetadata
normalizeDiagramType
```

### Documentation
Expand Down
47 changes: 43 additions & 4 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,54 @@ using `write`.
write("mermaid_diagram.png", mermaid_diagram_as_png)
```

![Mermaid diagram as PNG example](mermaid_diagram.png)

Note the difference in file size and fonts when rendering to SVG.
Note the difference in file size when rendering to SVG.

```@example diagrams
write("mermaid_diagram.svg", render(mermaid_diagram, "svg"))
```

![Mermaid diagram as SVG example](mermaid_diagram.svg)
### Overriding `Base.show` behavior

In some cases the output format that is used may affect how the final diagram
renders. Although it is always possible to explicitly fall back to calling
`render` in these cases, this may be cumbersome when writing documentation,
etc. and having to explicitly do that for every diagram of a specific type. As
an alternative, it is possible to instruct `Kroki` to ignore certain output
formats when rendering a specific diagram type using `Base.show`.

For instance, by default (in most cases) a Mermaid diagram will be rendered as
an SVG. Due to the way these diagrams are rendered by Kroki this may result in
text getting cut off, see
[yuzutech/kroki#1345](https://github.com/yuzutech/kroki/issues/1345) for
details.

```@example diagrams
mermaid_diagram
```

!!! tip "Inspecting the output format of an image"

The most straightforward way of inspecting the output format is to open the
image in a new tab in your browser and checking its extension, e.g. by
right clicking on it and selecting _Open image in new tab_ in Chrome or a
similar browser. Alternatively the type of the rendered image can be seen
by inspecting the source of this page.

Using [`Kroki.overrideShowable`](@ref), `Kroki` can be instructed to not render
to SVG and pick the next most suitable output format with a fallback of
rendering the diagram to text if none are available.

```@example diagrams
Kroki.overrideShowable(MIME"image/svg+xml"(), :mermaid, false)
```

For Mermaid diagrams in the context of `Documenter` this means rendering the
diagram as a PNG. When rendering to PNG the previously mentioned font issues do
not arise.

```@example diagrams
mermaid_diagram
```

## Controlling text rendering

Expand Down
70 changes: 61 additions & 9 deletions src/Kroki.jl
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,9 @@ they occur.
_SVG output is supported for all [`Diagram`](@ref) types_. See the [support
table](@ref diagram-support) for an overview of other supported output formats
per diagram type.
per diagram type and [`overrideShowable`](@ref) in case it is necessary to
override default `Base.show` behavior, e.g. to disable a specific output
format.
"""
render(
diagram::Diagram,
Expand Down Expand Up @@ -354,6 +356,48 @@ const MIME_TO_RENDER_ARGUMENT_MAP = Dict{MIME, String}(
MIME"text/plain; charset=utf-8"() => "utxt",
)

"""
Normalizes the `type` of the [`Diagram`](@ref) enabling consistent comparisons,
etc. while enabling user-facing case insensitivity.
"""
normalizeDiagramType(diagram::Diagram) = normalizeDiagramType(diagram.type)
normalizeDiagramType(diagram_type::Symbol) = Symbol(lowercase(String(diagram_type)))

"""
Overrides the behavior of `Base.showable` for a specific `output_format` and
`diagram_type` relative to what is recorded in
[`LIMITED_DIAGRAM_SUPPORT`](@ref). The overrides are tracked through
[`SHOWABLE_OVERRIDES`](@ref).
Note that overriding whether a diagram type is `showable` for a specific output
format, specifically enabling one, requires the Kroki service to support it for
a diagram to properly render!
"""
function overrideShowable(output_format::MIME, diagram_type::Symbol, supported::Bool)
SHOWABLE_OVERRIDES[(normalizeDiagramType(diagram_type), output_format)] = supported
end

"""
Resets [`SHOWABLE_OVERRIDES`](@ref) so that the default `showable` support is
enabled for all diagram types.
"""
resetShowableOverrides() = empty!(SHOWABLE_OVERRIDES)

"""
Tracks overrides that should be applied to the output format support registered
in [`LIMITED_DIAGRAM_SUPPORT`](@ref) for specific diagram types.
Typically used to disable an output format that might selected by `Base.show`
to render a specific diagram type to for a given display in case that produces
undesired results. Can also be used to enable output formats in addition to SVG
for new diagram types that support them and that may not have been added to
this package's [`LIMITED_DIAGRAM_SUPPORT`](@ref) yet.
Should be manipulated using [`overrideShowable`](@ref) and
[`resetShowableOverrides`](@ref).
"""
const SHOWABLE_OVERRIDES = Dict{Tuple{Symbol, MIME}, Bool}()

# `Base.show` methods should only be defined for diagram types that actually
# support the desired output format. This would make sure incompatible formats
# are not accidentally rendered on compatible `AbstractDisplay`s causing
Expand All @@ -369,14 +413,22 @@ Base.show(io::IO, ::T, diagram::Diagram) where {T <: MIME} =
# to render this MIME type, it is simply forwarded to that method
Base.show(io::IO, ::MIME"text/plain", diagram::Diagram) = show(io, diagram)

# SVG output is supported by _all_ diagram types. An additional `showable`
# method is necessary as `LIMITED_DIAGRAM_SUPPORT` documents only those diagram
# types that _only_ support SVG. This makes sure SVG output also works for new
# diagram types if they get added to the Kroki service, but not yet to this
# package
Base.showable(::MIME"image/svg+xml", ::Diagram) = true
Base.showable(::T, diagram::Diagram) where {T <: MIME} =
Symbol(lowercase(String(diagram.type))) get(LIMITED_DIAGRAM_SUPPORT, T(), Tuple([]))
function Base.showable(output_format::T, diagram::Diagram) where {T <: MIME}
diagram_type = normalizeDiagramType(diagram)

override_index = (diagram_type, output_format)
if haskey(SHOWABLE_OVERRIDES, override_index)
return SHOWABLE_OVERRIDES[override_index]
elseif T === MIME"image/svg+xml"
# SVG output is supported by _all_ diagram types. Instead of encoding
# this for all types in `LIMITED_DIAGRAM_SUPPORT` this is tracked here
# so that SVG output also works for new diagram types if they get added
# to the Kroki service, but not yet to this package
return true
else
return diagram_type get(LIMITED_DIAGRAM_SUPPORT, output_format, Tuple([]))
end
end

"""
Defines the MIME type to be used when `show` gets called on a [`Diagram`](@ref)
Expand Down
70 changes: 54 additions & 16 deletions test/kroki/rendering_test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@ module RenderingTest

using Test: @test, @testset, @test_throws

using Kroki: Diagram, Kroki, render
using Kroki: Diagram, Kroki, overrideShowable, render, resetShowableOverrides
using Kroki.Exceptions: InvalidOutputFormatError, UnsupportedMIMETypeError

PDF_MIME_TYPE = MIME"application/pdf"()
PNG_MIME_TYPE = MIME"image/png"()
SVG_MIME_TYPE = MIME"image/svg+xml"()

function testShowMethodRenders(
diagram::Diagram,
mime_type::MIME,
Expand Down Expand Up @@ -121,37 +125,36 @@ end
# should be overridden to indicate the diagram cannot be rendered in the
# specified MIME type
svgbob_diagram = Diagram(:svgbob, "-->[_...__... ]")
@test_throws(
InvalidOutputFormatError,
show(IOBuffer(), MIME"application/pdf"(), svgbob_diagram)
)
@test !showable("application/pdf", svgbob_diagram)
@test_throws(InvalidOutputFormatError, sprint(show, MIME"image/png"(), svgbob_diagram))
@test !showable(MIME"image/png"(), svgbob_diagram)
@test_throws(InvalidOutputFormatError, show(IOBuffer(), PDF_MIME_TYPE, svgbob_diagram))
@test !showable(PDF_MIME_TYPE, svgbob_diagram)
@test_throws(InvalidOutputFormatError, sprint(show, PNG_MIME_TYPE, svgbob_diagram))
@test !showable(PNG_MIME_TYPE, svgbob_diagram)
@test_throws(
InvalidOutputFormatError,
show(IOBuffer(), MIME"image/jpeg"(), svgbob_diagram)
)
@test !showable("image/jpeg", svgbob_diagram)
testShowMethodRenders(svgbob_diagram, MIME"image/svg+xml"(), "svg")
testShowMethodRenders(svgbob_diagram, SVG_MIME_TYPE, "svg")
@test !showable("non-existent/mime-type", svgbob_diagram)

plantuml_diagram = Diagram(:PlantUML, "A -> B: C")
testShowMethodRenders(plantuml_diagram, MIME"image/png"(), "png")
testShowMethodRenders(plantuml_diagram, PNG_MIME_TYPE, "png")
# PlantUML diagrams support SVG, but are not part of the
# `LIMITED_DIAGRAM_SUPPORT` as they support more output formats.
#
# Given that `show` is tested directly, through `testShowMethodRenders`, it
# is necessary to make sure a `showable` method is available to indicate
# SVG is always supported to those enviroments that need to query that
# information
@test showable(MIME"image/svg+xml"(), plantuml_diagram)
testShowMethodRenders(plantuml_diagram, MIME"image/svg+xml"(), "svg")
@test showable(SVG_MIME_TYPE, plantuml_diagram)
testShowMethodRenders(plantuml_diagram, SVG_MIME_TYPE, "svg")

@testset "`text/plain`" begin
plain_text_mime_type = MIME"text/plain"()

@testset "without ASCII/Unicode rendering support" begin
# These diagram types should simply display their `specification`
@test sprint(show, MIME"text/plain"(), svgbob_diagram) ==
@test sprint(show, plain_text_mime_type, svgbob_diagram) ==
svgbob_diagram.specification
end

Expand All @@ -166,21 +169,56 @@ end
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"text/plain; charset=utf-8"()
testShowMethodRenders(plantuml_diagram, MIME"text/plain"(), "utxt")

Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"text/plain"()
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = plain_text_mime_type
testShowMethodRenders(plantuml_diagram, MIME"text/plain"(), "txt")

@testset "generates an error if an invalid `text/plain` MIME type is configured" begin
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = MIME"image/png"()
Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = PNG_MIME_TYPE

@test_throws(
UnsupportedMIMETypeError,
show(IOBuffer(), MIME"text/plain"(), plantuml_diagram)
show(IOBuffer(), plain_text_mime_type, plantuml_diagram)
)
end

Kroki.TEXT_PLAIN_SHOW_MIME_TYPE[] = original_text_plain_mimetype
end
end

@testset "overrides" begin
diagram = Diagram(:plantuml, "A -> B: C")

@testset "for `image/svg+xml`" begin
# SVG is supported by default
@test showable(SVG_MIME_TYPE, diagram)
overrideShowable(SVG_MIME_TYPE, :plantuml, false)
@test !showable(SVG_MIME_TYPE, diagram)
end

@testset "for other MIME types" begin
# PDF is supported explicitly for PlantUML through the
# `LIMITED_DIAGRAM_SUPPORT`
@test showable(PDF_MIME_TYPE, diagram)
overrideShowable(PDF_MIME_TYPE, :plantuml, false)
@test !showable(PDF_MIME_TYPE, diagram)
end

resetShowableOverrides()
@test showable(SVG_MIME_TYPE, diagram)
@test showable(PDF_MIME_TYPE, diagram)

@testset "diagram types are case insensitive" begin
overrideShowable(SVG_MIME_TYPE, :PlAnTuMl, false)
@test !showable(SVG_MIME_TYPE, diagram)

overrideShowable(PDF_MIME_TYPE, :PlantUML, false)
@test !showable(PDF_MIME_TYPE, diagram)
end

resetShowableOverrides()
@test showable(SVG_MIME_TYPE, diagram)
@test showable(PDF_MIME_TYPE, diagram)
end
end
end

Expand Down

0 comments on commit ec2de56

Please sign in to comment.