Sunday, February 26, 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.


What's this JavaScript about?


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".

2. Download MathJax from this link, under "Downloads for Local Installation" section, select the link which at this time of writing, says "Current Version: MathJax-1.1a.zip (15.4MB)".

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>"
    "</head>"
    "<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 <math><mi>a</mi><mo>&#x2260;</mo><mn>0</mn></math>,"
    "there are two solutions to <math>"
    "<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>"
    "</math> 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>"
    "</math>";
     */
    
    //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;
    myWebView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
myWebView.delegate = self;
    
    
    NSURLRequest* req = [[NSURLRequest alloc] initWithURL:url]; 
    
    //original request to show MathJax stuffs
    [myWebView loadRequest:req];
    
    [self.view addSubview:myWebView];
    [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:


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 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>

0 comments:

Post a Comment