Question

It is sad to see that science and math is never given enough attention in Android platform. Maybe this is an easy problem, but I'm a total dude in javascript so I'm struggling to understand how to solve this problem.

What I want to do is simple: I just need to use render mathematic equation. It can be MathJax or JqMath or anything that is small and fast. If I have to do it in a webview, how can I show plain text in paragraph and formatted equation within the same webview?

(anyone comes out with other methods which obviously works, I'll consider that as solution also)

WHAT I'VE DONE SO FAR:

I started with using MathJax to put formula into webview (I don't know if there is any successful jqMath use case out there? Anyone care to share their experience?) I can reproduce all the functionalities just like that of the standalone MathJax App. In that app, a user keys in a string into EditText field and MathJax will render the formula in Latex once user presses a button to confirm.

I don't need user to confirm. I thought this should be easier because there is no user-interaction, but somehow the equation never gets displayed. I know I must be missing something because the codes in the standalone MathJax is meant for user input. Which part of the code should I modify to directly render an equation from a string, say, \sqrt{b^2-4ac} ? Here is the initial header for webview:

WebView w = (WebView) findViewById(R.id.webview);
w.getSettings().setJavaScriptEnabled(true);
w.getSettings().setBuiltInZoomControls(true);
w.loadDataWithBaseURL("http://bar", "<script type='text/x-mathjax-config'>"
                        +"MathJax.Hub.Config({ showMathMenu: false, "
                        +"jax: ['input/TeX','output/HTML-CSS'], "
                        +"extensions: ['tex2jax.js'], " 
                        +"TeX: { extensions: ['AMSmath.js','AMSsymbols.js',"
                        +"'noErrors.js','noUndefined.js'] } "
                        +"});</script>"
                        +"<script type='text/javascript' "
                        +"src='file:///android_asset/MathJax/MathJax.js'"
                        +"></script><span id='math'></span>","text/html","utf-8","");

And here is how the original webview loads when user presses a button after input to edittext:

WebView w = (WebView) findViewById(R.id.webview);
EditText e = (EditText) findViewById(R.id.edit);
w.loadUrl("javascript:document.getElementById('math').innerHTML='\\\\["
                  +doubleEscapeTeX(e.getText().toString())
                  +"\\\\]';");
w.loadUrl("javascript:MathJax.Hub.Queue(['Typeset',MathJax.Hub]);");

private static String doubleEscapeTeX(String s) {
    String t="";
    for (int i=0; i < s.length(); i++) {
        if (s.charAt(i) == '\'') t += '\\';
        if (s.charAt(i) != '\n') t += s.charAt(i);
        if (s.charAt(i) == '\\') t += "\\";
    }
    return t;
}

I attempted to replace with a hard-coded string

w.loadUrl("javascript:document.getElementById('math').innerHTML='\\\\["
                  +doubleEscapeTeX("sample string")
                  +"\\\\]';");

but it doesn't work, the text "sample string" won't even show up in the webview.

[SOLUTION] see Joe's Answer

To elaborate on his answer, this is an example on how to set a sentence in plain text and then display a formatted equation in display form (all in a single webview):

Replace the last line above at

+"></script><span id='math'></span>","text/html","utf-8","");

with

+"></script><span id='text'>This is plain text.</span> <span id='math'></span>", "text/html", "utf-8", "");

Then call

w.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
    super.onPageFinished(view, url);
        if (!url.startsWith("http://bar"))
            return;
        w.loadUrl("javascript:document.getElementById('math').innerHTML='\\\\["
                    +doubleEscapeTeX("\\sqrt{b^2-4ac}") + "\\\\]';");
        w.loadUrl("javascript:MathJax.Hub.Queue(['Typeset',MathJax.Hub]);");
    }
});

This will set a string This is plain text in normal font and center-display the formula foo+bar in webview.

EDIT (Android 4.4 issue)

Starting with Android KitKat, you have to replace all the lines loadUrl(...) with evaluateJavascript(...). But this is only available for KitKat (API 19 or above), so you need to do an SDK version check first. For example:

if (android.os.Build.VERSION.SDK_INT < 19) {
    w.loadUrl("javascript:MathJax.Hub.Queue(['Typeset',MathJax.Hub]);");
} else {
    w.evaluateJavascript("javascript:MathJax.Hub.Queue(['Typeset',MathJax.Hub]);",null);
}

UPDATE (AS OF MATHJAX 2.5)

Because there are folders changes, previous code won't work on latest version of MathJax. Besides, the recommended way to minimalize the size of local MathJax is now using Grunt. The template to reduce size of MathJax can be found here.

Assuming you have successfully reduced the size of MathJax, and assuming output of HTML-CSS, here is a simple version that can load MathJax:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    final WebView w = (WebView) findViewById(R.id.webview);
    w.getSettings().setJavaScriptEnabled(true);
    w.getSettings().setBuiltInZoomControls(true);

    String Url = "</style>"
                 + "<script type='text/x-mathjax-config'>"
                 + "  MathJax.Hub.Config({" + "    showMathMenu: false,"
                 + "             jax: ['input/TeX','output/HTML-CSS', 'output/CommonHTML'],"
                 + "      extensions: ['tex2jax.js','MathMenu.js','MathZoom.js', 'CHTML-preview.js'],"
                 + "         tex2jax: { inlineMath: [ ['$','$'] ], processEscapes: true },"
                 + "             TeX: {" + "               extensions:['AMSmath.js','AMSsymbols.js',"
                 + "                           'noUndefined.js']" + "             }"
                 + "  });"
                 + "</script>"
                 + "<script type='text/javascript' src='file:///android_asset/MathJax/MathJax.js'>"
                 + "</script>"
                 + "<p style=\"line-height:1.5; padding: 16 16\" align=\"justify\">"
                 + "<span >";

    // Demo display equation
    url += "This is a display equation: $$P=\frac{F}{A}$$";

    url += "This is also an identical display equation with different format:\\[P=\\frac{F}{A}\\]";

    // equations aligned at equal sign
    url += "You can also put aligned equations just like Latex:";
    String align = "\\begin{aligned}"
                + "F\\; &= P \\times A \\\\ "
                + "&= 4000 \\times 0.2\\\\"
                + "&= 800\\; \\text{N}\\end{aligned}"; 
    url += align;

    url += "This is an inline equation \\sqrt{b^2-4ac}.";

    // Finally, must enclose the brackets
    url += "</span></p>";

    mWebView.loadDataWithBaseURL("http://bar", url, "text/html", "utf-8", "");
    mWebView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (!url.startsWith("http://bar")) return;
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                view.loadUrl(JAVASCRIPT);
            } else {
                view.evaluateJavascript(JAVASCRIPT, null);
            }
        }
    });
}

Unlike Joes's answer, there is no need to separate text or math types, MathJax will just format them accordingly.

However, it appears that there is a bug that webview in KitKat devices cannot render capital letter "A". Anyone who knows a way is welcome to provide a workaround for this.

Was it helpful?

Solution

If I understand your problem correctly, it seems the issue is due to the MathJax library has not completed loading (from the loadDataWithBaseURL() call) when you call the additional loadUrl()s to display the formula. The simplest fix is to wait for the onPageFinished() callback to make the call.

For example, the following code seems to work fine on mine:

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    final WebView w = (WebView) findViewById(R.id.webview);
    w.getSettings().setJavaScriptEnabled(true);
    w.getSettings().setBuiltInZoomControls(true);
    w.loadDataWithBaseURL("http://bar", "<script type='text/x-mathjax-config'>" 
            + "MathJax.Hub.Config({ "
            + "showMathMenu: false, " 
            + "jax: ['input/TeX','output/HTML-CSS'], " 
            + "extensions: ['tex2jax.js'], "
            + "TeX: { extensions: ['AMSmath.js','AMSsymbols.js'," 
            + "'noErrors.js','noUndefined.js'] } "
            + "});</script>" 
            + "<script type='text/javascript' " 
            + "src='file:///android_asset/MathJax/MathJax.js'"
            + "></script><span id='math'></span>", "text/html", "utf-8", "");
    w.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (!url.startsWith("http://bar")) return;
            w.loadUrl("javascript:document.getElementById('math').innerHTML='\\\\["
                    + doubleEscapeTeX("sample string") + "\\\\]';");
            w.loadUrl("javascript:MathJax.Hub.Queue(['Typeset',MathJax.Hub]);");
        }
    });
}

Update: To accommodate additional output in the WebView, I would suggest adding HTML elements to the initial loadDataWithBaseURL() call. So for the example above, instead of this line at the end:

            + "></script><span id='math'></span>", "text/html", "utf-8", "");

We can do something like the following:

            + "></script><span id='text'>Formula:</span>"
            + "<span id='math'></span>", "text/html", "utf-8", "");

Additionally, if you need to update that part interactively afterward, you can use the same mechanism that we used for the "math" part, something like:

            w.loadUrl("javascript:document.getElementById('text').innerHTML='"
                    + newText + "';");

You can also use other HTML elements (instead of the <span> that we use above) if you want to have specific styling/formatting.

OTHER TIPS

Instead of using javascript and javascript libraries to render the LaTeX client-side, why not do the following?

  1. Write a server-side function that takes some LaTeX string and produces an image file. You can do this with the "out of the box" standard LaTeX command line tool. (It seems like this is something you're capable of doing, and that the problem is in getting the rendering to take place client-side.)

  2. Create an endpoint on your web site/service that looks like the following:
    GET /latex?f={image format}&s={latex string}

  3. On your web pages, use a simple HTML <img/> tag to render your LaTeX. For example:
    <img src="/latex?f=jpeg&s=f%40x%41%61x%942"/>
    will render the f(x)=x^2 equation as a JPEG.

  4. (optional) Create a simple server-side cache for (f,s) pairs so that you only render a particular LaTeX string on the first request ever made for that particular string (by any client). As a first prototype, you could just have a server directory in which you have files named {hash(s)}.{f}, where hash is just some hash function like SHA1.

This relieves you from trying to make everything work client-side with javascript. And, I would argue (although some may disagree), that this is a lot cleaner. All you're sending to the client is:

  1. the HTML <img/> tag
  2. the actual image contents when the browser resolves the src attribute

Very lightweight and fast!

The "capital A" problem is a konwn bug of MathJax on Android WebView. You can replace the font files with tweaked font files. The fix will work when using HTML-CSS output with TeX-fonts. Besides MathJax, you can try KaTeX, which is less beautiful but faster.

Actually I've packed them both into an Android custom view, have a look on MathView.

Late to the party but I think I have a better solution than Joe's. The original question asked:

how can I show plain text in paragraph and a formatted equation within the same webview?

In the original example we had:

w.loadUrl("javascript:document.getElementById('math').innerHTML='\\\\["
                  +doubleEscapeTeX("sample string")
                  +"\\\\]';");

Do you notice the \\\\[ and \\\\]? Those four backslashes are interpreted by LaTeX as only one, and \[ and \] in LaTeX is a shorthand for \begin{equation}... \end{equation} which produces a block of displayed mathematics. If we want a paragraph of text with some equations within the paragraph we need to get rid of the \\\\[ and use the LaTeX command \( and \). The code for an example with paragraph mathematics and displayed mathematics would be:

w.loadUrl("javascript:document.getElementById('math').innerHTML='"
   +doubleEscapeTeX(
           "This is a second degree equation \\( \\small ax^2+bx+c=0\\) and its "
          +"roots are \\[x=\\frac{-b\\pm \\sqrt{b^2-4ac}}{2a}. \\]"
    )+"';");

The WebView becomes: enter image description here Notes:

  • We only need two backslashes instead of four, this is because we are inside doubleEscapeTeX() and this function duplicates the backslashes.

  • Also the text outside the math environments is rendered with HTML (inside the <span id='math'> tag).

  • The LaTeX font is bigger that the html font, so adding a \\small in each math environment will make them roughly the same size. Here I added the \\small command only to the first equation to show the difference.

jqMath is a JavaScript library like MathJax, but much smaller, simpler, and faster. If you don't need all of LaTeX, you might like it. It should work fine on Android.

It is just a suggestion, I have no idea what errors or advantages you might have if you use MathJax.

MathJax is much bigger and more complicated than jqMath, and roughly 5 times slower.

Working JSFIDDLE: http://jsfiddle.net/sinhayash/gWWB5/1/

For \sqrt{b^2-4ac} you can have this code: √(b^2-4ac)

You can easily use string handling in javascript to convert strings to the jqMath format and easily display it

You can easily use math formula in android using below code.

public void setView(String data,WebView w){
    WebSettings webSettings = w.getSettings();
    webSettings.setJavaScriptEnabled(true);
    String path="file:///android_asset/v/";
    String js="<html><head>"
       + "<link rel='stylesheet' href='file:///android_asset/v/jqmath.css'>"
       + "<script src = 'file:///android_asset/v/jquery-3.0.0.min.js'></script>"
       + "<script src = 'file:///android_asset/v/jqmath-etc-0.4.3.min.js'></script>"
       + "<script src = 'http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML'></script>"
       + "</head><body>"
       + "<script>var s ='"+data+"';M.parseMath(s);document.write(s);</script> </body>";
    w.loadDataWithBaseURL("", js, "text/html", "UTF-8", "");
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top