qmllabelqtquick2

Is there a way to make the text characters inside a Label not have a space underneath?


In a QML code, I want to put a label containing capitalised words inside an image. The label is at the bottom of the image and I want the bottom of the text to match with the edge of the image (for example, like the graph paper from a diary). By making the bottom of both match with

anchors.bottom: myImage.bottom 

I can't avoid having some space between the letters and the edge of the image.

I have simplified it into an example as follows:

Rectangle {
    id: content
    width: 300
    height: 300
    color: "blue"
    Label {
        text: "ABC"
        color: "black"
        font.pixelSize: 200
        anchors.bottom: content.bottom
        anchors.horizontalCenter: content.horizontalCenter
        background: Rectangle{
            color: "green"
        }
    }
}

And I can see that there is a green space between the letters and the bottom of the blue rectangle.

Is there a simple way to fix this other than messing with the value of anchors.bottomMargin?


Solution

  • Based on the documentation, there are four vertical anchor lines for every visible Item: top, verticalCenter, baseline, and bottom. If the component is not a Text component, the baseline is the same as the top anchor. [1]

    For a Text component, assuming a standard font and the initial size of the Text, the baseline is where the text sits, with characters like 'A' and 'B', placed exactly on top of the baseline. The baseline is illustrated in the below image.

    The Answer

    The "empty space" in the OP's question is actually the space between the baseline and the bottom of the Text item, which can be used for anchoring. As shown in the following code:

    Label {
        anchors.baseline: parent.bottom
    }
    

    The baseline position can also be retrieved from baselineOffset, which can also be used for positioning the text.

    anchor lines

    This code generates the image above.

    Label {
        id: label
        text: 'AxyÁ'
        font.family: 'Times New Roman'
        font.pixelSize: 100
        background: Rectangle { color: '#fed' }
    
        Repeater {
            model: ['top','baseline','verticalCenter','bottom']
            Rectangle {
                anchors.top: parent[modelData]
                height: 1; width: parent.width; color: Qt.hsva(index/4,.5,.8)
                Text {
                    color: parent.color; text: modelData; padding: 5;
                    anchors { left: parent.right; baseline: parent.top }
                }
            }
        }
    }
    

    Details

    But it doesn’t end there. This approach will likely clip some glyphs of your Text, such as 'g', 'y', and 'p', where parts of the characters extend below the baseline.

    To fix this, we can use states to change the anchors based on characters that might extend below the baseline. The following code checks the text for the presence of yqpjg, and if any of these characters are found, it adjusts the anchor accordingly.

    Rectangle {
        color: '#fed'
        Text {
            id: label
            text: 'ABC'
            font.pixelSize: 100
            anchors.horizontalCenter: parent.horizontalCenter
            states: [
                State { when: !label.text.match('[yqpjg]');
                    AnchorChanges { target: label; anchors.baseline: parent.bottom } },
                State { when: label.text.match('[yqpjg]');
                    AnchorChanges { target: label; anchors.bottom: parent.bottom } }
            ]
            //** Or just use the baselineOffset. **
            // x: (parent.width - width)/2
            // y: parent.height - (label.text.match('[yqpjg]') ? height : baselineOffset)
        }
    }
    
    Metrics

    As mentioned, the appearance of a character can vary depending on the font. Some fonts may add extra space below characters like 'g', 'y', etc. The recommended lowest position for a glyph, known as the descent, can also differ from the bottom of the glyph or its height.

    In QML, we can access these values using FontMetrics.

    The following image demonstrates some of these values within a Label.

    metrics lines

    This code generates the image above.

    Label {
        id: label
        text: 'AxyÁ'
        font.family: 'Times New Roman'
        font.pixelSize: 100
        background: Rectangle { color: '#fed' }
        FontMetrics { id: metrics; font: label.font }
        Repeater {
            model: [
                ['overlinePosition', label.baselineOffset - metrics.overlinePosition],
                ['\nascent', label.baselineOffset - metrics.ascent],
                ['baseline', label.baselineOffset],
                ['xHeight', label.baselineOffset - metrics.xHeight],
                ['strikeOutPosition', label.baselineOffset - metrics.strikeOutPosition],
                ['\nunderlinePosition', label.baselineOffset + metrics.underlinePosition],
                ['descent', metrics.descent + label.baselineOffset],
                ['\nheight', metrics.height],
            ]
            Rectangle {
                y: modelData[1] ?? 0
                height: 0.5; width: parent.width; color: Qt.hsva(index/7,1,.7,1)
                Text {
                    color: parent.color; text: modelData[0]; padding: 5;
                    anchors { left: parent.right; verticalCenter: parent.top }
                }
            }
        }
    }
    

    As you can see, the ascent is slightly lower than the top position. In this example, I used Times New Roman, where the descent matches the glyph's height, though the descent can sometimes be higher than the bottom of the Text.

    In conclusion, if you need simple alignment, anchors are sufficient. However, for more advanced control over text positioning, you should use FontMetrics.