How to precisely center and adjust the size of any text ?



  • I've been trying and failing to center in a Rectangle a character from the Google Material font.
    In order to demonstrate the problem, here are two images.

    arrow character from Material font

    Cg characters from Arimo regular font

    The first one shows an arrow from the Material font. This font is available here : https://github.com/google/material-design-icons/blob/master/iconfont/MaterialIcons-Regular.ttf
    The second one shows two characters from the Arimo regular font. This font is available here : https://fonts.google.com/download?family=Arimo

    I want a text, from any font, to be centered in a Rectangle.
    I also want that the text size increases in order that it completely fills the height of its parent. Top of the glyphs must be at the top of the parent. Bottom of the glyphs must be at the bottom of the parent.

    None of these two requirements are fulfilled by any of the two fonts.
    If the horizontal centering is partly acheived, it's not the case of the vertical centering. In each case, the reference for centering is not the vertical middle of the glyphs.
    I have painted in darker color the boundingRect and the tightBoundingRect of each text rendering.
    I have put an empty rectangle in the center of the container in order to show where I expect the text to be painted. In the following, I ignore the horizontal misalignement of the Arimo font, but it should be corrected as well.

    With the Arimo, the text is actually painted below the vertical middle. I guess that one of the reference lines is taken from the font to be used as the « vertical middle » of the font, for any character to be printed.
    With the Material, the text is actually painted above the vertical middle. I guess that, for some reason, the Qt rendering engine does not compute the tightBoundingRect in the tightest way : the height of this bounding rect is too large. Consequently, even if the problem of reference does not impact this case, as the calculated height of the glyph is wrong, the glyph is not painted at the vertical middle.

    So my question is : how can I acheive my goal ?

    For the Arimo font : as the tightBoundingRect is correct, I can render the text in an invisible Rect, and the copy the result centered the way I like. But : the size of the result is not tall enough. I see no solution right now but iterating with different Rectangle container height until I find a tightBoundingRect.height that is the closest to my display target height. This look incredibly ugly, but it's feasable at startup.

    For the Material font, as the tightBoundingRect looks incorrect, I have to first look for the correct value. I can do it by adding a detection of glyph pixels. Begining at the bottom of the tightBoundingRect , I look on each line going upwards for a pixel that is not of the same colour as the background. The first one found sets the bottom of the glyph, and I have my tighterBoundingRect.

    I find this solution cumbersome and it makes me think I've completely missed how this text rendering engine works.
    If anyone has a lead, if not a solution, in order to acheive what l took for a simple goal, I'd be grateful. Modifying the font is a possibilty for me. I've used the great Trufont tool to look at the properties of the font. Even though Trufont exportation to .otf font fails on my Windows system, I guess I can manage to use it in a VM if it proves useful.

    The code for the images :

    import QtQuick 2.9
    import QtQuick.Window 2.0
    
    
    Window
    {
        visible: true
        width: 2000
        height: 2000
        id:root
        property string theText: "Cg"//"\ue040"//"\uE15F"//"aText"//"This is p"//
        property string theFont: "Arimo"
    //    property string theFont: "Material"
        Row
        {
            anchors.centerIn: parent
            spacing: aRect.width/20
            Rectangle
            {
                id:aRect
                width: 700
                height: 500
                color: "yellow"
                Text
                {
                    font.family: root.theFont
                    id: theText
                    text: root.theText
                    height: parent.height         // not needed when anchors.fill: parent
                    font.pixelSize: height*2
                    onContentHeightChanged: console.info("height="+height+" contentHeight="+contentHeight+"parent.height="+parent.height+" fontInfo.pixelSize="+fontInfo.pixelSize);
                    fontSizeMode: Text.Fit
                    minimumPixelSize: 10
                    anchors.alignWhenCentered: false    // see: https://stackoverflow.com/questions/43284233/qt-quick-2-qml-place-text-exactly-in-center-of-circle
                }
                TextMetrics
                {
                    id:tm
                    text: theText.text
                    onHeightChanged: console.info("TextMetrics font="+font+" height="+height+" tightBoundingRect.height"+tightBoundingRect.height)
                    font.pixelSize: theText.fontInfo.pixelSize//83//theText.height
                    font.family: theText.font.family
                }
                FontMetrics
                {
                    id: fmm
                    font.family: "Material"
                    font.pixelSize: theText.fontInfo.pixelSize//83
                    Component.onCompleted: console.info("FontMetrics font="+font+"ascent="+ascent+" descent="+descent+" xHeight="+xHeight+" overlinePosition="+overlinePosition+" underlinePosition="+underlinePosition+" boundingRect("+theText.text+")="+boundingRect(theText.text)+" tightBoundingRect("+theText.text+")="+tightBoundingRect(theText.text))
                }
                FontMetrics
                {
                    id: fma
                    font.family: "Arial"
                    font.pixelSize: theText.fontInfo.pixelSize//83
                    Component.onCompleted: console.info("FontMetrics font="+font+"ascent="+ascent+" descent="+descent+" xHeight="+xHeight+" overlinePosition="+overlinePosition+" underlinePosition="+underlinePosition+" boundingRect("+theText.text+")="+boundingRect(theText.text)+" tightBoundingRect("+theText.text+")="+tightBoundingRect(theText.text))
                }
                Rectangle
                {
                    id: cloned
                    color: "red"
                    opacity: 0.2
                    width: tm.boundingRect.width
                    height: tm.boundingRect.height
                    ShaderEffectSource
                    {
                        id: anExample
                        sourceItem: anExample.target
                        property var target: theText
                        width: target.width
                        height: target.height
                    }
                    Rectangle
                    {
                        color: "blue"
                        opacity: 0.3
                        x: tm.tightBoundingRect.x - tm.boundingRect.x
                        y: tm.tightBoundingRect.y - tm.boundingRect.y
    
                        width: tm.tightBoundingRect.width
                        height: tm.tightBoundingRect.height
                    }
                }
                Rectangle
                {
                    color: "transparent"
                    border.color: "brown"
                    border.width: Math.max(2, Math.min(10, width/20))
                    opacity: 0.3
                    anchors.centerIn: parent
                    width: tm.tightBoundingRect.width
                    height: tm.tightBoundingRect.height
                }
            }
            Rectangle
            {
                width: 700
                height: 500
                color: Qt.lighter("yellow")
                Text
                {
                    font.family: root.theFont
                    id: theText2
                    text: root.theText
                    anchors.fill: parent
                    font.pixelSize: height*2
                    onContentHeightChanged: console.info("height="+height+" contentHeight="+contentHeight+"parent.height="+parent.height+" fontInfo.pixelSize="+fontInfo.pixelSize);
                    fontSizeMode: Text.Fit
                    minimumPixelSize: 10
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    anchors.alignWhenCentered: false    // see: https://stackoverflow.com/questions/43284233/qt-quick-2-qml-place-text-exactly-in-center-of-circle
                }
                TextMetrics
                {
                    id:tm2
                    text: theText2.text
                    onHeightChanged: console.info("TextMetrics font="+font+" height="+height+" tightBoundingRect.height"+tightBoundingRect.height)
                    font.pixelSize: theText2.fontInfo.pixelSize//83//theText2.height
                    font.family: theText2.font.family
                }
                FontMetrics
                {
                    id: fmm2
                    font.family: "Material"
                    font.pixelSize: theText2.fontInfo.pixelSize//83
                    Component.onCompleted: console.info("FontMetrics font="+font+"ascent="+ascent+" descent="+descent+" xHeight="+xHeight+" overlinePosition="+overlinePosition+" underlinePosition="+underlinePosition+" boundingRect("+theText2.text+")="+boundingRect(theText2.text)+" tightBoundingRect("+theText2.text+")="+tightBoundingRect(theText2.text))
                }
                FontMetrics
                {
                    id: fma2
                    font.family: "Arial"
                    font.pixelSize: theText2.fontInfo.pixelSize//83
                    Component.onCompleted: console.info("FontMetrics font="+font+"ascent="+ascent+" descent="+descent+" xHeight="+xHeight+" overlinePosition="+overlinePosition+" underlinePosition="+underlinePosition+" boundingRect("+theText2.text+")="+boundingRect(theText2.text)+" tightBoundingRect("+theText2.text+")="+tightBoundingRect(theText2.text))
                }
                Rectangle
                {
                    id: cloned2
                    color: "red"
                    opacity: 0.2
                    width: tm2.boundingRect.width
                    height: tm2.boundingRect.height
                    ShaderEffectSource
                    {
                        id: anExample2
                        sourceItem: anExample2.target
                        property var target: theText2
                        width: target.width
                        height: target.height
                    }
                    Rectangle
                    {
                        color: "blue"
                        opacity: 0.3
                        x: tm2.tightBoundingRect.x - tm2.boundingRect.x
                        y: tm2.tightBoundingRect.y - tm2.boundingRect.y
                        width: tm2.tightBoundingRect.width
                        height: tm2.tightBoundingRect.height
                    }
                }
                Rectangle
                {
                    color: "transparent"
                    border.color: "brown"
                    border.width: Math.max(2, Math.min(10, width/20))
                    opacity: 0.3
                    anchors.centerIn: parent
                    width: tm2.tightBoundingRect.width
                    height: tm2.tightBoundingRect.height
                }
            }
        }
    }
    


  • Your description and code sample are rather big so I try to answer your question more generally.
    In summary, you want a text to fill it's parent and if one direction is smaller then its parent it should be centered in that direction?
    Does the following do what you want?

    Rectangle {
      Text {
        anchors.fill: parent
        fontSizeMode: Text.Fit
        font.pixelSize: 10000 // maximum height of the font
        minimumPixelSize: 8 // minimum height of the font
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
      }
    }
    

    Cheers!



  • @DuBu said in How to precisely center and adjust the size of any text ?:

    Your description and code sample are rather big so I try to answer your question more generally.

    Sorry, for some reason, I missed your answer.
    The code sample is meant to be cut and pasted in your Qt Creator and just work. You have to get the fonts first in order to reproduce the same images, but using other fonts and characters may also work in demonstrating the problem (the "Cg" example should be easy to reproduce with any font).

    In summary, you want a text to fill it's parent and if one direction is smaller then its parent it should be centered in that direction?

    Yes.

    Does the following do what you want?

    Rectangle {
      Text {
        anchors.fill: parent
        fontSizeMode: Text.Fit
        font.pixelSize: 10000 // maximum height of the font
        minimumPixelSize: 8 // minimum height of the font
        horizontalAlignment: Text.AlignHCenter
        verticalAlignment: Text.AlignVCenter
      }
    }
    

    No.
    I use this kind of parametrization it in the provided code.
    It fails in both aims you put in your summary.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.