Browse Source

Add org-static-mathjax to the contrib directory

Thanks to Jan Bker for this contribution.
Carsten Dominik 14 years ago
parent
commit
4a028cc7c0

+ 171 - 0
contrib/lisp/org-static-mathjax.el

@@ -0,0 +1,171 @@
+;;; org-static-mathjax.el --- Muse-like tags in Org-mode
+;;
+;; Author: Jan Böker <jan dot boecker at jboecker dot de>
+
+;; This elisp code integrates Static MathJax into the
+;; HTML export process of Org-mode.
+;;
+;; The supporting files for this package are in contrib/scripts/staticmathjax
+;; Please read the README.org file in that directory for more information.
+
+;; To use it, evaluate it on startup, add the following to your .emacs:
+
+;; (require 'org-static-mathjax)
+;;
+;; You will then have to customize the following two variables:
+;; - org-static-mathjax-app-ini-path
+;; - org-static-mathjax-local-mathjax-path
+;;
+;; If xulrunner is not in your $PATH, you will also need to customize
+;; org-static-mathjax-xulrunner-path.
+;;
+;; If everything is setup correctly, you can trigger Static MathJax on
+;; export to HTML by adding the following line to your Org file:
+;; #+StaticMathJax: embed-fonts:nil output-file-name:"embedded-math.html"
+;;
+;; You can omit either argument.
+;; embed-fonts defaults to nil. If you do not specify output-file-name,
+;; the exported file is overwritten with the static version.
+;;
+;; If embed-fonts is non-nil, the fonts are embedded directly into the
+;; output file using data: URIs.
+;;
+;; output-file-name specifies the file name of the static version. You
+;; can use any arbitrary lisp form here, for example:
+;; output-file-name:(concat (file-name-sans-extension buffer-file-name) "-static.html")
+;;
+;; The StaticMathJax XULRunner application expects a UTF-8 encoded
+;; input file. If the static version displays random characters instead
+;; of your math, add the following line at the top of your Org file:
+;; -*- coding: utf-8; -*-
+;;
+;; License: GPL v2 or later
+
+(defcustom org-static-mathjax-app-ini-path
+  (or (expand-file-name
+       "../scripts/staticmatchjax/application.ini"
+       (file-name-directory (or load-file-name buffer-file-name)))
+      "")
+  "Path to \"application.ini\" of the Static MathJax XULRunner application.
+If you have extracted StaticMathJax to e.g. ~/.local/staticmathjax, set
+this to ~/.local/staticmathjax/application.ini"
+  :type 'string)
+
+(defcustom org-static-mathjax-xulrunner-path
+  "xulrunner"
+  "Path to your xulrunner binary"
+  :type 'string)
+
+(defcustom org-static-mathjax-local-mathjax-path
+  ""
+  "Extract the MathJax zip file somewhere on your local
+hard drive and specify the path here.
+
+The directory has to be writeable, as org-static-mathjax
+creates a temporary file there during export."
+  :type 'string)
+
+(defvar org-static-mathjax-debug
+  nil
+  "If non-nil, org-static-mathjax will print some debug messages")
+
+(defun org-static-mathjax-hook-installer ()
+  "Installs org-static-mathjax-process in after-save-hook.
+
+Sets the following buffer-local variables for org-static-mathjax-process to pick up:
+org-static-mathjax-mathjax-path: The path to MathJax.js as used by Org HTML export
+org-static-mathjax-options:      The string given with #+STATICMATHJAX: in the file"
+  (let ((static-mathjax-option-string (plist-get opt-plist :static-mathjax)))
+	(if static-mathjax-option-string
+		(progn (set (make-local-variable 'org-static-mathjax-options) static-mathjax-option-string)
+			   (set (make-local-variable 'org-static-mathjax-mathjax-path)
+					(nth 1 (assq 'path org-export-html-mathjax-options)))
+			   (let ((mathjax-options (plist-get opt-plist :mathjax)))
+				 (if mathjax-options
+					 (if (string-match "\\<path:" mathjax-options)
+						 (set 'org-static-mathjax-mathjax-path
+							  (car (read-from-string
+									(substring mathjax-options (match-end 0))))))))
+			   (add-hook 'after-save-hook 
+						 'org-static-mathjax-process
+						 nil t)))))
+
+
+(defun org-static-mathjax-process ()
+  (save-excursion
+	; some sanity checking
+	(if (or (string= org-static-mathjax-app-ini-path "")
+			(not (file-exists-p org-static-mathjax-app-ini-path)))
+		(error "Static MathJax: You must customize org-static-mathjax-app-ini-path!"))
+	(if (or (string= org-static-mathjax-local-mathjax-path "")
+			(not (file-exists-p org-static-mathjax-local-mathjax-path)))
+		(error "Static MathJax: You must customize org-static-mathjax-local-mathjax-path!"))
+
+	; define variables
+	(let* ((options org-static-mathjax-options)
+		   (output-file-name buffer-file-name)
+		   (input-file-name (let ((temporary-file-directory (file-name-directory org-static-mathjax-local-mathjax-path)))
+							  (make-temp-file "org-static-mathjax-" nil ".html")))
+		   (html-code (buffer-string))
+		   (mathjax-oldpath (concat "src=\"" org-static-mathjax-mathjax-path))
+		   (mathjax-newpath (concat "src=\"" org-static-mathjax-local-mathjax-path))
+		   embed-fonts)
+	  ; read file-local options
+	  (mapc
+	   (lambda (symbol)
+		 (if (string-match (concat "\\<" (symbol-name symbol) ":") options)
+			 (set symbol (eval (car (read-from-string
+									 (substring options (match-end 0))))))))
+	   '(embed-fonts output-file-name))
+	  
+	  ; debug
+	  (when org-static-mathjax-debug
+		(message "output file name, embed-fonts")
+		(print output-file-name)
+		(print embed-fonts))
+	  
+	  ; open (temporary) input file, copy contents there, replace MathJax path with local installation
+	  (with-temp-buffer
+		(insert html-code)
+		(goto-char 1)
+		(replace-regexp mathjax-oldpath mathjax-newpath)
+		(write-file input-file-name))
+	  
+	  ; prepare argument list for call-process
+	  (let ((call-process-args (list org-static-mathjax-xulrunner-path
+									 nil nil nil
+									 org-static-mathjax-app-ini-path
+									 input-file-name
+									 output-file-name)))
+		; if fonts are embedded, just append the --embed-fonts flag
+		(if embed-fonts
+			(add-to-list 'call-process-args "--embed-fonts" t))
+		; if fonts are not embedded, the XULRunner app must replace all references
+		; to the font files with the real location (Firefox inserts file:// URLs there,
+		; because we are using a local MathJax installation here)
+		(if (not embed-fonts)
+			(progn
+			  (add-to-list 'call-process-args "--final-mathjax-url" t)
+			  (add-to-list 'call-process-args 
+						   (file-name-directory org-static-mathjax-mathjax-path)
+						   t)))
+		
+		; debug
+		(when org-static-mathjax-debug
+		  (print call-process-args))
+		; call it
+		(apply 'call-process call-process-args)
+		; delete our temporary input file
+		(kill-buffer)
+		(delete-file input-file-name)
+		(let ((backup-file (concat input-file-name "~")))
+		  (if (file-exists-p backup-file)
+			  (delete-file backup-file)))))))
+
+(add-to-list 'org-export-inbuffer-options-extra
+'("STATICMATHJAX" :static-mathjax))
+
+(add-hook 'org-export-html-final-hook 'org-static-mathjax-hook-installer)
+
+
+(provide 'org-static-mathjax)

+ 1 - 0
contrib/scripts/staticmathjax/.gitignore

@@ -0,0 +1 @@
+*~

+ 79 - 0
contrib/scripts/staticmathjax/README.org

@@ -0,0 +1,79 @@
+Static MathJax v0.1 README
+#+AUTHOR: Jan Böcker <jan.boecker@jboecker.de>
+
+Static MathJax is a XULRunner application which loads a HTML input
+file that uses MathJax into a browser, waits until MathJax is done
+processing, and then writes the formatted result to an output HTML
+file.
+
+I have only tested exports from Emacs Org-mode as input files.  (As of
+2010-08-14, MathJax is used by default with HTML exports in the
+current Org development version.)
+
+Optionally, references to the math fonts used will be converted to
+"data:" URIs, thus embedding the font data into the HTML file itself.
+(see [[http://en.wikipedia.org/wiki/Data_URI_scheme]])
+
+The code is licensed under the GNU General Public License version
+2, or, at your option, any later version.
+
+
+* Usage
+  To run Static MathJax, an existing XULRunner installation is
+  required. From the directory to which you unpacked Static MathJax,
+  run:
+    
+  xulrunner application.ini <--embed-fonts | --final-mathjax-url <URL>>
+  <input file> <output file>
+
+  If you prefer to call "staticmathjax" instead of "xulrunner
+  application.ini", link xulrunner-stub into the directory:
+  ln /usr/lib/xulrunner-1.9.2.8/xulrunner-stub ./staticmathjax
+  
+  - input file :: 
+      name of the input file (the result of a HTML export
+      from Org-mode). It is assumed that this file uses the
+	  UTF-8 character encoding.
+
+  - output file ::
+      name of the output file.
+
+  - --embed-fonts :: 
+      if specified, the math fonts will be embedded into
+	  the output file using data: URIs
+
+  - --final-mathjax-url <URL> :: 
+	  if --embed-fonts is not specified, this
+      must be the URL to a MathJax installation folder (e.g. "MathJax"
+      if MathJax is installed in a subdirectory, or
+      "http://orgmode.org/mathjax" to use the version hosted on the Org
+      website.
+	  
+	  All references to math fonts in the output file will point to
+	  this directory.
+
+* Caveats
+  
+  The input file must not use a MathJax installation on the
+  web. Otherwise, due to a security feature of Firefox, MathJax will
+  fallback to image fonts.  If you have unpacked MathJax to a
+  subdirectory "MathJax", specify the following in your Org file:
+  
+  #+MathJax: path:"MathJax"
+
+  The math is rendered in Firefox, so MathJax applies its
+  Firefox-specific settings. When viewing the output files in other
+  browsers, it will look slightly different than the result that
+  running MathJax in that browser would produce.
+
+  Internet Explorer does not use the correct font, because it only
+  supports the EOT font format. For all other browsers (including
+  Firefox), MathJax uses the OTF font format.
+  
+  Embedding fonts into the HTML file wastes some space due to the
+  base64 encoding used in data: URIs.
+
+  I have found no way to access stdout or set an exit code in an
+  XULRunner app, so any code which calls Static MathJax has no idea if
+  processing was successful and when an error occurs, graphical
+  message boxes are displayed.

+ 91 - 0
contrib/scripts/staticmathjax/README.txt

@@ -0,0 +1,91 @@
+                      Static MathJax v0.1 README
+                      ==========================
+
+Author: Jan Böcker <jan.boecker@jboecker.de>
+Date: 2010-08-15 13:53:39 CEST
+
+
+Static MathJax is a XULRunner application which loads a HTML input
+file that uses MathJax into a browser, waits until MathJax is done
+processing, and then writes the formatted result to an output HTML
+file.
+
+I have only tested exports from Emacs Org-mode as input files.  (As of
+2010-08-14, MathJax is used by default with HTML exports in the
+current Org development version.)
+
+Optionally, references to the math fonts used will be converted to
+"data:" URIs, thus embedding the font data into the HTML file itself.
+(see [http://en.wikipedia.org/wiki/Data_URI_scheme])
+
+The code is licensed under the GNU General Public License version
+2, or, at your option, any later version.
+
+
+Table of Contents
+=================
+1 Usage 
+2 Caveats 
+
+
+1 Usage 
+~~~~~~~~
+  To run Static MathJax, an existing XULRunner installation is
+  required. From the directory to which you unpacked Static MathJax,
+  run:
+    
+  xulrunner application.ini <--embed-fonts | --final-mathjax-url <URL>>
+  <input file> <output file>
+
+  If you prefer to call "staticmathjax" instead of "xulrunner
+  application.ini", link xulrunner-stub into the directory:
+  ln /usr/lib/xulrunner-1.9.2.8/xulrunner-stub ./staticmathjax
+  
+  input file: 
+      name of the input file (the result of a HTML export
+      from Org-mode). It is assumed that this file uses the
+      UTF-8 character encoding.
+
+  output file:
+      name of the output file.
+
+  --embed-fonts: 
+      if specified, the math fonts will be embedded into
+      the output file using data: URIs
+
+  --final-mathjax-url <URL>: 
+      if --embed-fonts is not specified, this
+      must be the URL to a MathJax installation folder (e.g. "MathJax"
+      if MathJax is installed in a subdirectory, or
+      "[http://orgmode.org/mathjax]" to use the version hosted on the Org
+      website.
+      
+      All references to math fonts in the output file will point to
+      this directory.
+
+2 Caveats 
+~~~~~~~~~~
+  
+  The input file must not use a MathJax installation on the
+  web. Otherwise, due to a security feature of Firefox, MathJax will
+  fallback to image fonts.  If you have unpacked MathJax to a
+  subdirectory "MathJax", specify the following in your Org file:
+  
+  #+MathJax: path:"MathJax"
+
+  The math is rendered in Firefox, so MathJax applies its
+  Firefox-specific settings. When viewing the output files in other
+  browsers, it will look slightly different than the result that
+  running MathJax in that browser would produce.
+
+  Internet Explorer does not use the correct font, because it only
+  supports the EOT font format. For all other browsers (including
+  Firefox), MathJax uses the OTF font format.
+  
+  Embedding fonts into the HTML file wastes some space due to the
+  base64 encoding used in data: URIs.
+
+  I have found no way to access stdout or set an exit code in an
+  XULRunner app, so any code which calls Static MathJax has no idea if
+  processing was successful and when an error occurs, graphical
+  message boxes are displayed.

+ 11 - 0
contrib/scripts/staticmathjax/application.ini

@@ -0,0 +1,11 @@
+[App]
+Vendor=Jan Boecker
+Name=StaticMathJax
+Version=0.2
+BuildID=2
+Copyright=Copyright (c) 2010 Jan Boecker
+ID=xulapp@jboecker.de
+
+[Gecko]
+MinVersion=1.8
+

+ 1 - 0
contrib/scripts/staticmathjax/chrome/chrome.manifest

@@ -0,0 +1 @@
+content staticmathjax file:content/

+ 198 - 0
contrib/scripts/staticmathjax/chrome/content/main.js

@@ -0,0 +1,198 @@
+var docFrame;
+var logtextbox;
+var destFile;
+var embedFonts = false;
+var finalMathJaxURL = null;
+
+function log(text)
+{
+	logtextbox.setAttribute("value", logtextbox.getAttribute("value") + "\n" + text);
+}
+
+function init()
+{
+	try {
+		docFrame = document.getElementById("docFrame");
+		logtextbox = document.getElementById("logtextbox");
+
+		// parse command line arguments
+		var cmdLine = window.arguments[0];
+		cmdLine = cmdLine.QueryInterface(Components.interfaces.nsICommandLine);
+		
+		embedFonts = cmdLine.handleFlag("embed-fonts", false);
+		finalMathJaxURL = cmdLine.handleFlagWithParam("final-mathjax-url", false);
+		
+		if (!embedFonts && !finalMathJaxURL) {
+			alert("You must eiher specify --embed-fonts or --final-mathjax-url");
+			window.close();
+			return;
+		}
+		
+		sourceFilePath = cmdLine.getArgument(0);
+		destFilePath = cmdLine.getArgument(1);
+		if ( !sourceFilePath || !destFilePath ) {
+			alert("Not enough parameters, expecting two arguments:\nInput file, output file");
+			window.close();
+			return;
+		}
+		
+		sourceFile = cmdLine.resolveFile(sourceFilePath);
+		if (! (sourceFile.exists() && sourceFile.isFile()) ) {
+			alert("Invalid source file path.");
+			window.close();
+			return;
+		}
+		sourceURI = cmdLine.resolveURI(sourceFilePath);
+		
+		// create a nsIFile object for the output file
+		try{
+			destFile = cmdLine.resolveURI(destFilePath).QueryInterface(Components.interfaces.nsIFileURL).file;
+		}catch(e){
+			alert("Invalid destination file.\n\nException:\n" + e);
+			window.close();
+			return;
+		}
+		
+		// add iframeLoaded() as an onload event handler, then navigate to the source file
+		docFrame.addEventListener("DOMContentLoaded", iframeLoaded, true);
+		docFrame.setAttribute("src", sourceURI.spec);
+
+	} catch (e) {
+		alert("Error in init():\n\n" + e);
+		window.close();
+		return;
+	}
+}
+
+function iframeLoaded()
+{
+	/*
+	// print every MathJax signal to the log
+	docFrame.contentWindow.MathJax.Hub.Startup.signal.Interest(
+	function (message) {log("Startup: "+message)}
+	);
+	docFrame.contentWindow.MathJax.Hub.signal.Interest(
+	function (message) {log("Hub: "+message)}
+	);
+	*/
+
+	// tell MathJax to call serialize() when finished
+	docFrame.contentWindow.MathJax.Hub.Register.StartupHook("End", function() {serialize();});
+}
+
+function fileURLtoDataURI(url)
+{
+	var ios = Components.classes["@mozilla.org/network/io-service;1"]
+		.getService(Components.interfaces.nsIIOService);
+	var url_object = ios.newURI(url, "", null);
+	var file = url_object.QueryInterface(Components.interfaces.nsIFileURL).file;
+								
+	var data = "";  
+	var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].  
+		createInstance(Components.interfaces.nsIFileInputStream);
+	fstream.init(file, -1, -1, false);
+	var bstream = Components.classes["@mozilla.org/binaryinputstream;1"].  
+		createInstance(Components.interfaces.nsIBinaryInputStream);  
+	bstream.setInputStream(fstream);  
+				
+	var bytes = bstream.readBytes(bstream.available());
+	b64bytes = btoa(bytes);
+
+	return "data:;base64," + b64bytes;
+
+}
+
+function serialize()
+{
+	var MathJaxURL = docFrame.contentWindow.MathJax.Hub.config.root;
+
+	var searchURIList = new Array();
+	var replacementURIList = new Array();
+	
+	log("serialize: preprocessing");
+
+	// remove the MathJax status message window
+	msgdiv = docFrame.contentDocument.getElementById("MathJax_Message");
+	msgdiv.parentNode.removeChild(msgdiv);
+	
+	/* Loop through all CSS rules to find all @font-face rules.
+	   At this point, they refer to local absolute paths using file:// URLs.
+	   Replace them either with appropriate URLs relative to finalMathJaxURL
+	   or with data URIs. */
+	
+	for (var i = 0; i<docFrame.contentDocument.styleSheets.length; i++) {
+		var stylesheet = docFrame.contentDocument.styleSheets[i];
+		
+		for (var j=0; j< stylesheet.cssRules.length; j++) {
+			var rule = stylesheet.cssRules[j];
+			if (rule.cssText.match("font-face")) {
+
+				url = rule.style.getPropertyValue("src");
+				url = url.match(/url\(\"(.+)\"\)/)[1];
+				
+				// Since the properties seem read-only here, we populate
+				// searchURIList and replacementURIList to do text substitution
+				// after serialization
+				searchURIList.push(url);
+				if (embedFonts) {
+					replacementURIList.push(fileURLtoDataURI(url));
+				} else {
+					replacementURIList.push(url.replace(MathJaxURL, finalMathJaxURL));
+				}				
+			}
+		}
+	}
+
+
+	// find and remove the MathJax <script> tag
+	try{
+		var scriptTags = docFrame.contentDocument.getElementsByTagName("script");
+		for (var i=0; i<scriptTags.length; i++) {
+			if (scriptTags[i].getAttribute("src") && scriptTags[i].getAttribute("src").match(/MathJax.js/i))
+				scriptTags[i].parentNode.removeChild(scriptTags[i]);
+		}
+	}catch(e){alert(e);}
+
+	log("serialize: serializing");
+
+	var serializer = new XMLSerializer();
+	var xhtml = serializer.serializeToString(docFrame.contentDocument);
+	
+	log("serialize: postprocessing");
+	// make the MathJax URL relative again
+	//	xhtml = xhtml.replace(findMathJaxURL, "MathJax");
+	
+	try{
+		r1 = RegExp("&lt;!--/\\*--&gt;&lt;!\\[CDATA\\[/\\*&gt;&lt;!--\\*/", "g");
+		xhtml = xhtml.replace(r1, "");
+		r2 = RegExp("/\\*\\]\\]&gt;\\*/--&gt;", "g");
+		xhtml = xhtml.replace(r2, "");
+		r3 = RegExp("/\\*\\]\\]&gt;\\*///--&gt;", "g");
+		xhtml = xhtml.replace(r3, "");
+	}catch(e){alert(e);}
+	for (var i=0; i<searchURIList.length; i++)
+		xhtml = xhtml.replace(searchURIList[i], replacementURIList[i]);
+	
+	save(xhtml);
+	window.close();
+}
+
+function save(xhtml)
+{
+	try {
+		var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"].
+			createInstance(Components.interfaces.nsIFileOutputStream);
+
+		foStream.init(destFile, 0x02 | 0x08 | 0x20, 0666, 0); 
+		// write, create, truncate
+
+		// write in UTF-8 encoding
+		var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"].
+			createInstance(Components.interfaces.nsIConverterOutputStream);
+		converter.init(foStream, "UTF-8", 0, 0);
+		converter.writeString(xhtml);
+		converter.close(); // this closes foStream
+	} catch (e) {
+		alert("Error in save():\n\n" + e);
+	}
+}

+ 11 - 0
contrib/scripts/staticmathjax/chrome/content/main.xul

@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<window onload="init();" id="main" title="Static MathJax" width="300" height="300"
+xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script language="JavaScript" src="chrome://staticmathjax/content/main.js"/>
+
+  <browser flex="1" id="docFrame" src="" style="background-color:white;"/>
+  <textbox flex="1" id="logtextbox" multiline="true" style="display:none;"/>
+</window>

+ 1 - 0
contrib/scripts/staticmathjax/defaults/preferences/prefs.js

@@ -0,0 +1 @@
+pref("toolkit.defaultChromeURI", "chrome://staticmathjax/content/main.xul");