Index: .gitignore
===================================================================
--- .gitignore	(revision ecdd1329f3d08a5aa572dbfe37b8bff1e161f9d3)
+++ .gitignore	(revision e9df8a5f8d0ff47d97d92068ed122dd5f81d2285)
@@ -49,4 +49,5 @@
 /docs/sphinx-docs/source/user/perspectives
 /docs/sphinx-docs/source/user/sasgui
+/docs/sphinx-docs/katex*.zip
 
 
Index: docs/sphinx-docs/Makefile
===================================================================
--- docs/sphinx-docs/Makefile	(revision 1d5f5c20a9deebda81aa660ca4fd66d8695ed081)
+++ docs/sphinx-docs/Makefile	(revision 31943715d145c5ad0cc2df2ac75442591490886e)
@@ -16,5 +16,5 @@
 PAPEROPT_a4     = -D latex_paper_size=a4
 PAPEROPT_letter = -D latex_paper_size=letter
-ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source-temp
 # the i18n builder cannot share the environment and doctrees with the others
 I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
@@ -53,5 +53,5 @@
 	rm -rf source/dev/api
 	sphinx-apidoc -o source/dev/api -d 8 ../../src
-	
+
 html: stubs
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
Index: docs/sphinx-docs/build_sphinx.py
===================================================================
--- docs/sphinx-docs/build_sphinx.py	(revision 01f1e174e9a9bcc2a3ecebc3d6448a75d9028016)
+++ docs/sphinx-docs/build_sphinx.py	(revision f477159602807c5dfc0a1ec9bd2b3f438185ddc3)
@@ -324,5 +324,4 @@
         print("!!!!NO MODEL DOCS WILL BE BUILT!!!!")
 
-
 def retrieve_bumps_docs():
     """
@@ -360,9 +359,35 @@
                      SASVIEW_BUILD])
 
+def build_pdf():
+    """
+    Runs sphinx-build for pdf.  Reads in all .rst files and spits out the final html.
+    """
+    print("=== Build PDF Docs from ReST Files ===")
+    subprocess.call(["sphinx-build",
+                     "-b", "latex", # Builder name. TODO: accept as arg to setup.py.
+                     "-d", os.path.join(SPHINX_BUILD, "doctrees"),
+                     SPHINX_SOURCE,
+                     os.path.join(SPHINX_BUILD, "latex")])
+
+    LATEXDIR = os.path.join(SPHINX_BUILD, "latex")
+    def pdflatex():
+        subprocess.call(["pdflatex", "Sasview.tex"], cwd=LATEXDIR)
+    pdflatex()
+    pdflatex()
+    pdflatex()
+    subprocess.call(["makeindex", "-s", "python.ist", "Sasview.idx"], cwd=LATEXDIR)
+    pdflatex()
+    pdflatex()
+
+    print("=== Copy PDF to HTML Directory ===")
+    source = os.path.join(LATEXDIR, "Sasview.pdf")
+    target = os.path.join(SASVIEW_DOCS, "Sasview.pdf")
+    shutil.copyfile(source, target)
+
 def build():
     """
     Runs sphinx-build.  Reads in all .rst files and spits out the final html.
     """
-    print("=== Build HTML Docs from Rest Files ===")
+    print("=== Build HTML Docs from ReST Files ===")
     subprocess.call(["sphinx-build",
                      "-b", "html", # Builder name. TODO: accept as arg to setup.py.
@@ -375,16 +400,30 @@
     copy_tree(html, SASVIEW_DOCS)
 
-    #We are building latex doc on linux only
-    if "linux" in platform:
-        print "=== Build Latex Docs from Rest Files ==="
-        subprocess.call(["sphinx-build",
-                     "-b", "latex", # Builder name. TODO: accept as arg to setup.py.
-                     "-d", os.path.join(SPHINX_BUILD, "doctrees"),
-                     SPHINX_SOURCE,
-                     os.path.join(SPHINX_BUILD, "latex")])
-
-        print "=== Copy Latex Docs to Build Directory ==="
-        latex = os.path.join(SPHINX_BUILD, "latex")
-        copy_tree(latex, SASVIEW_DOCS)
+def fetch_katex(version, destination="_static"):
+    from zipfile import ZipFile
+    import urllib2
+    url = "https://github.com/Khan/KaTeX/releases/download/%s/katex.zip" % version
+    cache_path = "katex_%s.zip" % version
+    if not os.path.exists(cache_path):
+        try:
+            fd_in = urllib2.urlopen(url)
+            with open(cache_path, "wb") as fd_out:
+                fd_out.write(fd_in.read())
+        finally:
+            fd_in.close()
+    with ZipFile(cache_path) as zip:
+        zip.extractall(destination)
+
+def convert_katex():
+    print("=== Preprocess HTML, converting latex to html ===")
+    subprocess.call(["node", "convertKaTex.js", SASVIEW_DOCS])
+
+def convert_mathjax():
+    print("=== Preprocess HTML, converting latex to html ===")
+    subprocess.call(["node", "convertMathJax.js", SASVIEW_DOCS])
+
+def fetch_mathjax():
+    subprocess.call(["npm", "install", "mathjax-node-page"])
+    # TODO: copy fonts from node_modules/mathjax/fonts/HTML-CSS/Tex into static
 
 def rebuild():
@@ -393,6 +432,11 @@
     retrieve_user_docs()
     retrieve_bumps_docs()
+    #fetch_katex(version=KATEX_VERSION, destination=KATEX_PARENT)
+    #fetch_mathjax()
     apidoc()
     build()
+    #build_pdf()
+    #convert_katex()
+    #convert_mathjax()
 
     print("=== Done ===")
Index: docs/sphinx-docs/convertKaTex.js
===================================================================
--- docs/sphinx-docs/convertKaTex.js	(revision 31943715d145c5ad0cc2df2ac75442591490886e)
+++ docs/sphinx-docs/convertKaTex.js	(revision 31943715d145c5ad0cc2df2ac75442591490886e)
@@ -0,0 +1,1 @@
+/Users/pkienzle/Source/KaTeX/contrib/preprocess/convertTree.js
Index: docs/sphinx-docs/convertMathJax.js
===================================================================
--- docs/sphinx-docs/convertMathJax.js	(revision 35ddae56eb0449a0d89239d0152f73017ed8fc1e)
+++ docs/sphinx-docs/convertMathJax.js	(revision 35ddae56eb0449a0d89239d0152f73017ed8fc1e)
@@ -0,0 +1,168 @@
+#!/usr/bin/env node
+// Usage:
+//    npm install jsdom
+//    node convertTree.js build/html
+/* eslint no-console:0 */
+/* global katex */
+"use strict";
+
+/**
+ * Set a global variables name to value.
+ */
+function setGlobal(name, value) {
+    (function(globals) { globals[name] = value; }((1, eval)('this')));
+}
+
+const fs = require("fs");
+//const await = require("await");
+const mjpage = require('mathjax-node-page').mjpage;
+
+function isDir(path) {
+    const stat = fs.statSync(path);
+    return (stat && stat.isDirectory());
+}
+
+/**
+ * Walk a directory tree, yielding a list of matching filenames
+ *
+ * @param {path} dir - root of the tree which is walked
+ * @param {regex} [pattern] - regular expression match for the full path
+ */
+function* walkit(dir, pattern = /./) {
+    const _walk = function* (dir) {
+        const entries = fs.readdirSync(dir);
+        for (const entry of entries) {
+            const path = dir + "/" + entry;
+            if (isDir(path)) {
+                //console.log('walking '+path);
+                yield* _walk(path);
+            } else if (pattern.test(path)) {
+                //console.log('yielding '+path);
+                yield path;
+            }
+        }
+    };
+    yield* _walk(dir);
+}
+
+function readFileAsync(path) {
+    return new Promise((resolve, reject) =>
+        fs.readFile(path, (err, data) => {
+            if (err) { reject(err); } else { resolve(data); }
+        }));
+}
+
+function writeFileAsync(path, data) {
+    return new Promise((resolve, reject) =>
+        fs.writeFile(path, data, (err) => {
+            if (err) { reject(err); } else { resolve(data); }
+        }));
+}
+
+const pageConfig = {
+    format: ["TeX"],
+    //output: 'html',
+    output: 'svg',
+    fontURL: 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js/fonts/HTML-CSS',
+    MathJax: {
+            //config: ["MMLorHTML.js"],
+            //jax: ["input/TeX","input/MathML","output/HTML-CSS",
+            //      "output/NativeMML", "output/PreviewHTML"],
+            jax: ["input/TeX", "output/HTML-CSS"],
+            //extensions: ["tex2jax.js","mml2jax.js","MathMenu.js",
+            //             "MathZoom.js", "fast-preview.js",
+            //             "AssistiveMML.js", "a11y/accessibility-menu.js"
+            //            ],
+            extensions: ["tex2jax.js"],
+            TeX: {
+                extensions: ["AMSmath.js","AMSsymbols.js",
+                             "noErrors.js","noUndefined.js"]
+            }
+    }
+}
+const mjnodeConfig = {
+  ex: 6, // ex-size in pixels
+  width: 100, // width of math container (in ex) for linebreaking and tags
+  fragment: false,
+  useFontCache: true, // use <defs> and <use> in svg output?
+  useGlobalCache: false, // use common <defs> for all equations?
+  //state: mjstate, // track global state
+  linebreaks: false, // do linebreaking?
+  equationNumbers: "none", // or "AMS" or "all"
+  //math: "", // the math to typeset
+  html: true, // generate HTML output?
+  css: true, // generate CSS for HTML output?
+  //mml: false, // generate mml output?
+  //svg: false, // generate svg output?
+  //speakText: true, // add spoken annotations to output?
+  //timeout: 10 * 1000, // 10 second timeout before restarting MathJax
+}
+function mjpageAsync(input, pageConfig, mjnodeConfig) {
+    return new Promise((resolve, reject) =>
+        mjpage(input, pageConfig, mjnodeConfig, output => resolve(output))
+    );
+}
+async function updateHtmlAsync(path) {
+  try {
+    console.log("====> processing", path);
+    const input = await readFileAsync(path);
+    const output = await mjpageAsync(input, pageConfig, mjnodeConfig);
+    return await writeFileAsync(path, output);
+  } catch (err) {
+    console.log(err);
+  }
+}
+
+async function processTreeAsync(dir) {
+    //console.log(dir);
+    for (const path of walkit(dir, /\.html$/)) {
+        await updateHtmlAsync(path);
+    }
+}
+
+async function mainAsync(args) {
+    //console.log(process.argv.slice(2));
+    for (const arg of args) {
+        if (isDir(arg)) {
+            processTreeAsync(arg);
+        } else if (/\.html$/.test(arg)) {
+            updateHtmlAsync(arg);
+        }
+    }
+}
+
+
+function updateHtmlSortOfSync(path) {
+    console.log("====> loading", path);
+    const input = fs.readFileSync(path);
+    //const pageConfig = {format: ['TeX']}
+    //const mjnodeConfig = {html: true}
+    mjpage(input, pageConfig, mjnodeConfig,
+            //output => console.log(output));
+            output => {
+                console.log("====> processed", path);
+                fs.writeFileSync(path, output)
+            });
+}
+
+function processTreeSortOfSync(dir) {
+    //console.log(dir);
+    for (const path of walkit(dir, /\.html$/)) {
+        updateHtmlSortOfSync(path);
+    }
+}
+
+function mainSortOfSync(args) {
+    //console.log(process.argv.slice(2));
+    for (const arg of args) {
+        if (isDir(arg)) {
+            processTreeSortOfSync(arg);
+        } else if (/\.html$/.test(arg)) {
+            updateHtmlSortOfSync(arg);
+        }
+    }
+}
+
+if (typeof require !== 'undefined' && require.main === module) {
+    console.log(mainAsync(process.argv.slice(2)));
+}
Index: docs/sphinx-docs/source/_extensions/mathjax.py
===================================================================
--- docs/sphinx-docs/source/_extensions/mathjax.py	(revision f477159602807c5dfc0a1ec9bd2b3f438185ddc3)
+++ docs/sphinx-docs/source/_extensions/mathjax.py	(revision f477159602807c5dfc0a1ec9bd2b3f438185ddc3)
@@ -0,0 +1,117 @@
+# -*- coding: utf-8 -*-
+"""
+    sphinx.ext.mathjax
+    ~~~~~~~~~~~~~~~~~~
+
+    Allow `MathJax <http://mathjax.org/>`_ to be used to display math in
+    Sphinx's HTML writer -- requires the MathJax JavaScript library on your
+    webserver/computer.
+
+    :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
+    :license: BSD, see LICENSE for details.
+
+    sample lines in conf.py to use this::
+
+        mathjax_path = ['https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.js',
+                        'http://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/contrib/auto-render.min.js',
+                        'rendermath.js']
+        mathjax_css = 'https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.6.0/katex.min.css'
+
+    with rendermath.js containing::
+
+        document.addEventListener("DOMContentLoaded", function(event) {
+            renderMathInElement(document.body);
+        });
+
+    2016-08-09 Jason Sachs
+    * allow list of URLS for mathjax_path, which permits use of katex.js
+"""
+
+from docutils import nodes
+
+import sphinx
+from sphinx.locale import _
+from sphinx.errors import ExtensionError
+from sphinx.ext.mathbase import setup_math as mathbase_setup
+
+
+def html_visit_math(self, node):
+    self.body.append(self.starttag(node, 'span', '', CLASS='math'))
+    self.body.append(self.builder.config.mathjax_inline[0] +
+                     self.encode(node['latex']) +
+                     self.builder.config.mathjax_inline[1] + '</span>')
+    raise nodes.SkipNode
+
+
+def html_visit_displaymath(self, node):
+    self.body.append(self.starttag(node, 'div', CLASS='math'))
+    if node['nowrap']:
+        self.body.append(self.encode(node['latex']))
+        self.body.append('</div>')
+        raise nodes.SkipNode
+
+    # necessary to e.g. set the id property correctly
+    if node['number']:
+        self.body.append('<span class="eqno">(%s)' % node['number'])
+        self.add_permalink_ref(node, _('Permalink to this equation'))
+        self.body.append('</span>')
+    self.body.append(self.builder.config.mathjax_display[0])
+    parts = [prt for prt in node['latex'].split('\n\n') if prt.strip()]
+    if len(parts) > 1:  # Add alignment if there are more than 1 equation
+        self.body.append(r' \begin{align}\begin{aligned}')
+    for i, part in enumerate(parts):
+        part = self.encode(part)
+        if r'\\' in part:
+            self.body.append(r'\begin{split}' + part + r'\end{split}')
+        else:
+            self.body.append(part)
+        if i < len(parts) - 1:  # append new line if not the last equation
+            self.body.append(r'\\')
+    if len(parts) > 1:  # Add alignment if there are more than 1 equation
+        self.body.append(r'\end{aligned}\end{align} ')
+    self.body.append(self.builder.config.mathjax_display[1])
+    self.body.append('</div>\n')
+    raise nodes.SkipNode
+
+try:
+    basestring
+except:
+    basestring = str
+
+def builder_inited(app):
+    jaxpath = app.config.mathjax_path
+    if not jaxpath:
+        raise ExtensionError('mathjax_path config value must be set for the '
+                             'mathjax extension to work')
+
+    # app.config.mathjax_path can be a string or a list of strings
+    if isinstance(jaxpath, basestring):
+        app.add_javascript(jaxpath)
+    else:
+        for p in jaxpath:
+            app.add_javascript(p)
+
+    if app.config.mathjax_css:
+        app.add_stylesheet(app.config.mathjax_css)
+
+
+def setup(app):
+    try:
+        mathbase_setup(app, (html_visit_math, None), (html_visit_displaymath, None))
+    except ExtensionError:
+        raise ExtensionError('sphinx.ext.mathjax: other math package is already loaded')
+
+    # more information for mathjax secure url is here:
+    # http://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
+    app.add_config_value('mathjax_path',
+                         'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?'
+                         'config=TeX-MML-AM_CHTML',
+                         #'config=TeX-AMS-MML_HTMLorMML',
+                         False)
+    app.add_config_value('mathjax_css', None, 'html')
+    app.add_config_value('mathjax_use_katex', False, 'html')
+    app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')
+    app.add_config_value('mathjax_display', [r'\[', r'\]'], 'html')
+    app.connect('builder-inited', builder_inited)
+
+    return {'version': sphinx.__display_version__, 'parallel_read_safe': True}
Index: docs/sphinx-docs/source/_static/rendermath.js
===================================================================
--- docs/sphinx-docs/source/_static/rendermath.js	(revision afca48850c5f8018d72eb8d0b9c040637c7eb3bf)
+++ docs/sphinx-docs/source/_static/rendermath.js	(revision afca48850c5f8018d72eb8d0b9c040637c7eb3bf)
@@ -0,0 +1,4 @@
+// Render katex on page.
+document.addEventListener("DOMContentLoaded", function(event) {
+	    renderMathInElement(document.body);
+});
Index: docs/sphinx-docs/source/conf.py
===================================================================
--- docs/sphinx-docs/source/conf.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ docs/sphinx-docs/source/conf.py	(revision f477159602807c5dfc0a1ec9bd2b3f438185ddc3)
@@ -36,6 +36,24 @@
               'sphinx.ext.coverage',
               'sphinx.ext.mathjax',
+              #'mathjax',  # replacement mathjax that allows a list of paths
               'dollarmath',
               'sphinx.ext.viewcode']
+
+mathjax_path = (
+    'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.1/MathJax.js?'
+    'config=TeX-MML-AM_CHTML')
+
+# For katex uncomment the following
+"""
+#STATIC_PATH = '../../_static/'
+STATIC_PATH = ''
+mathjax_path = [
+    STATIC_PATH + 'katex/katex.min.js',
+    STATIC_PATH + 'katex/contrib/auto-render.min.js',
+    STATIC_PATH + 'rendermath.js'
+]
+mathjax_css = STATIC_PATH + 'katex/katex.min.css'
+"""
+
 
 # Add any paths that contain templates here, relative to this directory.
@@ -201,14 +219,15 @@
 # -- Options for LaTeX output --------------------------------------------------
 
+# TODO: seems like angstroms is defined twice.
 LATEX_PREAMBLE=r"""
+\usepackage[utf8]{inputenc}      % Allow unicode symbols in text
+\usepackage{underscore}          % Allow underscore outside math mode
+\usepackage[T1]{fontenc}         % Use underscore character from font
+\newcommand{\lt}{<}              % HTML needs \lt rather than <
+\newcommand{\gt}{>}              % HTML needs \gt rather than >
 \renewcommand{\AA}{\text{\r{A}}} % Allow \AA in math mode
-\usepackage[utf8]{inputenc}      % Allow unicode symbols in text
-\usepackage[T1]{fontenc}      % Taking care of underscores
-\catcode`\_=12                % Moving underscore to category 12
-\newcommand{\lt}{<}           %lower than symbol handling
-\newcommand{\gt}{>}           %greater than symbol handling
+\DeclareUnicodeCharacter {212B} {\AA}                  % Angstrom
 \DeclareUnicodeCharacter {00B7} {\ensuremath{\cdot}}   % cdot
 \DeclareUnicodeCharacter {00B0} {\ensuremath{^\circ}}  % degrees
-\DeclareUnicodeCharacter {212B} {\AA}                  % Angstrom
 """
 latex_elements = {
Index: src/sas/sasgui/guiframe/documentation_window.py
===================================================================
--- src/sas/sasgui/guiframe/documentation_window.py	(revision 959eb0181ad06828a748f04512f953aad5b31659)
+++ src/sas/sasgui/guiframe/documentation_window.py	(revision 35ddae56eb0449a0d89239d0152f73017ed8fc1e)
@@ -16,21 +16,42 @@
 import os
 import logging
-import wx
 import webbrowser
 import urllib
 import sys
 
+import wx
+try:
+    import wx.html2 as html
+    WX_SUPPORTS_HTML2 = True
+except ImportError:
+    WX_SUPPORTS_HTML2 = False
+
+from .gui_manager import get_app_dir
+
 logger = logging.getLogger(__name__)
 
 SPHINX_DOC_ENV = "SASVIEW_DOC_PATH"
-WX_SUPPORTS_HTML2 = True
-try:
-    import wx.html2 as html
-except:
-    WX_SUPPORTS_HTML2 = False
 
+THREAD_STARTED = False
+def start_documentation_server(doc_root, port):
+    import thread
+    global THREAD_STARTED
+    if not THREAD_STARTED:
+        thread.start_new_thread(_documentation_server, (doc_root, port))
+        THREAD_STARTED = True
 
-from gui_manager import get_app_dir
+def _documentation_server(doc_root, port):
+    from SimpleHTTPServer import SimpleHTTPRequestHandler
+    from SocketServer import TCPServer
 
+    os.chdir(doc_root)
+    httpd = TCPServer(("127.0.0.1", port), SimpleHTTPRequestHandler, bind_and_activate=False)
+    httpd.allow_reuse_address = True
+    try:
+        httpd.server_bind()
+        httpd.server_activate()
+        httpd.serve_forever()
+    finally:
+        httpd.server_close()
 
 class DocumentationWindow(wx.Frame):
@@ -70,13 +91,22 @@
         #Note added June 21, 2015     PDB
         file_path = os.path.join(docs_path, path)
-        url = "file:///" + urllib.quote(file_path, r'/\:')+ url_instruction
-
-        if not os.path.exists(file_path):
+        if path.startswith('http'):
+            url = path
+        elif not os.path.exists(file_path):
+            url = "index.html"
             logger.error("Could not find Sphinx documentation at %s \
             -- has it been built?", file_path)
-        elif WX_SUPPORTS_HTML2:
+        elif False:
+            start_documentation_server(docs_path, port=7999)
+            url = "http://127.0.0.1:7999/" + path.replace('\\', '/') + url_instruction
+        else:
+            url = "file:///" + urllib.quote(file_path, r'/\:')+ url_instruction
+
+        logger.info("showing url " + url)
+        if WX_SUPPORTS_HTML2:
             # Complete HTML/CSS support!
             self.view = html.WebView.New(self)
             self.view.LoadURL(url)
+            self.Bind(html.EVT_WEBVIEW_ERROR, self.OnError, self.view)
             self.Show()
         else:
@@ -88,10 +118,14 @@
             webbrowser.open_new_tab(url)
 
+    def OnError(self, evt):
+        logger.error("%d: %s", evt.GetInt(), evt.GetString())
+
 def main():
     """
     main loop function if running alone for testing.
     """
+    url = "index.html" if len(sys.argv) <= 1 else sys.argv[1]
     app = wx.App()
-    DocumentationWindow(None, -1, "index.html", "", "Documentation",)
+    DocumentationWindow(None, -1, url, "", "Documentation",)
     app.MainLoop()
 
