diff --git a/MakieCore/src/basic_plots.jl b/MakieCore/src/basic_plots.jl index 2cf29f08372..325d59d6806 100644 --- a/MakieCore/src/basic_plots.jl +++ b/MakieCore/src/basic_plots.jl @@ -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. @@ -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), diff --git a/Project.toml b/Project.toml index 0d8dbc16c14..71d019b1dd5 100644 --- a/Project.toml +++ b/Project.toml @@ -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" @@ -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" diff --git a/ReferenceTests/src/tests/examples2d.jl b/ReferenceTests/src/tests/examples2d.jl index 9c9bce57b5b..757eadde6ae 100644 --- a/ReferenceTests/src/tests/examples2d.jl +++ b/ReferenceTests/src/tests/examples2d.jl @@ -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 \ No newline at end of file diff --git a/docs/documentation/fonts.md b/docs/documentation/fonts.md new file mode 100644 index 00000000000..8bdd18c6d6f --- /dev/null +++ b/docs/documentation/fonts.md @@ -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). + + diff --git a/docs/examples/blocks/axis.md b/docs/examples/blocks/axis.md index fbb2acc1b95..bc9ac3765ca 100644 --- a/docs/examples/blocks/axis.md +++ b/docs/examples/blocks/axis.md @@ -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, ) @@ -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, ) diff --git a/docs/examples/plotting_functions/text.md b/docs/examples/plotting_functions/text.md index 955f6726da1..55e3adb831b 100644 --- a/docs/examples/plotting_functions/text.md +++ b/docs/examples/plotting_functions/text.md @@ -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} diff --git a/docs/tutorials/layout-tutorial.md b/docs/tutorials/layout-tutorial.md index a69b7e14e4b..c968e804212 100644 --- a/docs/tutorials/layout-tutorial.md +++ b/docs/tutorials/layout-tutorial.md @@ -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) @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/Makie.jl b/src/Makie.jl index adcb9076a7b..8d8aac3b557 100644 --- a/src/Makie.jl +++ b/src/Makie.jl @@ -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 @@ -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") @@ -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") @@ -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() diff --git a/src/basic_recipes/axis.jl b/src/basic_recipes/axis.jl index 088cb91e71d..f9744a8efb5 100644 --- a/src/basic_recipes/axis.jl +++ b/src/basic_recipes/axis.jl @@ -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), @@ -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) @@ -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] @@ -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 diff --git a/src/basic_recipes/buffers.jl b/src/basic_recipes/buffers.jl index a42d026582e..823ddbcae32 100644 --- a/src/basic_recipes/buffers.jl +++ b/src/basic_recipes/buffers.jl @@ -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 diff --git a/src/basic_recipes/text.jl b/src/basic_recipes/text.jl index 2358c03c732..00daa5ef7f0 100644 --- a/src/basic_recipes/text.jl +++ b/src/basic_recipes/text.jl @@ -7,12 +7,12 @@ function plot!(plot::Text) linecolors = Observable(RGBAf[]) lineindices = Ref(Int[]) - onany(plot.text, plot.fontsize, plot.font, plot.align, + onany(plot.text, plot.fontsize, plot.font, plot.fonts, plot.align, plot.rotation, plot.justification, plot.lineheight, plot.color, plot.strokecolor, plot.strokewidth, plot.word_wrap_width) do str, - ts, f, al, rot, jus, lh, col, scol, swi, www + ts, f, fs, al, rot, jus, lh, col, scol, swi, www ts = to_fontsize(ts) - f = to_font(f) + f = to_font(fs, f) rot = to_rotation(rot) col = to_color(col) scol = to_color(scol) @@ -36,12 +36,12 @@ function plot!(plot::Text) # as per string. broadcast_foreach( func, - str, 1:attr_broadcast_length(str), ts, f, al, rot, jus, lh, col, scol, swi, www + str, 1:attr_broadcast_length(str), ts, f, fs, al, rot, jus, lh, col, scol, swi, www ) else # Otherwise Vector arguments are interpreted by layout_text/ # glyph_collection as per character. - func(str, 1, ts, f, al, rot, jus, lh, col, scol, swi, www) + func(str, 1, ts, f, fs, al, rot, jus, lh, col, scol, swi, www) end glyphcollections[] = gcs linewidths[] = lwidths @@ -70,17 +70,20 @@ function plot!(plot::Text) pop!(attrs, :align) pop!(attrs, :color) - text!(plot, glyphcollections; attrs..., position = positions) + t = text!(plot, glyphcollections; attrs..., position = positions) + # remove attributes that the backends will choke on + pop!(t.attributes, :font) + pop!(t.attributes, :fonts) linesegments!(plot, linesegs_shifted; linewidth = linewidths, color = linecolors, space = :pixel) plot end -function _get_glyphcollection_and_linesegments(str::AbstractString, index, ts, f, al, rot, jus, lh, col, scol, swi, www) - gc = layout_text(string(str), ts, f, al, rot, jus, lh, col, scol, swi, www) +function _get_glyphcollection_and_linesegments(str::AbstractString, index, ts, f, fs, al, rot, jus, lh, col, scol, swi, www) + gc = layout_text(string(str), ts, f, fs, al, rot, jus, lh, col, scol, swi, www) gc, Point2f[], Float32[], RGBAf[], Int[] end -function _get_glyphcollection_and_linesegments(latexstring::LaTeXString, index, ts, f, al, rot, jus, lh, col, scol, swi, www) +function _get_glyphcollection_and_linesegments(latexstring::LaTeXString, index, ts, f, fs, al, rot, jus, lh, col, scol, swi, www) tex_elements, glyphcollections, offset = texelems_and_glyph_collection(latexstring, ts, al[1], al[2], rot, col, scol, swi, www) @@ -258,3 +261,276 @@ function texelems_and_glyph_collection(str::LaTeXString, fontscale_px, halign, v end iswhitespace(l::LaTeXString) = iswhitespace(replace(l.s, '$' => "")) + +struct RichText <: AbstractString + type::Symbol + children::Vector{Union{RichText,String}} + attributes::Dict{Symbol, Any} + function RichText(type::Symbol, children...; kwargs...) + cs = Union{RichText,String}[children...] + typeof(cs) + new(type, cs, Dict(kwargs)) + end +end + +function Base.String(r::RichText) + fn(io, x::RichText) = foreach(x -> fn(io, x), x.children) + fn(io, s::String) = print(io, s) + sprint() do io + fn(io, r) + end +end + +Base.ncodeunits(r::RichText) = ncodeunits(String(r)) # needed for isempty + +function Base.show(io::IO, ::MIME"text/plain", r::RichText) + print(io, "RichText: \"$(String(r))\"") +end + +function Base.:(==)(r1::RichText, r2::RichText) + r1.type == r2.type && r1.children == r2.children && r1.attributes == r2.attributes +end + +rich(args...; kwargs...) = RichText(:span, args...; kwargs...) +subscript(args...; kwargs...) = RichText(:sub, args...; kwargs...) +superscript(args...; kwargs...) = RichText(:sup, args...; kwargs...) + +export rich, subscript, superscript + +function _get_glyphcollection_and_linesegments(rt::RichText, index, ts, f, fset, al, rot, jus, lh, col, scol, swi, www) + gc = layout_text(rt, ts, f, fset, al, rot, jus, lh, col) + gc, Point2f[], Float32[], RGBAf[], Int[] +end + +struct GlyphState + x::Float32 + baseline::Float32 + size::Vec2f + font::FreeTypeAbstraction.FTFont + color::RGBAf +end + +struct GlyphInfo + glyph::Int + font::FreeTypeAbstraction.FTFont + origin::Point2f + extent::GlyphExtent + size::Vec2f + rotation::Quaternion + color::RGBAf + strokecolor::RGBAf + strokewidth::Float32 +end + +function GlyphCollection(v::Vector{GlyphInfo}) + GlyphCollection( + [i.glyph for i in v], + [i.font for i in v], + [Point3f(i.origin..., 0) for i in v], + [i.extent for i in v], + [i.size for i in v], + [i.rotation for i in v], + [i.color for i in v], + [i.strokecolor for i in v], + [i.strokewidth for i in v], + ) +end + + +function layout_text(rt::RichText, ts, f, fset, al, rot, jus, lh, col) + + _f = to_font(fset, f) + + stack = [GlyphState(0, 0, Vec2f(ts), _f, to_color(col))] + + lines = [GlyphInfo[]] + + process_rt_node!(stack, lines, rt, fset) + + apply_lineheight!(lines, lh) + apply_alignment_and_justification!(lines, jus, al) + + gc = GlyphCollection(reduce(vcat, lines)) + quat = to_rotation(rot)::Quaternionf + gc.origins .= Ref(quat) .* gc.origins + @assert gc.rotations.sv isa Vector # should always be a vector because that's how the glyphcollection is created + gc.rotations.sv .= Ref(quat) .* gc.rotations.sv + gc +end + +function apply_lineheight!(lines, lh) + for (i, line) in enumerate(lines) + for j in eachindex(line) + l = line[j] + l = Setfield.@set l.origin[2] -= (i-1) * 20 # TODO: Lineheight + line[j] = l + end + end + return +end + +function apply_alignment_and_justification!(lines, ju, al) + max_xs = map(lines) do line + maximum(line, init = 0f0) do ginfo + ginfo.origin[1] + ginfo.extent.hadvance * ginfo.size[1] + end + end + max_x = maximum(max_xs) + + top_y = maximum(lines[1]) do ginfo + ginfo.origin[2] + ginfo.extent.ascender * ginfo.size[2] + end + bottom_y = minimum(lines[end]) do ginfo + ginfo.origin[2] + ginfo.extent.descender * ginfo.size[2] + end + + al_offset_x = if al[1] == :center + max_x / 2 + elseif al[1] == :left + 0f0 + elseif al[1] == :right + max_x + else + 0f0 + end + + al_offset_y = if al[2] == :center + 0.5 * (top_y + bottom_y) + elseif al[2] == :bottom + bottom_y + elseif al[2] == :top + top_y + else + 0f0 + end + + fju = float_justification(ju, al) + + for (i, line) in enumerate(lines) + ju_offset = fju * (max_x - max_xs[i]) + for j in eachindex(line) + l = line[j] + l = Setfield.@set l.origin -= Point2f(al_offset_x - ju_offset, al_offset_y) + line[j] = l + end + end + return +end + +function float_justification(ju, al)::Float32 + halign = al[1] + float_justification = if ju === automatic + if halign == :left || halign == 0 + 0.0f0 + elseif halign == :right || halign == 1 + 1.0f0 + elseif halign == :center || halign == 0.5 + 0.5f0 + else + 0.5f0 + end + elseif ju == :left + 0.0f0 + elseif ju == :right + 1.0f0 + elseif ju == :center + 0.5f0 + else + Float32(ju) + end +end + +function process_rt_node!(stack, lines, rt::RichText, fonts) + _type(x) = nothing + _type(r::RichText) = r.type + + push!(stack, new_glyphstate(stack[end], rt, Val(rt.type), fonts)) + for (i, c) in enumerate(rt.children) + process_rt_node!(stack, lines, c, fonts) + end + gs = pop!(stack) + gs_top = stack[end] + # x needs to continue even if going a level up + stack[end] = GlyphState(gs.x, gs_top.baseline, gs_top.size, gs_top.font, gs_top.color) + return +end + +function process_rt_node!(stack, lines, s::String, _) + gs = stack[end] + y = gs.baseline + x = gs.x + for char in s + if char === '\n' + x = 0 + push!(lines, GlyphInfo[]) + else + gi = FreeTypeAbstraction.glyph_index(gs.font, char) + gext = GlyphExtent(gs.font, char) + ori = Point2f(x, y) + push!(lines[end], GlyphInfo( + gi, + gs.font, + ori, + gext, + gs.size, + to_rotation(0), + gs.color, + RGBAf(0, 0, 0, 0), + 0f0, + )) + x = x + gext.hadvance * gs.size[1] + end + end + stack[end] = GlyphState(x, y, gs.size, gs.font, gs.color) + return +end + +function new_glyphstate(gs::GlyphState, rt::RichText, val::Val, fonts) + gs +end + +_get_color(attributes, default)::RGBAf = haskey(attributes, :color) ? to_color(attributes[:color]) : default +_get_font(attributes, default::NativeFont, fonts)::NativeFont = haskey(attributes, :font) ? to_font(fonts, attributes[:font]) : default +_get_fontsize(attributes, default)::Vec2f = haskey(attributes, :fontsize) ? Vec2f(to_textsize(attributes[:fontsize])) : default +_get_offset(attributes, default)::Vec2f = haskey(attributes, :offset) ? Vec2f(attributes[:offset]) : default + +function new_glyphstate(gs::GlyphState, rt::RichText, val::Val{:sup}, fonts) + att = rt.attributes + fontsize = _get_fontsize(att, gs.size * 0.66) + offset = _get_offset(att, Vec2f(0)) .* fontsize + GlyphState( + gs.x + offset[1], + gs.baseline + 0.4 * gs.size[2] + offset[2], + fontsize, + _get_font(att, gs.font, fonts), + _get_color(att, gs.color), + ) +end + +function new_glyphstate(gs::GlyphState, rt::RichText, val::Val{:span}, fonts) + att = rt.attributes + fontsize = _get_fontsize(att, gs.size) + offset = _get_offset(att, Vec2f(0)) .* fontsize + GlyphState( + gs.x + offset[1], + gs.baseline + offset[2], + fontsize, + _get_font(att, gs.font, fonts), + _get_color(att, gs.color), + ) +end + +function new_glyphstate(gs::GlyphState, rt::RichText, val::Val{:sub}, fonts) + att = rt.attributes + fontsize = _get_fontsize(att, gs.size * 0.66) + offset = _get_offset(att, Vec2f(0)) .* fontsize + GlyphState( + gs.x + offset[1], + gs.baseline - 0.15 * gs.size[2] + offset[2], + fontsize, + _get_font(att, gs.font, fonts), + _get_color(att, gs.color), + ) +end + +iswhitespace(r::RichText) = iswhitespace(String(r)) diff --git a/src/conversions.jl b/src/conversions.jl index 013ead58353..215edf871bb 100644 --- a/src/conversions.jl +++ b/src/conversions.jl @@ -920,7 +920,7 @@ const FONT_CACHE = Dict{String, NativeFont}() a string naming a font, e.g. helvetica """ -function to_font(x::Union{Symbol, String}) +function to_font(x::String) str = string(x) get!(FONT_CACHE, str) do str == "default" && return to_font("TeX Gyre Heros Makie") @@ -952,6 +952,20 @@ to_font(x::Vector{String}) = to_font.(x) to_font(x::NativeFont) = x to_font(x::Vector{NativeFont}) = x +function to_font(fonts::Attributes, s::Symbol) + if haskey(fonts, s) + f = fonts[s][] + if f isa Symbol + error("The value for font $(repr(s)) was Symbol $(repr(f)), which is not allowed. The value for a font in the fonts collection cannot be another Symbol and must be resolvable via `to_font(x)`.") + end + return to_font(fonts[s][]) + end + error("The symbol $(repr(s)) is not present in the fonts collection:\n$fonts.") +end + +to_font(fonts::Attributes, x) = to_font(x) + + """ rotation accepts: to_rotation(b, quaternion) diff --git a/src/layouting/boundingbox.jl b/src/layouting/boundingbox.jl index 0faee6ef67e..18757dcb7a5 100644 --- a/src/layouting/boundingbox.jl +++ b/src/layouting/boundingbox.jl @@ -131,8 +131,9 @@ _is_latex_string(other) = false function text_bb(str, font, size) rot = Quaternionf(0,0,0,1) + fonts = nothing # TODO: remove the arg if possible layout = layout_text( - str, size, font, Vec2f(0), rot, 0.5, 1.0, + str, size, font, fonts, Vec2f(0), rot, 0.5, 1.0, RGBAf(0, 0, 0, 0), RGBAf(0, 0, 0, 0), 0f0, 0f0) return boundingbox(layout, Point3f(0), rot) end diff --git a/src/layouting/layouting.jl b/src/layouting/layouting.jl index b55f6842394..a6d6a06ee05 100644 --- a/src/layouting/layouting.jl +++ b/src/layouting/layouting.jl @@ -40,7 +40,7 @@ Compute a GlyphCollection for a `string` given fontsize, font, align, rotation, """ function layout_text( string::AbstractString, fontsize::Union{AbstractVector, Number}, - font, align, rotation, justification, lineheight, color, + font, fonts, align, rotation, justification, lineheight, color, strokecolor, strokewidth, word_wrap_width ) diff --git a/src/makielayout/lineaxis.jl b/src/makielayout/lineaxis.jl index 57113f9ef51..54cf9601f67 100644 --- a/src/makielayout/lineaxis.jl +++ b/src/makielayout/lineaxis.jl @@ -587,7 +587,7 @@ function get_ticks(l::LogTicks, scale::Union{typeof(log10), typeof(log2), typeof xs -> Showoff.showoff(xs, :plain), ticks_scaled ) - labels = _logbase(scale) .* Makie.UnicodeFun.to_superscript.(labels_scaled) + labels = rich.(_logbase(scale), superscript.(labels_scaled, offset = Vec2f(0.1f0, 0f0))) (ticks, labels) end @@ -650,7 +650,7 @@ end """ get_ticklabels(::Automatic, values) -Gets tick labels by applying `Showoff.showoff` to `values`. +Gets tick labels by applying `showoff` to `values`. """ get_ticklabels(::Automatic, values) = Showoff.showoff(values) @@ -677,7 +677,6 @@ function get_ticks(m::MultiplesTicks, any_scale, ::Automatic, vmin, vmax) multiples .* m.multiple, Showoff.showoff(multiples) .* m.suffix end - function get_minor_tickvalues(i::IntervalsBetween, scale, tickvalues, vmin, vmax) vals = Float64[] length(tickvalues) < 2 && return vals diff --git a/src/makielayout/types.jl b/src/makielayout/types.jl index 74c859013a5..84a933bb234 100644 --- a/src/makielayout/types.jl +++ b/src/makielayout/types.jl @@ -207,7 +207,7 @@ end "The axis title string." title = "" "The font family of the title." - titlefont::Makie.FreeTypeAbstraction.FTFont = "TeX Gyre Heros Makie Bold" + titlefont = :bold "The title's font size." titlesize::Float64 = @inherit(:fontsize, 16f0) "The gap between axis and title." @@ -223,7 +223,7 @@ end "The axis subtitle string." subtitle = "" "The font family of the subtitle." - subtitlefont::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + subtitlefont = :regular "The subtitle's font size." subtitlesize::Float64 = @inherit(:fontsize, 16f0) "The gap between subtitle and title." @@ -235,9 +235,9 @@ end "The axis subtitle line height multiplier." subtitlelineheight::Float64 = 1 "The font family of the xlabel." - xlabelfont::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + xlabelfont = :regular "The font family of the ylabel." - ylabelfont::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + ylabelfont = :regular "The color of the xlabel." xlabelcolor::RGBAf = @inherit(:textcolor, :black) "The color of the ylabel." @@ -255,9 +255,9 @@ end "The padding between the ylabel and the ticks or axis." ylabelpadding::Float64 = 5f0 # xlabels usually have some more visual padding because of ascenders, which are larger than the hadvance gaps of ylabels "The font family of the xticklabels." - xticklabelfont::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + xticklabelfont = :regular "The font family of the yticklabels." - yticklabelfont::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + yticklabelfont = :regular "The color of xticklabels." xticklabelcolor::RGBAf = @inherit(:textcolor, :black) "The color of yticklabels." @@ -492,7 +492,7 @@ end "The label color." labelcolor = @inherit(:textcolor, :black) "The label font family." - labelfont = @inherit(:font, "TeX Gyre Heros Makie") + labelfont = :regular "The label font size." labelsize = @inherit(:fontsize, 16f0) "Controls if the label is visible." @@ -500,7 +500,7 @@ end "The gap between the label and the ticks." labelpadding = 5f0 "The font family of the tick labels." - ticklabelfont = @inherit(:font, "TeX Gyre Heros Makie") + ticklabelfont = :regular "The font size of the tick labels." ticklabelsize = @inherit(:fontsize, 16f0) "Controls if the tick labels are visible." @@ -609,7 +609,7 @@ end "The font size of the text." fontsize::Float32 = @inherit(:fontsize, 16f0) "The font family of the text." - font::Makie.FreeTypeAbstraction.FTFont = @inherit(:font, "TeX Gyre Heros Makie") + font = :regular "The justification of the text (:left, :right, :center)." justification = :center "The lineheight multiplier for the text." @@ -783,7 +783,7 @@ end "The text of the button label." label = "Button" "The font family of the button label." - font = @inherit(:font, "TeX Gyre Heros Makie") + font = :regular "The width setting of the button." width = Auto() "The height setting of the button." @@ -947,7 +947,7 @@ const EntryGroup = Tuple{Optional{<:AbstractString}, Vector{LegendEntry}} "Controls if the parent layout can adjust to this element's height" tellheight = automatic "The font family of the legend group titles." - titlefont = "TeX Gyre Heros Makie Bold" + titlefont = :bold "The font size of the legend group titles." titlesize = @inherit(:fontsize, 16f0) "The horizontal alignment of the legend group titles." @@ -963,7 +963,7 @@ const EntryGroup = Tuple{Optional{<:AbstractString}, Vector{LegendEntry}} "The font size of the entry labels." labelsize = @inherit(:fontsize, 16f0) "The font family of the entry labels." - labelfont = @inherit(:font, "TeX Gyre Heros Makie") + labelfont = :regular "The color of the entry labels." labelcolor = @inherit(:textcolor, :black) "The horizontal alignment of the entry labels." @@ -1102,7 +1102,7 @@ end "Text color for the placeholder." textcolor_placeholder = RGBf(0.5, 0.5, 0.5) "Font family." - font = @inherit(:font, "TeX Gyre Heros Makie") + font = :regular "Color of the box." boxcolor = :transparent "Color of the box when focused." @@ -1211,11 +1211,11 @@ end "The z label size" zlabelsize = @inherit(:fontsize, 16f0) "The x label font" - xlabelfont = @inherit(:font, "TeX Gyre Heros Makie") + xlabelfont = :regular "The y label font" - ylabelfont = @inherit(:font, "TeX Gyre Heros Makie") + ylabelfont = :regular "The z label font" - zlabelfont = @inherit(:font, "TeX Gyre Heros Makie") + zlabelfont = :regular "The x label rotation" xlabelrotation = Makie.automatic "The y label rotation" @@ -1253,11 +1253,11 @@ end "The z ticklabel pad" zticklabelpad = 10 "The x ticklabel font" - xticklabelfont = @inherit(:font, "TeX Gyre Heros Makie") + xticklabelfont = :regular "The y ticklabel font" - yticklabelfont = @inherit(:font, "TeX Gyre Heros Makie") + yticklabelfont = :regular "The z ticklabel font" - zticklabelfont = @inherit(:font, "TeX Gyre Heros Makie") + zticklabelfont = :regular "The x grid color" xgridcolor = RGBAf(0, 0, 0, 0.12) "The y grid color" @@ -1335,7 +1335,7 @@ end "The axis title string." title = "" "The font family of the title." - titlefont = "TeX Gyre Heros Makie Bold" + titlefont = :bold "The title's font size." titlesize = @inherit(:fontsize, 16f0) "The gap between axis and title." diff --git a/src/theming.jl b/src/theming.jl index 1cc1cb19e85..2857266da31 100644 --- a/src/theming.jl +++ b/src/theming.jl @@ -55,7 +55,13 @@ const default_palettes = Attributes( const minimal_default = Attributes( palette = default_palettes, - font = "TeX Gyre Heros Makie", + font = :regular, + fonts = Attributes( + :regular => "TeX Gyre Heros Makie", + :bold => "TeX Gyre Heros Makie Bold", + :italic => "TeX Gyre Heros Makie Italic", + :bold_italic => "TeX Gyre Heros Makie Bold Italic", + ), fontsize = 16, textcolor = :black, padding = Vec3f(0.05),