Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Display (LaTeX) math expressions inline

I would like to display math terms inside a text, in particular in an inline mode, i.e. inside a sentence.

Using LaTeX, this would for example look like: "Given a right triangle having catheti of length \(a\) resp. \(b\) and a hypotenuse of length \(c\), we have \[a^2 + b^2 = c^2.\] This fact is known as the Pythagorean theorem."

Does anybody know how this can be achieved in Swift?

(I know that this example may be achieved in Swift without LaTeX-like tools. However, the expressions in my mind are in fact more complex than in this example, I do need the power of LaTeX.)

The optimal way would be a UITextView-like class which recognizes the math delimiters \(,\) resp. \[,\], recognizes LaTeX code inside these delimiters, and formats the text accordingly.

In the Khan Academy app, this problem seems to be solved as the screenshots in the Apple App Store/Google Play Store show inline (LaTeX) math.

I’ve found the package iosMath which provides a UILabel-like class MTMathUILabel. As this class can display solely formulas, this seems to be not good enough for my purpose, except if there was a method which takes a LaTeX source text such as in the example above, formats expressions such as \(a\) into tiny MTMathUILabels and sets these labels between the other text components. As I am new to Swift, I do not know whether and how this can be achieved. Moreover, this seems to be very difficult from a typographical point of view as there will surely occur difficulties with line breaks. And there might occur performance issues if there are a large number of such labels on the screen at the same time?

It is possible to achieve what I want using a WKWebView and MathJax or KaTeX, which is also a hack, of course. This leads to other difficulties, e.g. if one wants to set several of these WKWebViews on a screen, e.g. inside UITableViewCells.

like image 727
Sebastian Thomas Avatar asked Dec 30 '18 13:12

Sebastian Thomas


1 Answers

enter image description here Using iosMath, my solution on how to get a UILabel to have inline LaTeX is to include LATEX and ENDLATEX markers with no space. I replaced all ranges with an image of the MTMathUILabel, going from last range to first range so the positions don't get screwed up (This solution allows for multiple markers). The image returned from my function is flipped so i used .downMirrored orientation, and i sized it to fit my text, so you might need to fix the numbers a little for the flip scale of 2.5 and the y value for the attachment.bounds.

import UIKit
import iosMath

let question = UILabel()
let currentQuestion = "Given a right triangle having catheti of length LATEX(a)ENDLATEX resp. LATEX(b)ENDLATEX and a hypotenuse of length LATEX(c)ENDLATEX, we have LATEX[a^2 + b^2 = c^2]ENDLATEX. This fact is known as the Pythagorean theorem."
question.text = currentQuestion

if (question.text?.contains("LATEX"))! {
        let tempString = question.text!
        let tempMutableString = NSMutableAttributedString(string: tempString)
        let pattern = NSRegularExpression.escapedPattern(for: "LATEX")
        let regex = try? NSRegularExpression(pattern: pattern, options: [])
        if let matches = regex?.matches(in: tempString, options: [], range: NSRange(location: 0, length: tempString.count)) {
               var i = 0
               while i < matches.count {
                    let range1 = matches.reversed()[i+1].range
                    let range2 = matches.reversed()[i].range
                    let finalDistance = range2.location - range1.location + 5
                    let finalRange = NSRange(location: range1.location, length: finalDistance)
                    let startIndex = String.Index(utf16Offset: range1.location + 5, in: tempString)
                    let endIndex = String.Index(utf16Offset: range2.location - 3, in: tempString)
                    let substring = String(tempString[startIndex..<endIndex])
                    var image = UIImage()
                    image = imageWithLabel(string: substring)
                    let flip = UIImage(cgImage: image.cgImage!, scale: 2.5, orientation: .downMirrored)
                    let attachment = NSTextAttachment()
                    attachment.image = flip
                    attachment.bounds = CGRect(x: 0, y: -flip.size.height/2 + 10, width: flip.size.width, height: flip.size.height)
                    let replacement = NSAttributedString(attachment: attachment)
                    tempMutableString.replaceCharacters(in: finalRange, with: replacement)
                    question.attributedText = tempMutableString
                    i += 2
               }
        }
}

func imageWithLabel(string: String) -> UIImage {
        let label = MTMathUILabel()
        label.latex = string
        label.sizeToFit()
        UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
        defer { UIGraphicsEndImageContext() }
        label.layer.render(in: UIGraphicsGetCurrentContext()!)
        return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
}
like image 123
Michael Montalbano Avatar answered Oct 22 '22 09:10

Michael Montalbano