Problems writing text along curved path

Hi everyone,

I’m struggeling to write some text along an arc. It is basically working, but it looks horrible. The indvidual letters aren’t rotated correctly along the path and the text seems to kind of drift off. I can’t figure out where I’m going wrong. My code looks like this:

float startAngle = 3.76991;
float endAngle = 8.79646;
outlinePath.addArc(centre.getX() - radius , centre.getY() - radius,
                   2 * radius, 2 * radius,
                   startAngle, endAngle, true);

g.setColour (juce::Colours::white);
g.strokePath (outlinePath, juce::PathStrokeType (4.0f));

// Create a GlyphArrangement and add the text
juce::GlyphArrangement glyphs;
glyphs.addFittedText (g.getCurrentFont(), "Curved", 0, 0, 100, 20, juce::Justification::left, 1);

// Calculate the total width of the text
float totalWidth = 0.0f;
for (int i = 0; i < glyphs.getNumGlyphs(); ++i)
{
    totalWidth += glyphs.getGlyph(i).getBounds().getWidth();
}

// Define the middle section of the arc

const float middleSectionStart = startAngle  * 0.05f;
const float middleSectionEnd = startAngle * 0.3f;

// Manually position each glyph along the middle section of the path 
float currentAngle = middleSectionStart;
for (int i = 0; i < glyphs.getNumGlyphs(); ++i)
{
    auto glyph = glyphs.getGlyph(i);
    auto glyphWidth = glyph.getBounds().getWidth();
    auto angle = currentAngle + (glyphWidth / totalWidth) * (middleSectionEnd - middleSectionStart);
    auto point = centre.getPointOnCircumference (radius + (pathDistanceToSlider * 3.5), angle);
    
    juce::AffineTransform transform;
    transform = transform.translated(point.getX() - glyphWidth / 2, point.getY());
    transform = transform.rotated(angle, point.getX(), point.getY());

    juce::Path glyphPath;
    glyph.createPath (glyphPath);
    glyphPath.applyTransform (transform);

    g.fillPath (glyphPath);

    currentAngle += (glyphWidth / totalWidth) * (middleSectionEnd - middleSectionStart) * 0.8f; // Adjust spacing factor here
}

middleSectionStart and middleSectionEnd are my attempt to make the text appear in the middle of the arc.
centre.get(X) is 125 and centre.get(Y) is 92.5 in this case.

Ideally I would like the text to be written neatly along the arc, with normal distance between the individual letters and not spaced apart along the entire arc.

Can anyone give me any pointers where I’m going wrong?

Been wondering about how to do this myself. Try using a TextLayout instead of a GlyphArrangement. Use the glyph spacing in the TextLayout to position the letters along the circle.

Note that I’m bypassing the Graphics object to call the internal low-level graphics context to draw the glyphs more directly.

Matt

        g.fillAll(juce::Colours::black);

        auto center = getLocalBounds().toFloat().getCentre();
        auto radius = (float)juce::jmin(getWidth(), getHeight()) * 0.35f;
        auto circumfrence = juce::MathConstants<float>::twoPi * radius;

        juce::AttributedString as;
        as.append("THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG", g.getCurrentFont().withHeight(24.0f), juce::Colours::white);

        juce::TextLayout textLayout;
        textLayout.createLayout(as, std::numeric_limits<float>::max());
        float angle = -juce::MathConstants<float>::pi * 0.5f;

        for (auto const& line : textLayout)
        {
            auto origin = line.lineOrigin;
            for (auto const& run : line.runs)
            {
                g.setColour(run->colour);
                g.setFont(run->font);

                juce::Point<float> lastGlyphAnchor = run->glyphs.begin()->anchor;
                for (auto const& glyph : run->glyphs)
                {
                    //
                    // Convert linear distance between glyphs to distance around the circle
                    // and then to the angle increment in radians
                    //
                    auto distance = glyph.anchor.getDistanceFrom(lastGlyphAnchor);
                    lastGlyphAnchor = glyph.anchor;
                    auto angleStep = juce::MathConstants<float>::twoPi * distance / circumfrence;
                    angle += angleStep;

                    //
                    // Position and rotate the glyph on the circle
                    //
                    auto radialPoint = center.getPointOnCircumference(radius, angle);
                    std::array<uint16_t, 1> code{ (uint16_t)glyph.glyphCode };
                    std::array<juce::Point<float>, 1> anchor{ radialPoint };

                    auto transform = juce::AffineTransform::rotation(angle, radialPoint.getX(), radialPoint.getY());
                    g.getInternalContext().drawGlyphs(code, anchor, transform);
                }
            }
        }

2 Likes

thank you so much! that finally works. Really appreciate the help!

1 Like