QML Text sometimes skips rendering some characters

275 Views Asked by At

I have an application where I display the time on the screen. It works well except very rarely it sometimes does not render the time fully and skips rendering a number. For example if time is "11:07", it would render "11:0 " or "1 :07". I know it's a rendering problem because it fixes itself when the time changes and I can't recreate it by force setting the same time. Also I have the time in another screen in much smaller font, and I never see this issue there. I suspect it's something to do with rendering text in a large fontSize that causes this problem.

I tried creating the duplicate of my time text and using TextMetrics for both time text elements and compare their widths. If their widths are different, I assume that it did not render fully. I don't think this would actually work tho since it seems like the width of the elements would be the same, even if they don't render fully. I looked into SafeText but that does not work for my constraints. I am using NativeRendering since my font size is big (> 200px). Here is a MRE:

import QtQuick 2.15
import QtQuick.Window 2.2
import QtQuick.Controls 2.15

Window {

    width: 1024
    height: 600
    visible: true
    property string time: Qt.formatTime(new Date(),"hh:mm")

    onTimeChanged: {
        if(timeText.width !== timeTextDuplicate.width || timeMetric.width !== timeMetricDuplicate.width) {
            console.log("They are not the same")
            // redraw time
            time = ""
            time = Qt.formatTime(new Date(),"hh:mm")
        }
    }

    Rectangle {
        id: root
        anchors.fill: parent
        color: "black"

        Text {
            id: timeText
            anchors.centerIn: parent
            font.pixelSize: 200
            color: "white"
            text: time
        }

        TextMetrics {
            id: timeMetric
            text: timeText.text
            font: timeText.font
        }

        Text {
            id: timeTextDuplicate
            font: timeText.font
            text: timeText.text
            visible: false
        }

        TextMetrics {
            id: timeMetricDuplicate
            text: timeTextDuplicate.text
            font: timeTextDuplicate.font
        }

        Timer {
            id: timer
            interval: 60000
            repeat: true
            running: true
            onTriggered: time = Qt.formatTime(new Date(),"hh:mm")
        }
    }
}

This is the output I get after setting QSG_RENDERER_DEBUG=render

Renderer::render() QSGAbstractRenderer(0x7f52702a63d0) "rebuild: full"
Rendering:
 -> Opaque: 1 nodes in 1 batches...
 -> Alpha: 6 nodes in 3 batches...
 - 0x7f52702be8a0 [  upload] [noclip] [opaque] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0
 - 0x7f52702be710 [  upload] [noclip] [ alpha] [  merged]  Nodes:    4  Vertices:    56  Indices:    84  root: 0x0 opacity: 1
 - 0x7f52702be240 [  upload] [noclip] [ alpha] [unmerged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0
 - 0x7f52702bdd70 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 1
 -> times: build: 0, prepare(opaque/alpha): 0/0, sorting: 0, upload(opaque/alpha): 0/0, render: 0
Renderer::render() QSGAbstractRenderer(0x7f5270006190) "rebuild: full"
Rendering:
 -> Opaque: 2 nodes in 1 batches...
 -> Alpha: 19 nodes in 17 batches...
 - 0x7f52700d50a0 [  upload] [noclip] [opaque] [  merged]  Nodes:    2  Vertices:     8  Indices:    12  root: 0x0
 - 0x7f52700d18a0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 0.2
 - 0x7f52702c5ad0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:   152  Indices:   232  root: 0x0 opacity: 0.2
 - 0x7f52702c59e0 [  upload] [noclip] [ alpha] [unmerged]  Nodes:    2  Vertices:   184  Indices:   280  root: 0x0
 - 0x7f52702c62c0 [  upload] [noclip] [ alpha] [unmerged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0
 - 0x7f52702fdc90 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:    32  Indices:    48  root: 0x0 opacity: 1
 - 0x7f527030db10 [  upload] [noclip] [ alpha] [unmerged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0
 - 0x7f52702c6040 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:    96  Indices:   144  root: 0x0 opacity: 1
 - 0x7f52702c5dc0 [  upload] [noclip] [ alpha] [unmerged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0
 - 0x7f52700d5360 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:    84  Indices:   126  root: 0x0 opacity: 1
 - 0x7f52700c8150 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 1
 - 0x7f52702f0730 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 1
 - 0x7f52702c0ce0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:    16  Indices:    24  root: 0x0 opacity: 1
 - 0x7f52702c5f00 [  upload] [noclip] [ alpha] [  merged]  Nodes:    2  Vertices:   184  Indices:   284  root: 0x0 opacity: 1
 - 0x7f52702c5cb0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 1
 - 0x7f52702c5bc0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     8  Indices:    12  root: 0x0 opacity: 1
 - 0x7f52702c5800 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:     4  Indices:     6  root: 0x0 opacity: 1
 - 0x7f52702c58f0 [  upload] [noclip] [ alpha] [  merged]  Nodes:    1  Vertices:    48  Indices:    72  root: 0x0 opacity: 1
 -> times: build: 0, prepare(opaque/alpha): 0/0, sorting: 0, upload(opaque/alpha): 0/0, render: 0

I understand this could be a problem with the graphics card or QML itself, but is there anything I could do to detect that this has happened? Thanks

1

There are 1 best solutions below

6
Stephen Quan On

I use TextMetrics slightly differently. I built a BigText component that chooses the largest value for font.pixelSize so that your text will fit but will not exceed the bounding rectangle. It does this by:

  1. Looking at the width and height of the bounding rectangle
  2. Looking at the TextMetrics width and height for a "small" pixel size
  3. Calculate a scale factor to multiply the "small" pixel size so that the text will fit nicely in the bounding rectangle

The other point I would like to make is in your code, you set TextMetrics font as follows:

        TextMetrics { // ...
            font: timeText.font
        }

I have found this can screw things up as you're no longer using the original font that came with TextMetrics. I have found it safer to copy the font properties you need, e.g.

        TextMetrics { // ...
            font.family: timeText.font.family
        }

To see these concepts in in action, I changed your application by (1) increasing the frequency of your timer from 1 minute to 1 second. Change the time format from "hh:mm" to "hh:mm:ss".

Whilst running this app, try resizing the window and note the text font dynamically changes.

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
Page {
    id: page
    property double time: Date.now()
    //property string timeString: Qt.formatDateTime(new Date(time), "hh:mm")
    property string timeString: Qt.formatDateTime(new Date(time), "hh:mm:ss")
    ColumnLayout {
        anchors.centerIn: parent
        BigText {
            backgroundColor: "black"
            color: "white"
            Layout.alignment: Qt.AlignHCenter
            Layout.preferredWidth: page.width / 2
            Layout.preferredHeight: page.height / 2
            text: timeString
        }
        RowLayout {
            Layout.alignment: Qt.AlignHCenter
            Button {
                Layout.preferredWidth: height
                text: "-"
                //onClicked: slider.value -= 60000
                onClicked: slider.value -= 1000
            }
            Slider {
                id: slider
                Layout.fillWidth: true
                from: hhmm(0, 0, 0)
                to: hhmm(23, 59, 59)
                value: Date.now()
                function hhmm(hh, mm, ss) {
                    let d = new Date();
                    d.setHours(hh);
                    d.setMinutes(mm);
                    d.setSeconds(ss);
                    return d.getTime();
                }
                onValueChanged: {
                    autoButton.checked = false;
                    time = value;
                }
            }
            Button {
                Layout.preferredWidth: height
                text: "+"
                //onClicked: slider.value += 60000
                onClicked: slider.value += 1000
            }
        }
        Button {
            id: autoButton
            Layout.alignment: Qt.AlignHCenter
            text: qsTr("Auto")
            checkable: true
            Component.onCompleted: checked = true
        }
    }
    Timer {
        id: timer
        interval: 1000
        repeat: true
        running: autoButton.checked
        onTriggered: time = Date.now()
    }
}

// BigText.qml
import QtQuick
import QtQuick.Controls
Text {
    id: bigText
    width: 200
    height: 100
    color: "black"
    horizontalAlignment: Text.AlignHCenter
    verticalAlignment: Text.AlignVCenter
    property color backgroundColor: "white"
    Rectangle {
        anchors.fill: parent
        color: backgroundColor
        z: -1
    }
    property TextMetrics tm: TextMetrics {
        font.pixelSize: 10
        font.family: bigText.font.family
        text: bigText.text
    }
    font.pixelSize: tm.font.pixelSize * Math.min(bigText.width / tm.width, bigText.height / tm.height) * 0.9
}

You can Try it Online!