Search code examples
htmlqtqmlhref

how to jump to a href hash tag location in a large Qt/QML text item with RichText textFormat


I use Qt/QML Text Item to render a large standalone HTML page with some inner href hash links. and the container height is less than its content height. Everything works fine; I can click the link and see the console log, but I cannot jump the text item's visual position to the href tag position. please help.

The y property of the text item in the container should have been updated when I clicked the href link.

To simplify my question, let me give a minimal reproducible example here.

import QtQuick

Window {
    id: root
    width: 640
    height: 480
    visible: true

    Rectangle {
        id:container
        height: root.height*.95
        width: root.width*.8
        anchors.centerIn: parent
        Text {
            id: label
            width: container.width
            textFormat: Text.RichText
            wrapMode:Text.WordWrap

            onLinkActivated: function(link){
                console.log(link)
            }
            TapHandler {
                onTapped:function(eventPoint, button){
                    ani.running = !ani.running
                }
            }
            SequentialAnimation on y {
                id: ani
                loops: Animation.Infinite
                running: true
                PropertyAnimation { to: -label.contentHeight/100;duration: 3000 }
                PropertyAnimation { to: 0 ;duration: 3000}
            }
        }
    }
    function makeRequest()
    {
        var doc = new XMLHttpRequest();
        label.text = "";
        doc.onreadystatechange = function() {
            if (doc.readyState === XMLHttpRequest.DONE) {
                label.text = doc.responseText;
            }
        }
        doc.open("GET", "https://www.mpfr.org/mpfr-current/mpfr.html");
        doc.send();
    }
    Component.onCompleted: {
        // label.text = backEnd.loadFile("C:/ebook/GNU MPFR 4.2.1.htm")
        makeRequest()
    }
}

and the HTML file can be manually downloaded from the URL link. Now I want to click the blue link, expecting the text item to scroll itself to the exact tag location.

app shuold be like this


Solution

  • The Text component lacks the ability to retrieve the coordinates of a specified text position. However, you can use TextEdit, which includes a positionToRectangle method that returns a QRect for the given text position.

    The challenge is that the text position must correspond to plain text, excluding any HTML or XML tags. Therefore, you need to locate the position of the target tag within the formatted text and then map that position to the plain text in the TextEdit. Since there is no built-in function to accomplish this, I created a helper component to determine the position of the target tag within the plain text.

    * Another point I forgot to mention is that the <h1 id="h1">h1</h1> will be converted to <h1><a name="h1"></a> after the TextEdit initialization. Therefore, the search should be for the <a name="${link}"></a>.

    ScrollView {
        id: scrollView
        contentWidth: width
        contentHeight: textedit.implicitHeight
    
        function textgen() {
            return `Table of content:<ul id="top"><li><a href="#h1">h1</a></li><li>
                <a href="#h2">h2</a></li><li><a href="#h3">h3</a></li><li><a href="#h4">h4</a></li></ul>
                <h1 id="h1">h1<sub><a href="#top">top</a></sub></h1>${'sss '.repeat(200)}
                <h1 id="h2">h2<sub><a href="#top">top</a></sub></h1>${'sss '.repeat(200)}
                <h1 id="h3">h3<sub><a href="#top">top</a></sub></h1>${'sss '.repeat(200)}
                <h1 id="h4">h4<sub><a href="#top">top</a></sub></h1>${'sss '.repeat(200)}`;
        }
    
        NumberAnimation {
            id: animateScroll
    
            target: scrollView.ScrollBar.vertical
            property: 'position'
            duration: 1000
            easing.type: Easing.InOutCubic
        }
    
        TextEdit {
            id: textedit
    
            property TextEdit helper: TextEdit { textFormat: Text.RichText }
    
            width: root.width
            text: scrollView.textgen()
            readOnly: true
            wrapMode:Text.WordWrap
            textFormat: Text.RichText
            selectByMouse: false
            
            HoverHandler { cursorShape: textedit.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor }
            
            onLinkActivated: link => {
                if(link.startsWith('#')) {
                    let idx = text.match(`<a name="${link.slice(1)}"></a>`)?.index ?? -1;
    
                    if(idx !== -1) {
                        // Copying the first slice from the beginning to the target index into the helper TextEdit
                        helper.text = text.slice(0, idx);
                        // The `helper.length` will represent the target position within the plain text.
                        const rect = positionToRectangle(helper.length);
    
                        animateScroll.to = rect.y/textedit.height;
                        animateScroll.restart();
                    }
                }
            }
        }
    }