Search code examples
pythonpyqtequationmathjax

MathJax flickering and statusbar showing in PyQt5


for a school project, we were supposed to make a simple calculator. The project's goal was to teach github, teamwork, testing, documentation, and all that jazz. It is not really about the calculator. However, my team and I decided to take it a step further and we wanted to create a calculator that displays mathematical expressions in real time.

I managed to find a way: using PyQt5 and its QWebEngineView, I was able to display a simple webpage, and, using javascript, I was able to display an equation in real time using MathJax. However, there are some problems:

Upon entering any number, first, a statusbar appears (for a split second) on the lower part of the display like this:

enter image description here

Bu that is not all - before the final number appears, first, a raw latex code is shown for a fraction of a second, then a HTMLPreview version of the fraction, and only after all this does the final render appear.

This is the python x javascript part:

from PyQt5.QtWidgets import *
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtGui import QFont
import sys
import time
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWebEngineWidgets import QWebEngineView

class Ui_s_cals(object):
    def __init__(self):
        self.content=["10","+","10"]

    def setupUi(self, s_cals):
        s_cals.setObjectName("s_cals")
        s_cals.resize(300, 500)
        s_cals.setWindowTitle("Calculator")

        self.display = QWebEngineView(s_cals)
        self.display.setGeometry(QtCore.QRect(20, 10, 300, 60))
        self.pushButton_n0 = QtWidgets.QPushButton(s_cals)
        self.pushButton_n0.setGeometry(QtCore.QRect(80, 400, 61, 61))
        self.pushButton_n0.setObjectName("pushButton_n0")
        self.pushButton_n0.setText("0")
        self.pushButton_n0.clicked.connect(lambda:self.print("0"))

    def print(self,number):
        self.content.append(number)
        print(self.content)
        #self.output1.setText(''.join(self.content))
        temp=''.join(self.content)
        self.expression=temp
        self.pageSource = '''
                                <html><head>
                                <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML">                     
                                </script></script><script>MathJax.Hub.Config({{
jax: ["input/TeX","output/HTML-CSS"],
displayAlign: "left"
}});</script>
                                </head>
                                <body>
                                <p><mathjax style="font-size:1em">$${expression}$$</mathjax></p>
                                </body></html>
                                '''.format(expression=self.expression)
        self.display.setHtml(self.pageSource)

class Main(QMainWindow):
    def __init__(self):
        super().__init__()
        self.ui = Ui_s_cals()
        self.ui.setupUi(self)
        self.show()

def main():
    calc = QApplication(sys.argv)
    instance = Main()
    sys.exit(calc.exec_())
if __name__ == '__main__':
    start = time.time()
    main()
    print(time.time() - start)

I was looking for answers for hours. I tried setting the visibility of body to hidden, and then making it visible by a script after mathjax is done rendering, but it didn't work.

I tried all the answers on stack overflow, but none of them counted on me using this inside a python script, which might be the problem. if you know a different way of showing equations in real time.


Solution

  • You don't need to change whole page content to render new expression, instead you can use QWebEnginePage.runJavaScript method. Look at input-tex2chtml.html example in MathJax-demos-web

    Demo:

    from PyQt5 import QtCore, QtGui, QtWidgets
    from PyQt5.QtWebEngineWidgets import QWebEngineView
    
    html = """
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta http-equiv="x-ua-compatible" content="ie=edge">
      <meta name="viewport" content="width=device-width">
      <title>MathJax v3 with interactive TeX input and HTML output</title>
      <script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
      <script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"></script>
      <script>
        function convert(input) {
          output = document.getElementById('output');
          output.innerHTML = '';
          MathJax.texReset();
          var options = MathJax.getMetricsFor(output);
          options.display = true;
          MathJax.tex2chtmlPromise(input, options).then(function (node) {
            output.appendChild(node);
            MathJax.startup.document.clear();
            MathJax.startup.document.updateDocument();
          }).catch(function (err) {
            output.appendChild(document.createTextNode(err.message));
          });
        }
      </script>
      <style>
      body, html {
        padding: 0;
        margin: 0;
      }
      #output {
        font-size: 120%;
        min-height: 2em;
        padding: 0;
        margin: 0;
      }
      .left {
        float: left;
      }
      .right {
        float: right;
      }
      </style>
    </head>
    <body>
    <div id="output" class="left"></div>
    </body>
    </html>
    """
    
    class Window_Ui:
        def setupUi(self, widget):
            view = QWebEngineView()
            edit = QtWidgets.QLineEdit()
            layout = QtWidgets.QVBoxLayout()
            layout.addWidget(view)
            layout.addWidget(edit)
            widget.setLayout(layout)
            self.view = view
            self.edit = edit
    
    class Window(QtWidgets.QWidget):
        def __init__(self, parent = None):
            super().__init__(parent)
            ui = Window_Ui()
            ui.setupUi(self)
            self._ui = ui
            ui.view.setHtml(html)
            page = ui.view.page()
            page.loadFinished.connect(self.onLoadFinished)
            ui.edit.setText("{-b \\pm \\sqrt{b^2-4ac} \\over 2a}")
            ui.edit.textChanged.connect(self.onTextChanged)
            self._ready = False
    
        def onLoadFinished(self):
            if self._ready:
                return
            self._ready = True
            self.onTextChanged(self._ui.edit.text())
    
        def onTextChanged(self, text):
            ui = self._ui
            text = text.replace("\\","\\\\")
            page = ui.view.page()
            page.runJavaScript('convert("{}");'.format(text))
    
        def sizeHint(self):
            return QtCore.QSize(300,150)
    
    
    if __name__ == "__main__":
        app = QtWidgets.QApplication([])
        window = Window()
        window.show()
        app.exec()