BR: Transform matrix incorrectly applied to SVG text elements

When a text element in an SVG file has its own transform matrix, then the text is drawn by Drawable methods (draw, drawAt, drawWithin) in wrong positions. Here is a simple example to test:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="239.889mm" height="310.444mm"
 viewBox="0 0 680 880"
 xmlns="" xmlns:xlink=""  version="1.2" baseProfile="tiny">
<title>SVG TEST</title>
<g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" >

<g fill="#000000" fill-opacity="1" stroke="#000000" stroke-opacity="1" stroke-width="1" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(0.46, 0, 0, 0.46, 365.76, 157.378)
<text fill="#000000" fill-opacity="1" stroke="none" xml:space="preserve" x="162.797" y="19.5938" font-family="Arial" font-size="20" font-weight="400" font-style="normal" 
 >SVG FONT TEST</text>


When I remove the transform matrix from the SVG and use the transformation applied directly, then everything is displayed correctly:

AffineTransform transform (0.46, 0, 365.76, 0, 0.46, 157.378);
g.addTransform (transform);
drawable->drawAt (g, 0, 0, 1);

So I assume there is some bug in the SVG parser or how the transform matrix is applied when drawing Drawable. Before I always changed texts to curves to eliminate this kind of problems but right now for some reasons I’d prefer to draw texts directly. Could you please look into this?

I did some debugging and apparently the problem is caused by applying the same transform matrix twice to text SVG elements. Fonts themselves are scaled once and have correct sizes but after that positions of text rectangles are transformed again, so: text position = initial position correctly translated + (scale * translation). This can be checked in juce_DrawableComposite.cpp, line 56 (JUCE 6.1.5):

Rectangle<float> DrawableComposite::getDrawableBounds() const
    Rectangle<float> r;

    for (auto* c : getChildren())
        if (auto* d = dynamic_cast<const Drawable*> (c))
            r = r.getUnion (d->isTransformed() ? d->getDrawableBounds().transformedBy (d->getTransform())
                                               : d->getDrawableBounds());

    return r;

For text elements this iteration returns drawable bounds translated twice.

Thank you for reporting this. A fix for this issue is now out on develop

1 Like

Thanks! I am just testing my app after the fix and it seems everything is ok now.


It looks this commit caused that now getOutlineAsPath does not work correctly when a transform matrix is applied. So, I think it is the same conceptual error as before but because it was removed by you for the draw methods, it “appeared” now for DrawableText.getOutlineAsPath :wink:

I tried to reproduce this by applying AffineTransforms to a DrawableComposite containing text as well, and seeing if the rendered outline followed the drawn composite. I couldn’t produce anything that looked odd.

Could you provide more details please, or ideally, a code sample that reproduced the issue?

Yes, I prepared a small demo app where you can test it. So, when I load SVG to Drawable and then draw my drawable, everything seems to be ok*. When I load SVG to Drawable and then I getOutlineAsPath, and then draw it, all texts are shifted (if there was some transform matrix in the SVG, of course). Both cases you can test in the app.

  • In fact, in the test1.svg in the app, it appears that the text is not rendered correctly even if loaded directly from SVG. Please compare test1.svg in the app and opened via an external app like Inkscape etc. (41.1 KB)


Bumping this thread because I’ve encountered a similar issue today when utilizing juce::Drawable::setTransformToFit() when rendering an SVG to a resizable plugin UI. Applying juce::AffineTransform::scale() to my graphics context directly before utilizing juce::Drawable::drawAt() has fixed my issue as a workaround.


This is on our short list of fixes, but the correct fix seems a little involved. Thank you for the feedback so far.


The issues reproduced in this thread have been fixed on develop with