Skip to content

Instantly share code, notes, and snippets.

@jkrumbiegel
Created April 12, 2026 11:25
Show Gist options
  • Select an option

  • Save jkrumbiegel/1b174d4f22be8f35472cbaec8107060b to your computer and use it in GitHub Desktop.

Select an option

Save jkrumbiegel/1b174d4f22be8f35472cbaec8107060b to your computer and use it in GitHub Desktop.
TextOnPath for Makie 0.24 — place text along a BezierPath
using LinearAlgebra: norm
"""
TextOnPath(text::String, path::Makie.BezierPath)
A text value that lays out characters along a `BezierPath`. Pass as the `text`
argument to `text!`, together with `markerspace = :data` so that glyph positions
and fontsize are in data-space units. The text position should be `Point2f(0)`.
Important: don't provide a closed path — it can lead to infinite loops.
See also [`text_on_path!`](@ref) which handles these settings automatically.
"""
struct TextOnPath
text::String
path::Makie.BezierPath
end
"""
text_on_path!(target, text::String, path::Makie.BezierPath; fontsize=0.5, kwargs...)
Place `text` along `path` in data-space coordinates. `fontsize` is in the same
units as the path (data units). All other keyword arguments are forwarded to
`text!`.
Important: don't provide a closed path — it can lead to infinite loops.
"""
function text_on_path!(target, text::String, path::Makie.BezierPath; fontsize=0.5, kwargs...)
return text!(target, Point2f(0);
text=TextOnPath(text, path), fontsize=fontsize,
markerspace=:data, kwargs...)
end
function Makie.convert_text_string!(
outputs::NamedTuple,
top::TextOnPath, i, N, fontsize, font, align, rotation, justification,
lineheight, word_wrap_width, offset, fonts, color, strokecolor, strokewidth
)
points = only(Makie.convert_arguments(Makie.PointBased(), top.path))
_font = Makie.to_font(Makie.sv_getindex(font, i))
_fontsize = Makie.to_fontsize(Makie.sv_getindex(fontsize, i))
_color = Makie.to_color(Makie.sv_getindex(color, i))
_strokecolor = Makie.to_color(Makie.sv_getindex(strokecolor, i))
_strokewidth = Float32(Makie.sv_getindex(strokewidth, i))
gc = _glyph_collection_on_path(points, top.text, _font, _fontsize, _color, _strokecolor, _strokewidth)
curr = length(outputs.glyphindices)
n = length(gc.glyphs)
push!(outputs.glyphcollections, gc)
push!(outputs.text_blocks, (curr + 1):(curr + n))
append!(outputs.glyphindices, gc.glyphs)
append!(outputs.glyph_origins, gc.origins)
append!(outputs.glyph_extents, gc.extents)
append!(outputs.font_per_char, Makie.collect_vector(gc.fonts, n))
append!(outputs.text_color, Makie.collect_vector(gc.colors, n))
append!(outputs.text_strokecolor, Makie.collect_vector(gc.strokecolors, n))
append!(outputs.text_strokewidth, Makie.collect_vector(gc.strokewidths, n))
append!(outputs.text_rotation, Makie.collect_vector(gc.rotations, n))
append!(outputs.text_scales, Makie.collect_vector(gc.scales, n))
return
end
function _glyph_collection_on_path(points, text, font, fontsize, color, strokecolor, strokewidth)
glyphinfos = Makie.GlyphInfo[]
seg = 1
at_fraction = 0.0
sz = Vec2f(fontsize, fontsize)
for char in text
while seg < length(points) - 1 && (any(isnan, points[seg]) || any(isnan, points[seg + 1]))
seg += 1
at_fraction = 0.0
end
if seg >= length(points) - 1
@warn "Could not fit all characters on path, reduce fontsize"
break
end
v = points[seg + 1] - points[seg]
p = points[seg] + v * at_fraction
ext = Makie.GlyphExtent(font, char)
hadvance = ext.hadvance * sz[1]
# Advance along path by character width to find next character's start
accumulated = 0.0
while true
if seg >= length(points) - 1
break
end
if any(isnan, points[seg]) || any(isnan, points[seg + 1])
seg += 1
at_fraction = 0.0
continue
end
seglength = norm(points[seg + 1] - points[seg])
remaining = (1 - at_fraction) * seglength
to_go = hadvance - (accumulated + remaining)
if to_go <= 0
at_fraction = 1 - (-to_go / seglength)
break
else
accumulated += remaining
at_fraction = 0.0
seg += 1
end
end
gi = Makie.GlyphInfo(
Makie.FreeTypeAbstraction.glyph_index(font, char),
font,
Point2f(p),
ext,
sz,
Makie.to_rotation(Vec2f(-v[2], v[1])),
color,
strokecolor,
strokewidth,
)
push!(glyphinfos, gi)
if seg >= length(points) - 1
@warn "Could not fit all characters on path, reduce fontsize"
break
end
end
return Makie.GlyphCollection(glyphinfos)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment