Created
April 12, 2026 11:25
-
-
Save jkrumbiegel/1b174d4f22be8f35472cbaec8107060b to your computer and use it in GitHub Desktop.
TextOnPath for Makie 0.24 — place text along a BezierPath
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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