Drawing size difference between TextLayout and Label

I’m trying to auto-fit a label’s size to it’s (multi-line) content. What I do is I first fire up a TextLayout, and add the AttributedText to show to it, also specifying the font used by the label. I then use TextLayout.createLayoutWithBalancedLineLengths, look what the resulting width/height ratio is, and do it again with an adjusted maxWidth if the resulting ratio is poor.

TextLayout then tells me the final width & height of the text, but when I set up the label to use that text and width/height, I get clipping – the last line doesn’t really fit or if there’s only one line, the end of the line is clipped and ellipses show. This happens on Windows 10 using Juce 5.3.2.

I now have a workaround in place that adds 5% to the width and 10% to the height of the label, but I really want those fudge numbers gone.

What could be the cause of such discrepancy?

Here’s the relevant snippet of my code (effectively running from within a DocumentWindow’s constructor), where the m_messageCtrl member is the actual Label we’re using, and where the m_messageScroller member is a ViewPort wrapping the label in case we need to show more text than we have space. The g_… variables are just const numeric magic numbers.

void MessageWindow::FitText() {
    // get the limits wrt the screen size, since we have no parent yet
    float maxWidth = floor(getParentWidth() * g_maxScreenPercentage);
    float maxHeight = floor(getParentHeight() * g_maxScreenPercentage);

    // set up an attributed string so we can know how our font renders our text
    juce::AttributedString attributedText;
    attributedText.append(m_messageCtrl.getText(), m_messageCtrl.getFont());
    attributedText.setJustification(juce::Justification::topLeft);

    // get the best width/height for this text
    int textWidth;
    int textHeight;
    TryToFit(attributedText, maxWidth, maxHeight, textWidth, textHeight);
    float widthHeightRatio = (float)textWidth / textHeight;
    if (widthHeightRatio > g_bestWidthHeightRatio) {
        // turns out not ideal -> make it more ideal
        TryToFit(attributedText, textWidth * g_bestWidthHeightRatio / widthHeightRatio, maxHeight, textWidth, textHeight);
    }

    // WORKAROUND: add an extra failsafe margin since the Label drawing the text
    // will always do it a bit bigger causing extra wraps and/or ellipses/clips
    textWidth *= 1.05f;
    textHeight *= 1.10f;

    // cap it if it exceeds our available space
    int cappedHeight = std::min(textHeight, (int)maxHeight);

    // and finally size us properly
    messageBounds.setPosition(g_borderMargin, g_borderMargin);
    messageBounds.setSize(textWidth, cappedHeight);
    m_messageScroller.setBounds(messageBounds);
    m_messageCtrl.setBounds(0, 0, textWidth, textHeight);
    m_messageScroller.setWantsKeyboardFocus(cappedHeight < textHeight);
}

The TryToFit method is defined as such:

// tries to fit the text in the given TextLayout within the given constraints
void MessageWindow::TryToFit(juce::AttributedString& text, int tryWidth, int tryHeight, int& resultWidth, int& resultHeight) {
    juce::TextLayout textLayout;
    textLayout.createLayoutWithBalancedLineLengths(text, tryWidth, tryHeight);
    resultWidth = (int)ceil(textLayout.getWidth());
    resultHeight = (int)ceil(textLayout.getHeight());
}

I’m experiencing this on Windows 10 as well, using the system default font (Segoe UI). It is both word-wrapping and clipping. Noticing it most when using “centered” justification.