Sunday, 26 February 2012

Tutorial - How to setup MathJax locally

[Update 02/03/2012] Please also have a look at this post - Tutorial - How to setup MathJax locally - "slim" edition

In this post last year, I talked about how to get MathJax working in your iPhone project. However that requires internet connection as the JavaScript in MathJax needs to connect back to their web site to get all the fonts and details, otherwise all those math formulas won't be displayed properly.

In this post I am going to talk about how I setup my iPhone/iPad project to use MathJax "locally" so that your math formulas can be displayed properly even if there's no Internet connection.

Before you start

A few things you have to be aware of:
1) Nothing is free, to provide this convenience, you have to add the 25+MB MathJax library into your Xcode project
2) As your final application will be over the 20MB limit, your users/customers won't be able to download it directly from the iPhone/iPad device, they have to connect it to a PC/Mac to down your application and future updates.
3) From my observation, I noticed when the JavaScript runs, it still tries to connect to their web site, and then it fall back to use the local copy (as shown in screen dumps below: 1st one says "Loading Web-Font TeX/Main/Regular", 2nd one says "Web-Fonts not available -- using image fonts instead").

If this is not what you expected, and you still want to use MathJax in your application, may be you should just follow the example in the previous MathJax post.

How was it done?

At a high level - using the same technique mentioned in the MathJax post last year, we will first write a HTML file locally and then display it using UIWebView. But the difference this time, is we will change the reference to the JavaScript to point to local directory, so it can fall back locally if internet connection is not available for whatever reason.

If you look at any examples from the MathJax web site (for example: this one, or this one) and view source, you will see this link somewhere in the page.

<script type="text/javascript" src="http://cdn.mathjax.org/mathjax/2.0-beta/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
</head>

This JavaScript is the main component that makes the whole thing works. Our task is to change the link for it to point to the local copy of MathJax library, that is, change the part highlighted in red.

Steps Involved

1. Start a new project with single view, I am using the name "MathJaxLocal".

3. Expand the downloaded .ZIP file, to make it easier to differentiate between different version, I renamed the folder name to "mathjax-MathJax-v1.1a".

4. Drag and drop the whole "mathjax-MathJax-v1.1a" folder into your Xcode project. At the popup screen as shown below, make sure you click "Copy Items into destination group's folder (if needed)" and  "Create folder reference for any added folders". Otherwise the folder structure won't be created properly.

Please note that it's quite big and might take a few minutes depending on how powerful your machine is, so please be patient.

5. Once it's done, you should see as below a special blue coloured folder with all the MathJax files copied into your project. Feel free to expand it and explore around.

6. Now let's start changing the code, first add a UIWebView declaration. If you are lazy like me, just stick it somewhere in the beginning of your UIViewController file.

UIWebView *myWebView;

7. Next, let's add a new method, which will handle writing HTML file locally, I called it "writeStringToFile". It basically concatenates a few strings into a big one and then write to the specified path with specified filename.

-(void)writeStringToFile:(NSString *)dir fileName:(NSString *)strFileName pathName:(NSString *)strPath content:(NSString *)strContent{

NSLog(@" inside writeStringToFile, strPath=%@", strPath);

NSString *path = [dir stringByAppendingPathComponent:strFileName];

NSString *foo0 = @"<html><head><meta name='viewport' content='initial-scale=1.0' />"
"<script type='text/javascript' src='";

NSString *foo1 = @"?config=TeX-AMS-MML_HTMLorMML-full'></script>"
"<body>";
NSString *foo2 = @"</body></html>";
NSString *fooFinal = [NSString stringWithFormat:@"%@%@%@%@%@",foo0,strPath,foo1,strContent,foo2];

NSLog(@"Final content is %@",fooFinal);

[fooFinal writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:nil];

}

8.  Finally add the following code into your "viewDidLoad" or whichever method which you think appropriate. Note that I copied 2 examples from their web site, you can change it to see the differences. 1st one is for "TEX" and there's more work as you have to change all the single slash "\" to double slashes "\\", the 2nd one is for "MathML"

//content copied from http://www.mathjax.org/demos/tex-samples/
NSString *xContent = @"<p>\$" "\\left( \\sum_{k=1}^n a_k b_k \\right)^{\\!\\!2} \\leq" "\\left( \\sum_{k=1}^n a_k^2 \\right) \\left( \\sum_{k=1}^n b_k^2 \\right)" "\$</p>"
"<BR/>"
"<p>\$" "\\frac{1}{(\\sqrt{\\phi \\sqrt{5}}-\\phi) e^{\\frac25 \\pi}} =" "1+\\frac{e^{-2\\pi}} {1+\\frac{e^{-4\\pi}} {1+\\frac{e^{-6\\pi}}" "|{1+\\frac{e^{-8\\pi}} {1+\\ldots} } } }" "\$</p>";

/*
NSString *xContent =@"When $<mi>a</mi><mo>&#x2260;</mo><mn>0</mn>$,"
"there are two solutions to $" "<mi>a</mi><msup><mi>x</mi><mn>2</mn></msup>" "<mo>+</mo> <mi>b</mi><mi>x</mi>" "<mo>+</mo> <mi>c</mi> <mo>=</mo> <mn>0</mn>" "$ and they are"
"<math mode='display'>"
"<mi>x</mi> <mo>=</mo>"
"<mrow>"
"<mfrac>"
"<mrow>"
"<mo>&#x2212;</mo>"
"<mi>b</mi>"
"<mo>&#x00B1;</mo>"
"<msqrt>"
"<msup><mi>b</mi><mn>2</mn></msup>"
"<mo>&#x2212;</mo>"
"<mn>4</mn><mi>a</mi><mi>c</mi>"
"</msqrt>"
"</mrow>"
"<mrow> <mn>2</mn><mi>a</mi> </mrow>"
"</mfrac>"
"</mrow>"
"<mtext>.</mtext>"
"[/itex]";
*/

//temp file filename
NSString *tmpFileName = @"test1.html";

//temp dir
NSString *tempDir = NSTemporaryDirectory();
NSLog(@"tempDirectory: %@",tempDir);

//create NSURL
NSString *path4 = [tempDir stringByAppendingPathComponent:tmpFileName];
NSURL* url = [NSURL fileURLWithPath:path4];
NSLog(@"Path=%@, url=%@",path4,url);

//setup HTML file contents
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"MathJax" ofType:@"js" inDirectory:@"mathjax-MathJax-v1.1a"];
NSLog(@"filePath = %@",filePath);

//write to temp file "tempDir/tmpFileName", set MathJax JavaScript to use "filePath" as directory, add "xContent" as content of HTML file
[self writeStringToFile:tempDir fileName:tmpFileName pathName:filePath content:xContent];

//create UIWebView
CGRect webRect = CGRectMake(10,10,300,400);
myWebView =[[UIWebView alloc] initWithFrame:webRect];
myWebView.scalesPageToFit = YES;
//[Update 21/03/2013] This step no longer required
// myWebView.delegate = self;

NSURLRequest* req = [[NSURLRequest alloc] initWithURL:url];

//original request to show MathJax stuffs

[req release];

You can now run your application, again it might take a while for 1st run due to the huge file size. The result of 1st example is shown below:

For 2nd example using "MathML", the result is as below:

[Update 21/03/2013] This step no longer required
I noticed there's this warning as shown below saying "Assigning to 'id' from incompatible type 'ViewController *'", which I don't know why as I have seen other examples on the net also doing the same thing - assigning UIWebView.delegate to "self". Let me know if any of you have any idea about this or how to get rid of it.

myWebView.delegate = self;

That's it, hope you find this helpful. And as usual, let me know if you need further information or have any better idea about this. Thanks!

[Update 21/03/2013] This step no longer required
[Update 02/03/2012] I found the fix to get rid of the warning. Just need to change this line in "ViewController.h" header file

@interface ViewController : UIViewController

to this

@interface ViewController : UIViewController <UIWebViewDelegate>

[Update 21/03/2013]
I tried MathJax v2.1 on Xcode 4.6 and following the steps in this post, got it working on iPhone/iPad 6.1 simulator and on my iPhone 5 as well. Please see this post for other further info.

1. Thanks for the great post!

The delay you are seeing when it is looking for fonts is not because it is trying to access them over the network; your setup is completely local and MathJax will not make network connections (except for help files if the user links to them). The delay is because is because it is having difficulty detecting the fact that the fonts are available, and is waiting for them to arrive; since it is not detecting them properly, it eventually times out and switches over to image fonts.

The reason for the failure in detection, as I recall, is due to the small screen size, and I'm hoping that it is fixed in the v2.0 release that was just made yesterday. You might try that out and see if it works better.

If v2.0 does detect the fonts better, then you can remove a lot of the 25MB by removing the image fonts (MathJax/fonts/HTML-CSS/TeX/png directory) and setting the imageFont:null in the HTML-CSS section of your configuration, since you won't need them in this environment. You can also remove the MathJax/unpacked directory entirely, as well as MathJax/test, and MathJax/docs. There is more savings possible as well (you can get it down to less than 2MB as I recall), but this should help a lot already.

Davide

2. Thanks Davide for the detail info. I have to admit I don't know much about the details of how MathJax works internally. Will certainly give 2.0 a try on both iPhone and iPad. Would love to see if we can really cut down the library size to 2MB as you mentioned.

Can you please also provide some info about where can I find the "HTML-CSS" section to set the "imageFont:null" thing?

Thanks

3. See the configuration options documentation at

http://www.mathjax.org/docs/2.0/options/index.html

for details. There is an HTML-CSS link at the bottom of the page.

As for cutting down the size, see

See if that helps.

Davide

4. Hi Can you please provide how to highlight the MathMl Content on the UIWebView

5. Hi,
sorry I am not aware of how to highlight the content as I think it's displayed as image. You might have to ask Davide and other guys at www.mathjax.org to see if they have any solution for that. May be there's some syntax in the MathJax that allows you to highlight part/all of the content?
Please kindly let me know too if you found the answer. Thanks!

6. Hi,

If you are not using delegate methods of UIWebView then their is no need to set delegate and declare delegate in view controller.h file.

myWebView.delegate = self; // no need without delegate methods

@interface ViewController : UIViewController // ne need to set delegate method here also