MathTran Equation Editor

Introduction

MathTran is a Web service that can be used to obtain images of equations written using the TeX typesetting language. Equations can be embedded into Web pages using a clever JavaScript library that you embed into your pages - this converts the TeX markup written in the src attributes of image elements into requests to the MathTran service.

You can also write requests to the service directly in the src to obtain images in a static Web page, or use the same approach to obtain images in a desktop application. This is exactly what we do in this example.

mathtran-equation-viewer.png

Note: You should read the Terms of Use of the MathTran service before using this example.

Modules

We use the QtCore and QtGui modules to provide the basic infrastructure and user interface for our utility.

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtNetwork import *

Although the Python standard library has facilities for fetching data from Web services, we use the QtNetwork module to communicate with the MathTran server.

The Window Class

The Window class provides both the user interface for the utility and handles all communication with the server using a QHttp instance whose done() signal is connected to the updateForm() method.

class Window(QWidget):

    def __init__(self, parent = None):
    
        QWidget.__init__(self, parent)
        
        self.http = QHttp()
        self.connect(self.http, SIGNAL("done(bool)"), self.updateForm)
        
        self.outputLabel = QLabel()
        self.outputLabel.setFrameShape(QFrame.StyledPanel)
        self.equationEditor = QTextEdit()
        
        buttonBox = QDialogButtonBox()
        self.clearButton = buttonBox.addButton(self.tr("&Clear"),
            QDialogButtonBox.ResetRole)
        self.updateButton = buttonBox.addButton(self.tr("&Update"),
            QDialogButtonBox.ApplyRole)
        
        self.connect(self.clearButton, SIGNAL("clicked()"), self.clearForm)
        self.connect(self.updateButton, SIGNAL("clicked()"), self.fetchImage)

The user interface itself is simple. We use a placeholder QLabel that will be used to display the images obtained from the server, the user enters text into a QTextEdit widget, and a QDialogButtonBox contains two buttons with custom titles. The Clear button is connected to the clearForm() method of the class, allowing the user to clear the current text and image. The Update button is connected to the fetchImage() method, and this is used to request a new image from the server.

        layout = QVBoxLayout()
        layout.addWidget(self.outputLabel, 1)
        layout.addWidget(self.equationEditor)
        layout.addWidget(buttonBox)
        self.setLayout(layout)
        
        self.setWindowTitle(self.tr("MathTran Equation Editor"))

The widgets are arranged vertically, with the label at the top, the text editor in the middle, and the buttons at the bottom. The window title is customized to show the name of the utility.

The clearForm() method simply gives the label an empty (null) pixmap to display; this has the effect of removing the previously displayed image.

    def clearForm(self):
    
        self.outputLabel.setPixmap(QPixmap())
        self.equationEditor.clear()

We clear the equation editor at the same time, allowing the user to enter a new equation.

The fetchImage() method sets up and transmits a new request to the server. This method does not receive the data returned by the server; the updateForm() method will handle the server's response.

Before we do anything else, we first ensure that the user is not able to interact with the parts of the user interface that cause actions to be triggered. We disable the buttons and make the text editor read only:

    def fetchImage(self):
    
        self.clearButton.setEnabled(False)
        self.updateButton.setEnabled(False)
        self.equationEditor.setReadOnly(True)

This ensures that the user cannot send another request while another one is pending.

We need to construct a request to the server in the appropriate format:

    http://mathtran.org/cgi-bin/mathtran?D=<size>;tex=<encoded TeX markup>

The following code constructs a URL using a size of 3 and ensures that the text entered by the user is correctly encoded for use:

        url = QUrl()
        url.setPath("/cgi-bin/mathtran")
        url.setQueryDelimiters("=", ";")
        url.addQueryItem("D", "3")
        url.addQueryItem("tex", str(QUrl.toPercentEncoding(
                         self.equationEditor.toPlainText())))

We use the QHttp object previously created to fetch the data at the URL we specified:

        self.http.setHost("mathtran.org")
        self.http.get(url.toString())

Note that the request is not synchronous - we do not receive the data immediately. When the data is available the QHttp object will emit the done(bool) signal. In the __init__() method, we connected this signal to the updateForm() method for the purpose of handling data returned from any requests we make.

The updateForm() method is called with an argument that indicates whether an error occurred during the transaction with the server.

    def updateForm(self, error):
    
        self.clearButton.setEnabled(True)
        self.updateButton.setEnabled(True)
        self.equationEditor.setReadOnly(False)

        if error:
            return

We re-enable the buttons and make the text editor writable again in any case, and return if an error occurred.

At this point, the server should have returned some data into the QHttp object, which holds it internally. We now extract this data using its readAll() method and attempt to load it into a QImage object, taking advantage of its ability to determine the image format from the data it is given.

        image = QImage()
        if not image.loadFromData(self.http.readAll()):
            return
        
        pixmap = QPixmap.fromImage(image)
        self.outputLabel.setPixmap(pixmap)

If the image data was valid, we convert the image to a pixmap and pass it to the label to display.

Running the Application

We add the standard if statement and suite to create a QApplication object and an instance of our Window class.

if __name__ == "__main__":

    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec_())

Finally, we show the window and start the event loop.

Further Developments

A more flexible version of this example was written for the Creating_GUI_Applications_with_PyQt_and_Qt_Designer talk at PyCon UK 2007.

PyQt/MathTran_Equation_Editor (last edited 2014-06-08 00:09:23 by DavidBoddie)

Unable to edit the page? See the FrontPage for instructions.