Skip to content

Commit

Permalink
Rich text (MakieOrg#2321)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkrumbiegel authored and t-bltg committed Dec 31, 2022
1 parent e386a5b commit 478f83e
Show file tree
Hide file tree
Showing 17 changed files with 509 additions and 58 deletions.
3 changes: 2 additions & 1 deletion MakieCore/src/basic_plots.jl
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ Plots one or multiple texts passed via the `text` keyword.
- `text` specifies one piece of text or a vector of texts to show, where the number has to match the number of positions given. Makie supports `String` which is used for all normal text and `LaTeXString` which layouts mathematical expressions using `MathTeXEngine.jl`.
- `align::Tuple{Union{Symbol, Real}, Union{Symbol, Real}} = (:left, :bottom)` sets the alignment of the string w.r.t. `position`. Uses `:left, :center, :right, :top, :bottom, :baseline` or fractions.
- `font::Union{String, Vector{String}} = "TeX Gyre Heros Makie"` sets the font for the string or each character.
- `font::Union{String, Vector{String}} = :regular` sets the font for the string or each character.
- `justification::Union{Real, Symbol} = automatic` sets the alignment of text w.r.t its bounding box. Can be `:left, :center, :right` or a fraction. Will default to the horizontal alignment in `align`.
- `rotation::Union{Real, Quaternion}` rotates text around the given position.
- `fontsize::Union{Real, Vec2f}` sets the size of each character.
Expand Down Expand Up @@ -517,6 +517,7 @@ Plots one or multiple texts passed via the `text` keyword.
default_theme(scene)...,
color = theme(scene, :textcolor),
font = theme(scene, :font),
fonts = theme(scene, :fonts),
strokecolor = (:black, 0.0),
strokewidth = 0,
align = (:left, :bottom),
Expand Down
3 changes: 2 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
RelocatableFolders = "05181044-ff0b-4ac5-8273-598c1e38db00"
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Setfield = "efcf1570-3423-57d1-acb7-fd33fddbac46"
Showoff = "992d4aef-0814-514b-bc4d-f2e9a6c4116f"
SignedDistanceFields = "73760f76-fbc4-59ce-8f25-708e95d2df96"
SnoopPrecompile = "66db9d55-30c0-4569-8b51-7e840670fc0c"
Expand Down Expand Up @@ -77,8 +78,8 @@ KernelDensity = "0.5, 0.6"
LaTeXStrings = "1.2"
MakieCore = "=0.5.2"
Match = "1.1"
MiniQhull = "0.4"
MathTeXEngine = "0.5"
MiniQhull = "0.4"
Observables = "0.5.3"
OffsetArrays = "1"
Packing = "0.4"
Expand Down
15 changes: 15 additions & 0 deletions ReferenceTests/src/tests/examples2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -761,3 +761,18 @@ end
ax.yticks = ([3, 6, 9], [L"x" , L"y" , L"z"])
f
end

@reference_test "Rich text" begin
f = Figure(fontsize = 30, resolution = (800, 600))
ax = Axis(f[1, 1],
limits = (1, 100, 0.001, 1),
xscale = log10,
yscale = log2,
title = rich("A ", rich("title", color = :red, font = :bold_italic)),
xlabel = rich("X", subscript("label", fontsize = 25)),
ylabel = rich("Y", superscript("label")),
)
Label(f[1, 2], rich("Hi", rich("Hi", offset = (0.2, 0.2), color = :blue)), tellheight = false)
Label(f[1, 3], rich("X", superscript("super"), subscript("sub")), tellheight = false)
f
end
60 changes: 60 additions & 0 deletions docs/documentation/fonts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Fonts

Makie uses the `FreeType.jl` package for font support, therefore, most fonts that this package can load should be supported by Makie as well.
Fonts can be selected in multiple different ways:

## String

If you pass a `String` as a font, this can either be resolved as a file name for a font file, or as the (partial) name of the font itself (font family plus style).
Font name matching is case insensitive and accepts partial matches.

```julia
font_by_path = "/some/path/to/a/font_file.ttf"
font_by_name = "TeX Gyre Heros Makie"
```

If you want to find out what exact font your string was resolved as, you can execute `Makie.to_font(the_string)`:

```julia:fonts1
using Makie
Makie.to_font("Blackchancery")
```
\show{fonts1}

## Symbol

A `Symbol` will be resolved by looking it up in the `text`'s `fonts` attribute.
The default theme has the following fonts set:

```julia:fonts2
using Makie
Makie.current_default_theme()[:fonts]
```
\show{fonts2}

Therefore, you can pick a font from this set by setting, for example, `font = :bold_italic`.
The advantage of this is that you can set your fonts not by hardcoding specific ones at every place where you use `text`, but by setting the fonts at the top level.

You can modify or add keys in the font set using `set_theme!`, `with_theme`, `update_theme!`, or by passing them to the `Figure` constructor.
Here's an example:

\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

f = Figure(fontsize = 24, fonts = (; regular = "Dejavu", weird = "Blackchancery"))
Axis(f[1, 1], title = "A title", xlabel = "An x label", xlabelfont = :weird)

f
```
\end{examplefigure}

## Emoji and color fonts

Currently, Makie does not have the ability to draw emoji or other color fonts.
This is due to the implementation of text drawing in GLMakie and WGLMakie, which relies on signed distance fields that can only be used to render monochrome glyphs, but not arbitrary bitmaps.
If you want to use emoji as scatter markers, consider using images (you will need to find suitable images separately, you cannot easily extract emoji from fonts with Makie).


4 changes: 2 additions & 2 deletions docs/examples/blocks/axis.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ Axis(
f[2, 1],
title = "Third Title",
titlecolor = :gray50,
titlefont = "TeX Gyre Heros Bold Italic Makie",
titlefont = :bold_italic,
titlealign = :right,
titlesize = 25,
)
Expand All @@ -200,7 +200,7 @@ Axis(
titlealign = :left,
subtitlegap = 2,
titlegap = 5,
subtitlefont = "TeX Gyre Heros Italic Makie",
subtitlefont = :italic,
subtitlelineheight = 0.9,
titlelineheight = 0.9,
)
Expand Down
73 changes: 73 additions & 0 deletions docs/examples/plotting_functions/text.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,76 @@ Legend(f[1, 2], ax)
f
```
\end{examplefigure}

## Rich text

With rich text, you can conveniently plot text whose parts have different colors or fonts, and you can position sections as subscripts and superscripts.
You can create such rich text objects using the functions `rich`, `superscript` and `subscript`, all of which create `RichText` objects.

Each of these functions takes a variable number of arguments, each of which can be a `String` or `RichText`.
Each can also take keyword arguments such as `color` or `font`, to set these attributes for the given part.
The top-level settings for font, color, etc. are taken from the `text` attributes as usual.

\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

f = Figure(fontsize = 30)
Label(
f[1, 1],
rich(
"H", subscript("2"), "O is the formula for ",
rich("water", color = :cornflowerblue, font = :italic)
)
)

str = "A BEAUTIFUL RAINBOW"
rainbow = cgrad(:rainbow, length(str), categorical = true)
fontsizes = 30 .+ 10 .* sin.(range(0, 3pi, length = length(str)))

rainbow_chars = map(enumerate(str)) do (i, c)
rich("$c", color = rainbow[i], fontsize = fontsizes[i])
end

Label(f[2, 1], rich(rainbow_chars...), font = :bold)

f
```
\end{examplefigure}

### Tweaking offsets

Sometimes, when using regular and italic fonts next to each other, the gaps between glyphs are too narrow or too wide.
You can use the `offset` value for rich text to shift glyphs by an amount proportional to the fontsize.


\begin{examplefigure}{svg = true}
```julia
using CairoMakie
CairoMakie.activate!() # hide
Makie.inline!(true) # hide

f = Figure(fontsize = 30)
Label(
f[1, 1],
rich(
"ITALIC",
superscript("Regular without x offset", font = :regular),
font = :italic
)
)

Label(
f[2, 1],
rich(
"ITALIC",
superscript("Regular with x offset", font = :regular, offset = (0.15, 0)),
font = :italic
)
)

f
```
\end{examplefigure}
12 changes: 6 additions & 6 deletions docs/tutorials/layout-tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ colgap!(ga, 10)
rowgap!(ga, 10)

Label(ga[1, 1:2, Top()], "Stimulus ratings", valign = :bottom,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 0, 5, 0))

xs = LinRange(0.5, 6, 50)
Expand Down Expand Up @@ -111,7 +111,7 @@ axs[3, 1].xlabel = "Day 1"
axs[3, 2].xlabel = "Day 2"

Label(gd[1, :, Top()], "EEG traces", valign = :bottom,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 0, 5, 0))

rowgap!(gd, 10)
Expand All @@ -133,7 +133,7 @@ colsize!(gd, 2, Auto(n_day_2))
for (label, layout) in zip(["A", "B", "C", "D"], [ga, gb, gc, gd])
Label(layout[1, 1, TopLeft()], label,
fontsize = 26,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 5, 5, 0),
halign = :right)
end
Expand Down Expand Up @@ -283,7 +283,7 @@ We can make a title by placing a label across the top two elements.
\begin{examplefigure}{px_per_unit = 1.5}
```julia
Label(ga[1, 1:2, Top()], "Stimulus ratings", valign = :bottom,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 0, 5, 0))

f
Expand Down Expand Up @@ -413,7 +413,7 @@ We can make a little title for the six axes by placing a `Label` in the top prot
\begin{examplefigure}{px_per_unit = 1.5}
```julia
Label(gd[1, :, Top()], "EEG traces", valign = :bottom,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 0, 5, 0))

f
Expand Down Expand Up @@ -485,7 +485,7 @@ That will leave all other alignments intact, because we're not creating any new
for (label, layout) in zip(["A", "B", "C", "D"], [ga, gb, gc, gd])
Label(layout[1, 1, TopLeft()], label,
fontsize = 26,
font = "TeX Gyre Heros Bold",
font = :bold,
padding = (0, 5, 5, 0),
halign = :right)
end
Expand Down
15 changes: 8 additions & 7 deletions src/Makie.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import FileIO
import SparseArrays
import TriplotBase
import MiniQhull
import Setfield

using IntervalSets: IntervalSets, (..), OpenInterval, ClosedInterval, AbstractInterval, Interval, endpoints
using FixedPointNumbers: N0f8
Expand Down Expand Up @@ -88,6 +89,9 @@ const RGBAf = RGBA{Float32}
const RGBf = RGB{Float32}
const NativeFont = FreeTypeAbstraction.FTFont

const ASSETS_DIR = RelocatableFolders.@path joinpath(@__DIR__, "..", "assets")
assetpath(files...) = normpath(joinpath(ASSETS_DIR, files...))

include("documentation/docstringextension.jl")
include("utilities/quaternions.jl")
include("bezier.jl")
Expand All @@ -101,16 +105,16 @@ include("utilities/utilities.jl") # need Makie.AbstractPattern
# Basic scene/plot/recipe interfaces + types
include("scenes.jl")

include("interfaces.jl")
include("conversions.jl")
include("units.jl")
include("shorthands.jl")
include("theming.jl")
include("themes/theme_ggplot2.jl")
include("themes/theme_black.jl")
include("themes/theme_minimal.jl")
include("themes/theme_light.jl")
include("themes/theme_dark.jl")
include("interfaces.jl")
include("units.jl")
include("conversions.jl")
include("shorthands.jl")

# camera types + functions
include("camera/projection_math.jl")
Expand Down Expand Up @@ -273,9 +277,6 @@ export cgrad, available_gradients, showgradients

export Pattern

const ASSETS_DIR = RelocatableFolders.@path joinpath(@__DIR__, "..", "assets")
assetpath(files...) = normpath(joinpath(ASSETS_DIR, files...))

export assetpath
# default icon for Makie
function icon()
Expand Down
8 changes: 4 additions & 4 deletions src/basic_recipes/axis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ $(ATTRIBUTES)
scale = Vec3f(1),
padding = 0.1,
inspectable = false,

fonts = theme(scene, :fonts),
names = Attributes(
axisnames = ("x", "y", "z"),
textcolor = (:black, :black, :black),
Expand Down Expand Up @@ -222,7 +222,7 @@ to3tuple(x::Tuple{Any, Any}) = (x[1], x[2], x[2])
to3tuple(x::Tuple{Any, Any, Any}) = x
to3tuple(x) = ntuple(i-> x, Val(3))

function draw_axis3d(textbuffer, linebuffer, scale, limits, ranges_labels, args...)
function draw_axis3d(textbuffer, linebuffer, scale, limits, ranges_labels, fonts, args...)
# make sure we extend all args to 3D
ranges, ticklabels = ranges_labels
args3d = to3tuple.(args)
Expand Down Expand Up @@ -284,7 +284,7 @@ function draw_axis3d(textbuffer, linebuffer, scale, limits, ranges_labels, args.
end
end
if !isempty(axisnames[i])
font = to_font(tfont[i])
font = to_font(fonts, tfont[i])
tick_widths = maximum(ticklabels[i]) do label
widths(text_bb(label, font, tfontsize[i]))[1]
end / scale[j]
Expand Down Expand Up @@ -337,7 +337,7 @@ function plot!(scene::SceneLike, ::Type{<: Axis3D}, attributes::Attributes, args
map_once(
draw_axis3d,
Observable(textbuffer), Observable(linebuffer), scale(scene),
axis[1], axis.ticks.ranges_labels, args...
axis[1], axis.ticks.ranges_labels, Observable(axis.fonts), args...
)
push!(scene, axis)
return axis
Expand Down
6 changes: 5 additions & 1 deletion src/basic_recipes/buffers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ function append!(tb::Annotations, text_positions::Vector{Tuple{String, Point{N,
isempty(tb[key][]) && error("please provide default for $key")
return last(tb[key][])
end
val_vec = same_length_array(text_positions, val, Key{key}())
val_vec = if key === :font
same_length_array(text_positions, to_font(tb.fonts, val))
else
same_length_array(text_positions, val, Key{key}())
end
append!(tb[key][], val_vec)
end
return
Expand Down
Loading

0 comments on commit 478f83e

Please sign in to comment.