/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ /* vim: set ts=2 et sw=2 tw=80: */ /************************************************************* * * MathJax.js * * The main support code for the MathJax Hub, including the * Ajax, Callback, Messaging, and Object-Oriented Programming * libraries, as well as the base Jax classes, and startup * processing code. * * --------------------------------------------------------------------- * * Copyright (c) 2009-2013 The MathJax Consortium * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ if (document.getElementById && document.childNodes && document.createElement) { if (!window.MathJax) {window.MathJax= {}} if (!MathJax.Hub) { // skip if already loaded MathJax.version = "2.2"; MathJax.fileversion = "2.2"; /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var PROTO = []; // a static object used to indicate when a prototype is being created var OBJECT = function (def) { var obj = def.constructor; if (!obj) {obj = new Function("")} for (var id in def) {if (id !== 'constructor' && def.hasOwnProperty(id)) {obj[id] = def[id]}} return obj; }; var CONSTRUCTOR = function () { return new Function ("return arguments.callee.Init.call(this,arguments)"); }; // // Test for Safari 2.x bug (can't replace prototype for result of new Function()). // (We don't use this version for everyone since it is a closure and we don't need that). // var BUGTEST = CONSTRUCTOR(); BUGTEST.prototype = {bug_test: 1}; if (!BUGTEST.prototype.bug_test) { CONSTRUCTOR = function () { return function () {return arguments.callee.Init.call(this,arguments)}; }; }; BASE.Object = OBJECT({ constructor: CONSTRUCTOR(), Subclass: function (def,classdef) { var obj = CONSTRUCTOR(); obj.SUPER = this; obj.Init = this.Init; obj.Subclass = this.Subclass; obj.Augment = this.Augment; obj.protoFunction = this.protoFunction; obj.can = this.can; obj.has = this.has; obj.isa = this.isa; obj.prototype = new this(PROTO); obj.prototype.constructor = obj; // the real constructor obj.Augment(def,classdef); return obj; }, Init: function (args) { var obj = this; if (args.length === 1 && args[0] === PROTO) {return obj} if (!(obj instanceof args.callee)) {obj = new args.callee(PROTO)} return obj.Init.apply(obj,args) || obj; }, Augment: function (def,classdef) { var id; if (def != null) { for (id in def) {if (def.hasOwnProperty(id)) {this.protoFunction(id,def[id])}} // MSIE doesn't list toString even if it is not native so handle it separately if (def.toString !== this.prototype.toString && def.toString !== {}.toString) {this.protoFunction('toString',def.toString)} } if (classdef != null) { for (id in classdef) {if (classdef.hasOwnProperty(id)) {this[id] = classdef[id]}} } return this; }, protoFunction: function (id,def) { this.prototype[id] = def; if (typeof def === "function") {def.SUPER = this.SUPER.prototype} }, prototype: { Init: function () {}, SUPER: function (fn) {return fn.callee.SUPER}, can: function (method) {return typeof(this[method]) === "function"}, has: function (property) {return typeof(this[property]) !== "undefined"}, isa: function (obj) {return (obj instanceof Object) && (this instanceof obj)} }, can: function (method) {return this.prototype.can.call(this,method)}, has: function (property) {return this.prototype.has.call(this,property)}, isa: function (obj) { var constructor = this; while (constructor) { if (constructor === obj) {return true} else {constructor = constructor.SUPER} } return false; }, SimpleSUPER: OBJECT({ constructor: function (def) {return this.SimpleSUPER.define(def)}, define: function (src) { var dst = {}; if (src != null) { for (var id in src) {if (src.hasOwnProperty(id)) {dst[id] = this.wrap(id,src[id])}} // MSIE doesn't list toString even if it is not native so handle it separately if (src.toString !== this.prototype.toString && src.toString !== {}.toString) {dst.toString = this.wrap('toString',src.toString)} } return dst; }, wrap: function (id,f) { if (typeof(f) === 'function' && f.toString().match(/\.\s*SUPER\s*\(/)) { var fn = new Function(this.wrapper); fn.label = id; fn.original = f; f = fn; fn.toString = this.stringify; } return f; }, wrapper: function () { var fn = arguments.callee; this.SUPER = fn.SUPER[fn.label]; try {var result = fn.original.apply(this,arguments)} catch (err) {delete this.SUPER; throw err} delete this.SUPER; return result; }.toString().replace(/^\s*function\s*\(\)\s*\{\s*/i,"").replace(/\s*\}\s*$/i,""), toString: function () { return this.original.toString.apply(this.original,arguments); } }) }); })("MathJax"); /**********************************************************/ /* * Create a callback function from various forms of data: * * MathJax.Callback(fn) -- callback to a function * * MathJax.Callback([fn]) -- callback to function * MathJax.Callback([fn,data...]) * -- callback to function with given data as arguments * MathJax.Callback([object,fn]) * -- call fn with object as "this" * MathJax.Callback([object,fn,data...]) * -- call fn with object as "this" and data as arguments * MathJax.Callback(["method",object]) * -- call method of object wth object as "this" * MathJax.Callback(["method",object,data...]) * -- as above, but with data as arguments to method * * MathJax.Callback({hook: fn, data: [...], object: this}) * -- give function, data, and object to act as "this" explicitly * * MathJax.Callback("code") -- callback that compiles and executes a string * * MathJax.Callback([...],i) * -- use slice of array starting at i and interpret * result as above. (Used for passing "arguments" array * and trimming initial arguments, if any.) */ /* * MathJax.Callback.After([...],cb1,cb2,...) * -- make a callback that isn't called until all the other * ones are called first. I.e., wait for a union of * callbacks to occur before making the given callback. */ /* * MathJax.Callback.Queue([callback,...]) * -- make a synchronized queue of commands that process * sequentially, waiting for those that return uncalled * callbacks. */ /* * MathJax.Callback.Signal(name) * -- finds or creates a names signal, to which listeners * can be attached and are signaled by messages posted * to the signal. Responses can be asynchronous. */ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} // // Create a callback from an associative array // var CALLBACK = function (data) { var cb = new Function("return arguments.callee.execute.apply(arguments.callee,arguments)"); for (var id in CALLBACK.prototype) { if (CALLBACK.prototype.hasOwnProperty(id)) { if (typeof(data[id]) !== 'undefined') {cb[id] = data[id]} else {cb[id] = CALLBACK.prototype[id]} } } cb.toString = CALLBACK.prototype.toString; return cb; }; CALLBACK.prototype = { isCallback: true, hook: function () {}, data: [], object: window, execute: function () { if (!this.called || this.autoReset) { this.called = !this.autoReset; return this.hook.apply(this.object,this.data.concat([].slice.call(arguments,0))); } }, reset: function () {delete this.called}, toString: function () {return this.hook.toString.apply(this.hook,arguments)} }; var ISCALLBACK = function (f) { return (typeof(f) === "function" && f.isCallback); } // // Evaluate a string in global context // var EVAL = function (code) {return eval.call(window,code)} EVAL("var __TeSt_VaR__ = 1"); // check if it works in global context if (window.__TeSt_VaR__) { try { delete window.__TeSt_VaR__; } // NOTE IE9 throws when in IE7 mode catch (error) { window.__TeSt_VaR__ = null; } } else { if (window.execScript) { // IE EVAL = function (code) { BASE.__code = code; code = "try {"+BASENAME+".__result = eval("+BASENAME+".__code)} catch(err) {"+BASENAME+".__result = err}"; window.execScript(code); var result = BASE.__result; delete BASE.__result; delete BASE.__code; if (result instanceof Error) {throw result} return result; } } else { // Safari2 EVAL = function (code) { BASE.__code = code; code = "try {"+BASENAME+".__result = eval("+BASENAME+".__code)} catch(err) {"+BASENAME+".__result = err}"; var head = (document.getElementsByTagName("head"))[0]; if (!head) {head = document.body} var script = document.createElement("script"); script.appendChild(document.createTextNode(code)); head.appendChild(script); head.removeChild(script); var result = BASE.__result; delete BASE.__result; delete BASE.__code; if (result instanceof Error) {throw result} return result; } } } // // Create a callback from various types of data // var USING = function (args,i) { if (arguments.length > 1) { if (arguments.length === 2 && !(typeof arguments[0] === 'function') && arguments[0] instanceof Object && typeof arguments[1] === 'number') {args = [].slice.call(args,i)} else {args = [].slice.call(arguments,0)} } if (args instanceof Array && args.length === 1) {args = args[0]} if (typeof args === 'function') { if (args.execute === CALLBACK.prototype.execute) {return args} return CALLBACK({hook: args}); } else if (args instanceof Array) { if (typeof(args[0]) === 'string' && args[1] instanceof Object && typeof args[1][args[0]] === 'function') { return CALLBACK({hook: args[1][args[0]], object: args[1], data: args.slice(2)}); } else if (typeof args[0] === 'function') { return CALLBACK({hook: args[0], data: args.slice(1)}); } else if (typeof args[1] === 'function') { return CALLBACK({hook: args[1], object: args[0], data: args.slice(2)}); } } else if (typeof(args) === 'string') { return CALLBACK({hook: EVAL, data: [args]}); } else if (args instanceof Object) { return CALLBACK(args); } else if (typeof(args) === 'undefined') { return CALLBACK({}); } throw Error("Can't make callback from given data"); }; // // Wait for a given time to elapse and then perform the callback // var DELAY = function (time,callback) { callback = USING(callback); callback.timeout = setTimeout(callback,time); return callback; }; // // Callback used by AFTER, QUEUE, and SIGNAL to check if calls have completed // var WAITFOR = function (callback,signal) { callback = USING(callback); if (!callback.called) {WAITSIGNAL(callback,signal); signal.pending++} }; var WAITEXECUTE = function () { var signals = this.signal; delete this.signal; this.execute = this.oldExecute; delete this.oldExecute; var result = this.execute.apply(this,arguments); if (ISCALLBACK(result) && !result.called) {WAITSIGNAL(result,signals)} else { for (var i = 0, m = signals.length; i < m; i++) { signals[i].pending--; if (signals[i].pending <= 0) {signals[i].call()} } } }; var WAITSIGNAL = function (callback,signals) { if (!(signals instanceof Array)) {signals = [signals]} if (!callback.signal) { callback.oldExecute = callback.execute; callback.execute = WAITEXECUTE; callback.signal = signals; } else if (signals.length === 1) {callback.signal.push(signals[0])} else {callback.signal = callback.signal.concat(signals)} }; // // Create a callback that is called when a collection of other callbacks have // all been executed. If the callback gets called immediately (i.e., the // others are all already called), check if it returns another callback // and return that instead. // var AFTER = function (callback) { callback = USING(callback); callback.pending = 0; for (var i = 1, m = arguments.length; i < m; i++) {if (arguments[i]) {WAITFOR(arguments[i],callback)}} if (callback.pending === 0) { var result = callback(); if (ISCALLBACK(result)) {callback = result} } return callback; }; // // An array of prioritized hooks that are executed sequentially // with a given set of data. // var HOOKS = MathJax.Object.Subclass({ // // Initialize the array and the auto-reset status // Init: function (reset) { this.hooks = []; this.reset = reset; }, // // Add a callback to the list, in priority order (default priority is 10) // Add: function (hook,priority) { if (priority == null) {priority = 10} if (!ISCALLBACK(hook)) {hook = USING(hook)} hook.priority = priority; var i = this.hooks.length; while (i > 0 && priority < this.hooks[i-1].priority) {i--} this.hooks.splice(i,0,hook); return hook; }, Remove: function (hook) { for (var i = 0, m = this.hooks.length; i < m; i++) { if (this.hooks[i] === hook) {this.hooks.splice(i,1); return} } }, // // Execute the list of callbacks, resetting them if requested. // If any return callbacks, return a callback that will be // executed when they all have completed. // Execute: function () { var callbacks = [{}]; for (var i = 0, m = this.hooks.length; i < m; i++) { if (this.reset) {this.hooks[i].reset()} var result = this.hooks[i].apply(window,arguments); if (ISCALLBACK(result) && !result.called) {callbacks.push(result)} } if (callbacks.length === 1) {return null} if (callbacks.length === 2) {return callbacks[1]} return AFTER.apply({},callbacks); } }); // // Run an array of callbacks passing them the given data. // (Legacy function, since this has been replaced by the HOOKS object). // var EXECUTEHOOKS = function (hooks,data,reset) { if (!hooks) {return null} if (!(hooks instanceof Array)) {hooks = [hooks]} if (!(data instanceof Array)) {data = (data == null ? [] : [data])} var handler = HOOKS(reset); for (var i = 0, m = hooks.length; i < m; i++) {handler.Add(hooks[i])} return handler.Execute.apply(handler,data); }; // // Command queue that performs commands in order, waiting when // necessary for commands to complete asynchronousely // var QUEUE = BASE.Object.Subclass({ // // Create the queue and push any commands that are specified // Init: function () { this.pending = 0; this.running = 0; this.queue = []; this.Push.apply(this,arguments); }, // // Add commands to the queue and run them. Adding a callback object // (rather than a callback specification) queues a wait for that callback. // Return the final callback for synchronization purposes. // Push: function () { var callback; for (var i = 0, m = arguments.length; i < m; i++) { callback = USING(arguments[i]); if (callback === arguments[i] && !callback.called) {callback = USING(["wait",this,callback])} this.queue.push(callback); } if (!this.running && !this.pending) {this.Process()} return callback; }, // // Process the command queue if we aren't waiting on another command // Process: function (queue) { while (!this.running && !this.pending && this.queue.length) { var callback = this.queue[0]; queue = this.queue.slice(1); this.queue = []; this.Suspend(); var result = callback(); this.Resume(); if (queue.length) {this.queue = queue.concat(this.queue)} if (ISCALLBACK(result) && !result.called) {WAITFOR(result,this)} } }, // // Suspend/Resume command processing on this queue // Suspend: function () {this.running++}, Resume: function () {if (this.running) {this.running--}}, // // Used by WAITFOR to restart the queue when an action completes // call: function () {this.Process.apply(this,arguments)}, wait: function (callback) {return callback} }); // // Create a named signal that listeners can attach to, to be signaled by // postings made to the signal. Posts are queued if they occur while one // is already in process. // var SIGNAL = QUEUE.Subclass({ Init: function (name) { QUEUE.prototype.Init.call(this); this.name = name; this.posted = []; // the messages posted so far this.listeners = HOOKS(true); // those with interest in this signal }, // // Post a message to the signal listeners, with callback for when complete // Post: function (message,callback,forget) { callback = USING(callback); if (this.posting || this.pending) { this.Push(["Post",this,message,callback,forget]); } else { this.callback = callback; callback.reset(); if (!forget) {this.posted.push(message)} this.Suspend(); this.posting = true; var result = this.listeners.Execute(message); if (ISCALLBACK(result) && !result.called) {WAITFOR(result,this)} this.Resume(); delete this.posting; if (!this.pending) {this.call()} } return callback; }, // // Clear the post history (so new listeners won't get old messages) // Clear: function (callback) { callback = USING(callback); if (this.posting || this.pending) { callback = this.Push(["Clear",this,callback]); } else { this.posted = []; callback(); } return callback; }, // // Call the callback (all replies are in) and process the command queue // call: function () {this.callback(this); this.Process()}, // // A listener calls this to register intrest in the signal (so it will be called // when posts occur). If ignorePast is true, it will not be sent the post history. // Interest: function (callback,ignorePast,priority) { callback = USING(callback); this.listeners.Add(callback,priority); if (!ignorePast) { for (var i = 0, m = this.posted.length; i < m; i++) { callback.reset(); var result = callback(this.posted[i]); if (ISCALLBACK(result) && i === this.posted.length-1) {WAITFOR(result,this)} } } return callback; }, // // A listener calls this to remove itself from a signal // NoInterest: function (callback) { this.listeners.Remove(callback); }, // // Hook a callback to a particular message on this signal // MessageHook: function (msg,callback,priority) { callback = USING(callback); if (!this.hooks) {this.hooks = {}; this.Interest(["ExecuteHooks",this])} if (!this.hooks[msg]) {this.hooks[msg] = HOOKS(true)} this.hooks[msg].Add(callback,priority); for (var i = 0, m = this.posted.length; i < m; i++) {if (this.posted[i] == msg) {callback.reset(); callback(this.posted[i])}} return callback; }, // // Execute the message hooks for the given message // ExecuteHooks: function (msg,more) { var type = ((msg instanceof Array) ? msg[0] : msg); if (!this.hooks[type]) {return null} return this.hooks[type].Execute(msg); } },{ signals: {}, // the named signals find: function (name) { if (!SIGNAL.signals[name]) {SIGNAL.signals[name] = new SIGNAL(name)} return SIGNAL.signals[name]; } }); // // The main entry-points // BASE.Callback = BASE.CallBack = USING; BASE.Callback.Delay = DELAY; BASE.Callback.After = AFTER; BASE.Callback.Queue = QUEUE; BASE.Callback.Signal = SIGNAL.find; BASE.Callback.Hooks = HOOKS; BASE.Callback.ExecuteHooks = EXECUTEHOOKS; })("MathJax"); /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var isSafari2 = (navigator.vendor === "Apple Computer, Inc." && typeof navigator.vendorSub === "undefined"); var sheets = 0; // used by Safari2 // // Update sheets count and look up the head object // var HEAD = function (head) { if (document.styleSheets && document.styleSheets.length > sheets) {sheets = document.styleSheets.length} if (!head) { head = (document.getElementsByTagName("head"))[0]; if (!head) {head = document.body} } return head; }; // // Remove scripts that are completed so they don't clutter up the HEAD. // This runs via setTimeout since IE7 can't remove the script while it is running. // var SCRIPTS = []; // stores scripts to be removed after a delay var REMOVESCRIPTS = function () { for (var i = 0, m = SCRIPTS.length; i < m; i++) {BASE.Ajax.head.removeChild(SCRIPTS[i])} SCRIPTS = []; }; BASE.Ajax = { loaded: {}, // files already loaded loading: {}, // files currently in process of loading loadHooks: {}, // hooks to call when files are loaded timeout: 15*1000, // timeout for loading of files (15 seconds) styleDelay: 1, // delay to use before styles are available config: {root: ""}, // URL of root directory to load from STATUS: { OK: 1, // file is loading or did load OK ERROR: -1 // file timed out during load }, rootPattern: new RegExp("^\\["+BASENAME+"\\]"), // // Return a complete URL to a file (replacing the root pattern) // fileURL: function (file) {return file.replace(this.rootPattern,this.config.root)}, // // Load a file if it hasn't been already. // Make sure the file URL is "safe"? // Require: function (file,callback) { callback = BASE.Callback(callback); var type; if (file instanceof Object) { for (var i in file) {if (file.hasOwnProperty(i)) {type = i.toUpperCase(); file = file[i]}} } else {type = file.split(/\./).pop().toUpperCase()} file = this.fileURL(file); // FIXME: check that URL is OK if (this.loaded[file]) { callback(this.loaded[file]); } else { var FILE = {}; FILE[type] = file; this.Load(FILE,callback); } return callback; }, // // Load a file regardless of where it is and whether it has // already been loaded. // Load: function (file,callback) { callback = BASE.Callback(callback); var type; if (file instanceof Object) { for (var i in file) {if (file.hasOwnProperty(i)) {type = i.toUpperCase(); file = file[i]}} } else {type = file.split(/\./).pop().toUpperCase()} file = this.fileURL(file); if (this.loading[file]) { this.addHook(file,callback); } else { this.head = HEAD(this.head); if (this.loader[type]) {this.loader[type].call(this,file,callback)} else {throw Error("Can't load files of type "+type)} } return callback; }, // // Register a load hook for a particular file (it will be called when // loadComplete() is called for that file) // LoadHook: function (file,callback,priority) { callback = BASE.Callback(callback); if (file instanceof Object) {for (var i in file) {if (file.hasOwnProperty(i)) {file = file[i]}}} file = this.fileURL(file); if (this.loaded[file]) {callback(this.loaded[file])} else {this.addHook(file,callback,priority)} return callback; }, addHook: function (file,callback,priority) { if (!this.loadHooks[file]) {this.loadHooks[file] = MathJax.Callback.Hooks()} this.loadHooks[file].Add(callback,priority); }, // // Used when files are combined in a preloading configuration file // Preloading: function () { for (var i = 0, m = arguments.length; i < m; i++) { var file = this.fileURL(arguments[i]); if (!this.loading[file]) {this.loading[file] = {preloaded: true}} } }, // // Code used to load the various types of files // (JS for JavaScript, CSS for style sheets) // loader: { // // Create a SCRIPT tag to load the file // JS: function (file,callback) { var script = document.createElement("script"); var timeout = BASE.Callback(["loadTimeout",this,file]); this.loading[file] = { callback: callback, timeout: setTimeout(timeout,this.timeout), status: this.STATUS.OK, script: script }; // Add this to the structure above after it is created to prevent recursion // when loading the initial localization file (before loading messsage is available) this.loading[file].message = BASE.Message.File(file); script.onerror = timeout; // doesn't work in IE and no apparent substitute script.type = "text/javascript"; script.src = file; this.head.appendChild(script); }, // // Create a LINK tag to load the style sheet // CSS: function (file,callback) { var link = document.createElement("link"); link.rel = "stylesheet"; link.type = "text/css"; link.href = file; this.loading[file] = { callback: callback, message: BASE.Message.File(file), status: this.STATUS.OK }; this.head.appendChild(link); this.timer.create.call(this,[this.timer.file,file],link); } }, // // Timing code for checking when style sheets are available. // timer: { // // Create the timing callback and start the timing loop. // We use a delay because some browsers need it to allow the styles // to be processed. // create: function (callback,node) { callback = BASE.Callback(callback); if (node.nodeName === "STYLE" && node.styleSheet && typeof(node.styleSheet.cssText) !== 'undefined') { callback(this.STATUS.OK); // MSIE processes style immediately, but doesn't set its styleSheet! } else if (window.chrome && typeof(window.sessionStorage) !== "undefined" && node.nodeName === "STYLE") { callback(this.STATUS.OK); // Same for Chrome 5 (beta), Grrr. } else if (isSafari2) { this.timer.start(this,[this.timer.checkSafari2,sheets++,callback],this.styleDelay); } else { this.timer.start(this,[this.timer.checkLength,node,callback],this.styleDelay); } return callback; }, // // Start the timer for the given callback checker // start: function (AJAX,check,delay,timeout) { check = BASE.Callback(check); check.execute = this.execute; check.time = this.time; check.STATUS = AJAX.STATUS; check.timeout = timeout || AJAX.timeout; check.delay = check.total = 0; if (delay) {setTimeout(check,delay)} else {check()} }, // // Increment the time total, increase the delay // and test if we are past the timeout time. // time: function (callback) { this.total += this.delay; this.delay = Math.floor(this.delay * 1.05 + 5); if (this.total >= this.timeout) {callback(this.STATUS.ERROR); return 1} return 0; }, // // For JS file loads, call the proper routine according to status // file: function (file,status) { if (status < 0) {BASE.Ajax.loadTimeout(file)} else {BASE.Ajax.loadComplete(file)} }, // // Call the hook with the required data // execute: function () {this.hook.call(this.object,this,this.data[0],this.data[1])}, // // Safari2 doesn't set the link's stylesheet, so we need to look in the // document.styleSheets array for the new sheet when it is created // checkSafari2: function (check,length,callback) { if (check.time(callback)) return; if (document.styleSheets.length > length && document.styleSheets[length].cssRules && document.styleSheets[length].cssRules.length) {callback(check.STATUS.OK)} else {setTimeout(check,check.delay)} }, // // Look for the stylesheets rules and check when they are defined // and no longer of length zero. (This assumes there actually ARE // some rules in the stylesheet.) // checkLength: function (check,node,callback) { if (check.time(callback)) return; var isStyle = 0; var sheet = (node.sheet || node.styleSheet); try {if ((sheet.cssRules||sheet.rules||[]).length > 0) {isStyle = 1}} catch(err) { if (err.message.match(/protected variable|restricted URI/)) {isStyle = 1} else if (err.message.match(/Security error/)) { // Firefox3 gives "Security error" for missing files, so // can't distinguish that from OK files on remote servers. // or OK files in different directory from local files. isStyle = 1; // just say it is OK (can't really tell) } } if (isStyle) { // Opera 9.6 requires this setTimeout setTimeout(BASE.Callback([callback,check.STATUS.OK]),0); } else { setTimeout(check,check.delay); } } }, // // JavaScript code must call this when they are completely initialized // (this allows them to perform asynchronous actions before indicating // that they are complete). // loadComplete: function (file) { file = this.fileURL(file); var loading = this.loading[file]; if (loading && !loading.preloaded) { BASE.Message.Clear(loading.message); clearTimeout(loading.timeout); if (loading.script) { if (SCRIPTS.length === 0) {setTimeout(REMOVESCRIPTS,0)} SCRIPTS.push(loading.script); } this.loaded[file] = loading.status; delete this.loading[file]; this.addHook(file,loading.callback); } else { if (loading) {delete this.loading[file]} this.loaded[file] = this.STATUS.OK; loading = {status: this.STATUS.OK} } if (!this.loadHooks[file]) {return null} return this.loadHooks[file].Execute(loading.status); }, // // If a file fails to load within the timeout period (or the onerror handler // is called), this routine runs to signal the error condition. // loadTimeout: function (file) { if (this.loading[file].timeout) {clearTimeout(this.loading[file].timeout)} this.loading[file].status = this.STATUS.ERROR; this.loadError(file); this.loadComplete(file); }, // // The default error hook for file load failures // loadError: function (file) { BASE.Message.Set(["LoadFailed","File failed to load: %1",file],null,2000); BASE.Hub.signal.Post(["file load error",file]); }, // // Defines a style sheet from a hash of style declarations (key:value pairs // where the key is the style selector and the value is a hash of CSS attributes // and values). // Styles: function (styles,callback) { var styleString = this.StyleString(styles); if (styleString === "") { callback = BASE.Callback(callback); callback(); } else { var style = document.createElement("style"); style.type = "text/css"; this.head = HEAD(this.head); this.head.appendChild(style); if (style.styleSheet && typeof(style.styleSheet.cssText) !== 'undefined') { style.styleSheet.cssText = styleString; } else { style.appendChild(document.createTextNode(styleString)); } callback = this.timer.create.call(this,callback,style); } return callback; }, // // Create a stylesheet string from a style declaration object // StyleString: function (styles) { if (typeof(styles) === 'string') {return styles} var string = "", id, style; for (id in styles) {if (styles.hasOwnProperty(id)) { if (typeof styles[id] === 'string') { string += id + " {"+styles[id]+"}\n"; } else if (styles[id] instanceof Array) { for (var i = 0; i < styles[id].length; i++) { style = {}; style[id] = styles[id][i]; string += this.StyleString(style); } } else if (id.substr(0,6) === '@media') { string += id + " {"+this.StyleString(styles[id])+"}\n"; } else if (styles[id] != null) { style = []; for (var name in styles[id]) {if (styles[id].hasOwnProperty(name)) { if (styles[id][name] != null) {style[style.length] = name + ': ' + styles[id][name]} }} string += id +" {"+style.join('; ')+"}\n"; } }} return string; } }; })("MathJax"); /**********************************************************/ MathJax.HTML = { // // Create an HTML element with given attributes and content. // The def parameter is an (optional) object containing key:value pairs // of the attributes and their values, and contents is an (optional) // array of strings to be inserted as text, or arrays of the form // [type,def,contents] that describes an HTML element to be inserted // into the current element. Thus the contents can describe a complete // HTML snippet of arbitrary complexity. E.g.: // // MathJax.HTML.Element("span",{id:"mySpan",style{"font-style":"italic"}},[ // "(See the ",["a",{href:"http://www.mathjax.org"},["MathJax home page"]], // " for more details.)"]); // Element: function (type,def,contents) { var obj = document.createElement(type); if (def) { if (def.style) { var style = def.style; def.style = {}; for (var id in style) {if (style.hasOwnProperty(id)) {def.style[id.replace(/-([a-z])/g,this.ucMatch)] = style[id]}} } MathJax.Hub.Insert(obj,def); } if (contents) { if (!(contents instanceof Array)) {contents = [contents]} for (var i = 0; i < contents.length; i++) { if (contents[i] instanceof Array) { obj.appendChild(this.Element(contents[i][0],contents[i][1],contents[i][2])); } else if (type === "script") { // IE throws an error if script is added as a text node this.setScript(obj, contents[i]); } else { obj.appendChild(document.createTextNode(contents[i])); } } } return obj; }, ucMatch: function (match,c) {return c.toUpperCase()}, addElement: function (span,type,def,contents) {return span.appendChild(this.Element(type,def,contents))}, TextNode: function (text) {return document.createTextNode(text)}, addText: function (span,text) {return span.appendChild(this.TextNode(text))}, // // Set and get the text of a script // setScript: function (script,text) { if (this.setScriptBug) {script.text = text} else { while (script.firstChild) {script.removeChild(script.firstChild)} this.addText(script,text); } }, getScript: function (script) { var text = (script.text === "" ? script.innerHTML : script.text); return text.replace(/^\s+/,"").replace(/\s+$/,""); }, // // Manage cookies // Cookie: { prefix: "mjx", expires: 365, // // Save an object as a named cookie // Set: function (name,def) { var keys = []; if (def) { for (var id in def) {if (def.hasOwnProperty(id)) { keys.push(id+":"+def[id].toString().replace(/&/g,"&&")); }} } var cookie = this.prefix+"."+name+"="+escape(keys.join('&;')); if (this.expires) { var time = new Date(); time.setDate(time.getDate() + this.expires); cookie += '; expires='+time.toGMTString(); } try {document.cookie = cookie+"; path=/"} catch (err) {} // ignore errors saving cookies }, // // Get the contents of a named cookie and incorporate // it into the given object (or return a fresh one) // Get: function (name,obj) { if (!obj) {obj = {}} var pattern = new RegExp("(?:^|;\\s*)"+this.prefix+"\\."+name+"=([^;]*)(?:;|$)"); var match = pattern.exec(document.cookie); if (match && match[1] !== "") { var keys = unescape(match[1]).split('&;'); for (var i = 0, m = keys.length; i < m; i++) { match = keys[i].match(/([^:]+):(.*)/); var value = match[2].replace(/&&/g,'&'); if (value === "true") {value = true} else if (value === "false") {value = false} else if (value.match(/^-?(\d+(\.\d+)?|\.\d+)$/)) {value = parseFloat(value)} obj[match[1]] = value; } } return obj; } } }; /**********************************************************/ MathJax.Localization = { locale: "en", directory: "[MathJax]/localization", strings: { en: {menuTitle: "English", isLoaded: true}, // nothing needs to be loaded for this de: {menuTitle: "Deutsch"}, fr: {menuTitle: "Fran\u00E7ais"} }, // // The pattern for substitution escapes: // %n or %{n} or %{plural:%n|option1|option1|...} or %c // pattern: /%(\d+|\{\d+\}|\{[a-z]+:\%\d+(?:\|(?:%\{\d+\}|%.|[^\}])*)+\}|.)/g, SPLIT: ("axb".split(/(x)/).length === 3 ? function (string,regex) {return string.split(regex)} : // // IE8 and below don't do split() correctly when the pattern includes // parentheses (the split should include the matched exrepssions). // So implement it by hand here. // function (string,regex) { var result = [], match, last = 0; regex.lastIndex = 0; while (match = regex.exec(string)) { result.push(string.substr(last,match.index)); result.push.apply(result,match.slice(1)); last = match.index + match[0].length; } result.push(string.substr(last)); return result; }), _: function (id,phrase) { if (phrase instanceof Array) {return this.processSnippet(id,phrase)} return this.processString(this.lookupPhrase(id,phrase),[].slice.call(arguments,2)); }, processString: function (string,args,domain) { // // Process arguments for substitution // If the argument is a snippet (and we are processing snippets) do so, // Otherwise, if it is a number, convert it for the lacale // var i, m; for (i = 0, m = args.length; i < m; i++) { if (domain && args[i] instanceof Array) {args[i] = this.processSnippet(domain,args[i])} } // // Split string at escapes and process them individually // var parts = this.SPLIT(string,this.pattern); for (i = 1, m = parts.length; i < m; i += 2) { var c = parts[i].charAt(0); // first char will be { or \d or a char to be kept literally if (c >= "0" && c <= "9") { // %n parts[i] = args[parts[i]-1]; if (typeof parts[i] === "number") parts[i] = this.number(parts[i]); } else if (c === "{") { // %{n} or %{plural:%n|...} c = parts[i].substr(1); if (c >= "0" && c <= "9") { // %{n} parts[i] = args[parts[i].substr(1,parts[i].length-2)-1]; if (typeof parts[i] === "number") parts[i] = this.number(parts[i]); } else { // %{plural:%n|...} var match = parts[i].match(/^\{([a-z]+):%(\d+)\|(.*)\}$/); if (match) { if (match[1] === "plural") { var n = args[match[2]-1]; if (typeof n === "undefined") { parts[i] = "???"; // argument doesn't exist } else { n = this.plural(n) - 1; // index of the form to use var plurals = match[3].replace(/(^|[^%])(%%)*%\|/g,"$1$2%\uEFEF").split(/\|/); // the parts (replacing %| with a special character) if (n >= 0 && n < plurals.length) { parts[i] = this.processString(plurals[n].replace(/\uEFEF/g,"|"),args,domain); } else { parts[i] = "???"; // no string for this index } } } else {parts[i] = "%"+parts[i]} // not "plural", put back the % and leave unchanged } } } if (parts[i] == null) {parts[i] = "???"} } // // If we are not forming a snippet, return the completed string // if (!domain) {return parts.join("")} // // We need to return an HTML snippet, so buld it from the // broken up string with inserted parts (that could be snippets) // var snippet = [], part = ""; for (i = 0; i < m; i++) { part += parts[i]; i++; // add the string and move on to substitution result if (i < m) { if (parts[i] instanceof Array) { // substitution was a snippet snippet.push(part); // add the accumulated string snippet = snippet.concat(parts[i]); // concatenate the substution snippet part = ""; // start accumulating a new string } else { // substitution was a string part += parts[i]; // add to accumulating string } } } if (part !== "") {snippet.push(part)} // add final string return snippet; }, processSnippet: function (domain,snippet) { var result = []; // the new snippet // // Look through the original snippet for // strings or snippets to translate // for (var i = 0, m = snippet.length; i < m; i++) { if (snippet[i] instanceof Array) { // // This could be a sub-snippet: // ["tag"] or ["tag",{properties}] or ["tag",{properties},snippet] // Or it could be something to translate: // [id,string,args] or [domain,snippet] var data = snippet[i]; if (typeof data[1] === "string") { // [id,string,args] var id = data[0]; if (!(id instanceof Array)) {id = [domain,id]} var phrase = this.lookupPhrase(id,data[1]); result = result.concat(this.processMarkdown(phrase,data.slice(2),domain)); } else if (data[1] instanceof Array) { // [domain,snippet] result = result.concat(this.processSnippet.apply(this,data)); } else if (data.length >= 3) { // ["tag",{properties},snippet] result.push([data[0],data[1],this.processSnippet(domain,data[2])]); } else { // ["tag"] or ["tag",{properties}] result.push(snippet[i]); } } else { // a string result.push(snippet[i]); } } return result; }, markdownPattern: /(%.)|(\*{1,3})((?:%.|.)+?)\2|(`+)((?:%.|.)+?)\4|\[((?:%.|.)+?)\]\(([^\s\)]+)\)/, // %c or *bold*, **italics**, ***bold-italics***, or `code`, or [link](url) processMarkdown: function (phrase,args,domain) { var result = [], data; // // Split the string by the Markdown pattern // (the text blocks are separated by // c,stars,star-text,backtics,code-text,link-text,URL). // Start with teh first text string from the split. // var parts = phrase.split(this.markdownPattern); var string = parts[0]; // // Loop through the matches and process them // for (var i = 1, m = parts.length; i < m; i += 8) { if (parts[i+1]) { // stars (for bold/italic) // // Select the tag to use by number of stars (three stars requires two tags) // data = this.processString(parts[i+2],args,domain); if (!(data instanceof Array)) {data = [data]} data = [["b","i","i"][parts[i+1].length-1],{},data]; // number of stars determines type if (parts[i+1].length === 3) {data = ["b",{},data]} // bold-italic } else if (parts[i+3]) { // backtics (for code) // // Remove one leading or trailing space, and process substitutions // Make a tag // data = this.processString(parts[i+4].replace(/^\s/,"").replace(/\s$/,""),args,domain); if (!(data instanceof Array)) {data = [data]} data = ["code",{},data]; } else if (parts[i+5]) { // hyperlink // // Process the link text, and make an tag with the URL // data = this.processString(parts[i+5],args,domain); if (!(data instanceof Array)) {data = [data]} data = ["a",{href:this.processString(parts[i+6],args),target:"_blank"},data]; } else { // // Escaped character (%c) gets added into the string. // string += parts[i]; data = null; } // // If there is a tag to insert, // Add any pending string, then push the tag // if (data) { result = this.concatString(result,string,args,domain); result.push(data); string = ""; } // // Process the string that follows matches pattern // if (parts[i+7] !== "") {string += parts[i+7]} }; // // Add any pending string and return the resulting snippet // result = this.concatString(result,string,args,domain); return result; }, concatString: function (result,string,args,domain) { if (string != "") { // // Process the substutions. // If the result is not a snippet, turn it into one. // Then concatenate the snippet to the current one // string = this.processString(string,args,domain); if (!(string instanceof Array)) {string = [string]} result = result.concat(string); } return result; }, lookupPhrase: function (id,phrase,domain) { // // Get the domain and messageID // if (!domain) {domain = "_"} if (id instanceof Array) {domain = (id[0] || "_"); id = (id[1] || "")} // // Check if the data is available and if not, // load it and throw a restart error so the calling // code can wait for the load and try again. // var load = this.loadDomain(domain); if (load) {MathJax.Hub.RestartAfter(load)} // // Look up the message in the localization data // (if not found, the original English is used) // var localeData = this.strings[this.locale]; if (localeData) { if (localeData.domains && domain in localeData.domains) { var domainData = localeData.domains[domain]; if (domainData.strings && id in domainData.strings) {phrase = domainData.strings[id]} } } // // return the translated phrase // return phrase; }, // // Load a langauge data file from the proper // directory and file. // loadFile: function (file,data,callback) { callback = MathJax.Callback(callback||{}); file = (data.file || file); // the data's file name or the default name if (!file.match(/\.js$/)) {file += ".js"} // add .js if needed // // Add the directory if the file doesn't // contain a full URL already. // if (!file.match(/^([a-z]+:|\[MathJax\])/)) { var dir = (this.strings[this.locale].directory || this.directory + "/" + this.locale || "[MathJax]/localization/" + this.locale); file = dir + "/" + file; } // // Load the file and mark the data as loaded (even if it // failed to load, so we don't continue to try to load it // over and over). // var load = MathJax.Ajax.Require(file,function () {data.isLoaded = true; return callback()}); // // Return the callback if needed, otherwise null. // return (load.called ? null : load); }, // // Check to see if the localization data are loaded // for the given domain; if not, load the data file, // and return a callback for the loading operation. // Otherwise return null (data are loaded). // loadDomain: function (domain,callback) { var load, localeData = this.strings[this.locale]; if (localeData) { if (!localeData.isLoaded) { load = this.loadFile(this.locale,localeData); if (load) { return MathJax.Callback.Queue( load,["loadDomain",this,domain] // call again to load domain ).Push(callback); } } if (localeData.domains && domain in localeData.domains) { var domainData = localeData.domains[domain]; if (!domainData.isLoaded) { load = this.loadFile(domain,domainData); if (load) {return MathJax.Callback.Queue(load).Push(callback)} } } } // localization data are loaded, so just do the callback return MathJax.Callback(callback)(); }, // // Perform a function, properly handling // restarts due to localization file loads. // // Note that this may return before the function // has been called successfully, so you should // consider fn as running asynchronously. (Callbacks // can be used to synchronize it with other actions.) // Try: function (fn) { fn = MathJax.Callback(fn); fn.autoReset = true; try {fn()} catch (err) { if (!err.restart) {throw err} MathJax.Callback.After(["Try",this,fn],err.restart); } }, // // Set the current language // setLocale: function(locale) { // don't set it if there isn't a definition for it if (this.strings[locale]) {this.locale = locale} if (MathJax.Menu) {this.loadDomain("MathMenu")} }, // // Add or update a language or domain // addTranslation: function (locale,domain,definition) { var data = this.strings[locale], isNew = false; if (!data) {data = this.strings[locale] = {}; isNew = true} if (!data.domains) {data.domains = {}} if (domain) { if (!data.domains[domain]) {data.domains[domain] = {}} data = data.domains[domain]; } MathJax.Hub.Insert(data,definition); if (isNew && MathJax.Menu.menu) {MathJax.Menu.CreateLocaleMenu()} }, // // Set CSS for an element based on font requirements // setCSS: function (div) { var locale = this.strings[this.locale]; if (locale) { if (locale.fontFamily) {div.style.fontFamily = locale.fontFamily} if (locale.fontDirection) { div.style.direction = locale.fontDirection; if (locale.fontDirection === "rtl") {div.style.textAlign = "right"} } } return div; }, // // Get the language's font family or direction // fontFamily: function () { var locale = this.strings[this.locale]; return (locale ? locale.fontFamily : null); }, fontDirection: function () { var locale = this.strings[this.locale]; return (locale ? locale.fontDirection : null); }, // // Get the language's plural index for a number // plural: function (n) { var locale = this.strings[this.locale]; if (locale && locale.plural) {return locale.plural(n)} // default if (n == 1) {return 1} // one return 2; // other }, // // Convert a number to language-specific form // number: function(n) { var locale = this.strings[this.locale]; if (locale && locale.number) {return locale.number(n)} // default return n; } }; /**********************************************************/ MathJax.Message = { ready: false, // used to tell when the styles are available log: [{}], current: null, textNodeBug: (navigator.vendor === "Apple Computer, Inc." && typeof navigator.vendorSub === "undefined") || (window.hasOwnProperty && window.hasOwnProperty("konqueror")), // Konqueror displays some gibberish with text.nodeValue = "..." styles: { "#MathJax_Message": { position: "fixed", left: "1px", bottom: "2px", 'background-color': "#E6E6E6", border: "1px solid #959595", margin: "0px", padding: "2px 8px", 'z-index': "102", color: "black", 'font-size': "80%", width: "auto", 'white-space': "nowrap" }, "#MathJax_MSIE_Frame": { position: "absolute", top:0, left: 0, width: "0px", 'z-index': 101, border: "0px", margin: "0px", padding: "0px" } }, browsers: { MSIE: function (browser) { MathJax.Hub.config.styles["#MathJax_Message"].position = "absolute"; MathJax.Message.quirks = (document.compatMode === "BackCompat"); }, Chrome: function (browser) { MathJax.Hub.config.styles["#MathJax_Message"].bottom = "1.5em"; MathJax.Hub.config.styles["#MathJax_Message"].left = "1em"; } }, Init: function (styles) { if (styles) {this.ready = true} if (!document.body || !this.ready) {return false} // // ASCIIMathML replaces the entire page with a copy of itself (@#!#%@!!) // so check that this.div is still part of the page, otherwise look up // the copy and use that. // if (this.div && this.div.parentNode == null) { this.div = document.getElementById("MathJax_Message"); if (this.div) {this.text = this.div.firstChild} } if (!this.div) { var frame = document.body; if (MathJax.Hub.Browser.isMSIE) { frame = this.frame = this.addDiv(document.body); frame.removeAttribute("id"); frame.style.position = "absolute"; frame.style.border = frame.style.margin = frame.style.padding = "0px"; frame.style.zIndex = "101"; frame.style.height = "0px"; frame = this.addDiv(frame); frame.id = "MathJax_MSIE_Frame"; window.attachEvent("onscroll",this.MoveFrame); window.attachEvent("onresize",this.MoveFrame); this.MoveFrame(); } this.div = this.addDiv(frame); this.div.style.display = "none"; this.text = this.div.appendChild(document.createTextNode("")); } return true; }, addDiv: function (parent) { var div = document.createElement("div"); div.id = "MathJax_Message"; if (parent.firstChild) {parent.insertBefore(div,parent.firstChild)} else {parent.appendChild(div)} return div; }, MoveFrame: function () { var body = (MathJax.Message.quirks ? document.body : document.documentElement); var frame = MathJax.Message.frame; frame.style.left = body.scrollLeft + 'px'; frame.style.top = body.scrollTop + 'px'; frame.style.width = body.clientWidth + 'px'; frame = frame.firstChild; frame.style.height = body.clientHeight + 'px'; }, localize: function (message) { return MathJax.Localization._(message,message); }, filterText: function (text,n,id) { if (MathJax.Hub.config.messageStyle === "simple") { if (id === "LoadFile") { if (!this.loading) {this.loading = this.localize("Loading") + " "} text = this.loading; this.loading += "."; } else if (id === "ProcessMath") { if (!this.processing) {this.processing = this.localize("Processing") + " "} text = this.processing; this.processing += "."; } else if (id === "TypesetMath") { if (!this.typesetting) {this.typesetting = this.localize("Typesetting") + " "} text = this.typesetting; this.typesetting += "."; } } return text; }, Set: function (text,n,clearDelay) { if (n == null) {n = this.log.length; this.log[n] = {}} // // Translate message if it is [id,message,arguments] // var id = ""; if (text instanceof Array) { id = text[0]; if (id instanceof Array) {id = id[1]} // // Localization._() will throw a restart error if a localization file // needs to be loaded, so trap that and redo the Set() call // after it is loaded. // try { text = MathJax.Localization._.apply(MathJax.Localization,text); } catch (err) { if (!err.restart) {throw err} if (!err.restart.called) { // // Mark it so we can tell if the Clear() comes before the message is displayed // if (this.log[n].restarted == null) {this.log[n].restarted = 0} this.log[n].restarted++; delete this.log[n].cleared; MathJax.Callback.After(["Set",this,text,n,clearDelay],err.restart); return n; } } } // // Clear the timout timer. // if (this.timer) {clearTimeout(this.timer); delete this.timer} // // Save the message and filtered message. // this.log[n].text = text; this.log[n].filteredText = text = this.filterText(text,n,id); // // Hook the message into the message list so we can tell // what message to put up when this one is removed. // if (typeof(this.log[n].next) === "undefined") { this.log[n].next = this.current; if (this.current != null) {this.log[this.current].prev = n} this.current = n; } // // Show the message if it is the currently active one. // if (this.current === n && MathJax.Hub.config.messageStyle !== "none") { if (this.Init()) { if (this.textNodeBug) {this.div.innerHTML = text} else {this.text.nodeValue = text} this.div.style.display = ""; if (this.status) {window.status = ""; delete this.status} } else { window.status = text; this.status = true; } } // // Check if the message was resetarted to load a localization file // and if it has been cleared in the meanwhile. // if (this.log[n].restarted) { if (this.log[n].cleared) {clearDelay = 0} if (--this.log[n].restarted === 0) {delete this.log[n].cleared} } // // Check if we need to clear the message automatically. // if (clearDelay) {setTimeout(MathJax.Callback(["Clear",this,n]),clearDelay)} else if (clearDelay == 0) {this.Clear(n,0)} // // Return the message number. // return n; }, Clear: function (n,delay) { // // Detatch the message from the active list. // if (this.log[n].prev != null) {this.log[this.log[n].prev].next = this.log[n].next} if (this.log[n].next != null) {this.log[this.log[n].next].prev = this.log[n].prev} // // If it is the current message, get the next one to show. // if (this.current === n) { this.current = this.log[n].next; if (this.text) { if (this.div.parentNode == null) {this.Init()} // see ASCIIMathML comments above if (this.current == null) { // // If there are no more messages, remove the message box. // if (this.timer) {clearTimeout(this.timer); delete this.timer} if (delay == null) {delay = 600} if (delay === 0) {this.Remove()} else {this.timer = setTimeout(MathJax.Callback(["Remove",this]),delay)} } else if (MathJax.Hub.config.messageStyle !== "none") { // // If there is an old message, put it in place // if (this.textNodeBug) {this.div.innerHTML = this.log[this.current].filteredText} else {this.text.nodeValue = this.log[this.current].filteredText} } if (this.status) {window.status = ""; delete this.status} } else if (this.status) { window.status = (this.current == null ? "" : this.log[this.current].text); } } // // Clean up the log data no longer needed // delete this.log[n].next; delete this.log[n].prev; delete this.log[n].filteredText; // // If this is a restarted localization message, mark that it has been cleared // while waiting for the file to load. // if (this.log[n].restarted) {this.log[n].cleared = true} }, Remove: function () { // FIXME: do a fade out or something else interesting? this.text.nodeValue = ""; this.div.style.display = "none"; }, File: function (file) { var root = MathJax.Ajax.config.root; if (file.substr(0,root.length) === root) {file = "[MathJax]"+file.substr(root.length)} return this.Set(["LoadFile","Loading %1",file],null,null); }, Log: function () { var strings = []; for (var i = 1, m = this.log.length; i < m; i++) {strings[i] = this.log[i].text} return strings.join("\n"); } }; /**********************************************************/ MathJax.Hub = { config: { root: "", config: [], // list of configuration files to load styleSheets: [], // list of CSS files to load styles: { // styles to generate in-line ".MathJax_Preview": {color: "#888"} }, jax: [], // list of input and output jax to load extensions: [], // list of extensions to load preJax: null, // pattern to remove from before math script tag postJax: null, // pattern to remove from after math script tag displayAlign: 'center', // how to align displayed equations (left, center, right) displayIndent: '0', // indentation for displayed equations (when not centered) preRemoveClass: 'MathJax_Preview', // class of objects to remove preceeding math script showProcessingMessages: true, // display "Processing math: nn%" messages or not messageStyle: "normal", // set to "none" or "simple" (for "Loading..." and "Processing...") delayStartupUntil: "none", // set to "onload" to delay setup until the onload handler runs // set to "configured" to delay startup until MathJax.Hub.Configured() is called // set to a Callback to wait for before continuing with the startup skipStartupTypeset: false, // set to true to skip PreProcess and Process during startup elements: [], // array of elements to process when none is given explicitly positionToHash: true, // after initial typeset pass, position to #hash location? showMathMenu: true, // attach math context menu to typeset math? showMathMenuMSIE: true, // separtely determine if MSIE should have math menu // (since the code for that is a bit delicate) menuSettings: { zoom: "None", // when to do MathZoom CTRL: false, // require CTRL for MathZoom? ALT: false, // require Alt or Option? CMD: false, // require CMD? Shift: false, // require Shift? discoverable: false, // make math menu discoverable on hover? zscale: "200%", // the scaling factor for MathZoom renderer: "", // set when Jax are loaded font: "Auto", // what font HTML-CSS should use context: "MathJax", // or "Browser" for pass-through to browser menu locale: "en", // the language to use for messages mpContext: false, // true means pass menu events to MathPlayer in IE mpMouse: false, // true means pass mouse events to MathPlayer in IE texHints: true // include class names for TeXAtom elements }, errorSettings: { // localized HTML snippet structure for message to use message: ["[",["MathProcessingError","Math Processing Error"],"]"], style: {color: "#CC0000", "font-style":"italic"} // style for message } }, preProcessors: MathJax.Callback.Hooks(true), // list of callbacks for preprocessing (initialized by extensions) inputJax: {}, // mime-type mapped to input jax (by registration) outputJax: {order:{}}, // mime-type mapped to output jax list (by registration) processUpdateTime: 250, // time between screen updates when processing math (milliseconds) processUpdateDelay: 10, // pause between screen updates to allow other processing (milliseconds) signal: MathJax.Callback.Signal("Hub"), // Signal used for Hub events Config: function (def) { this.Insert(this.config,def); if (this.config.Augment) {this.Augment(this.config.Augment)} }, CombineConfig: function (name,def) { var config = this.config, id, parent; name = name.split(/\./); for (var i = 0, m = name.length; i < m; i++) { id = name[i]; if (!config[id]) {config[id] = {}} parent = config; config = config[id]; } parent[id] = config = this.Insert(def,config); return config; }, Register: { PreProcessor: function () {MathJax.Hub.preProcessors.Add.apply(MathJax.Hub.preProcessors,arguments)}, MessageHook: function () {return MathJax.Hub.signal.MessageHook.apply(MathJax.Hub.signal,arguments)}, StartupHook: function () {return MathJax.Hub.Startup.signal.MessageHook.apply(MathJax.Hub.Startup.signal,arguments)}, LoadHook: function () {return MathJax.Ajax.LoadHook.apply(MathJax.Ajax,arguments)} }, getAllJax: function (element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxByType: function (type,element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax && scripts[i].MathJax.elementJax.mimeType === type) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxByInputType: function (type,element) { var jax = [], scripts = this.elementScripts(element); for (var i = 0, m = scripts.length; i < m; i++) { if (scripts[i].MathJax && scripts[i].MathJax.elementJax && scripts[i].type && scripts[i].type.replace(/ *;(.|\s)*/,"") === type) {jax.push(scripts[i].MathJax.elementJax)} } return jax; }, getJaxFor: function (element) { if (typeof(element) === 'string') {element = document.getElementById(element)} if (element && element.MathJax) {return element.MathJax.elementJax} if (element && element.isMathJax) { while (element && !element.jaxID) {element = element.parentNode} if (element) {return MathJax.OutputJax[element.jaxID].getJaxFromMath(element)} } return null; }, isJax: function (element) { if (typeof(element) === 'string') {element = document.getElementById(element)} if (element && element.isMathJax) {return 1} if (element && element.tagName != null && element.tagName.toLowerCase() === 'script') { if (element.MathJax) {return (element.MathJax.state === MathJax.ElementJax.STATE.PROCESSED ? 1 : -1)} if (element.type && this.inputJax[element.type.replace(/ *;(.|\s)*/,"")]) {return -1} } return 0; }, setRenderer: function (renderer,type) { if (!renderer) return; if (!MathJax.OutputJax[renderer]) { this.config.menuSettings.renderer = ""; var file = "[MathJax]/jax/output/"+renderer+"/config.js"; return MathJax.Ajax.Require(file,["setRenderer",this,renderer,type]); } else { this.config.menuSettings.renderer = renderer; if (type == null) {type = "jax/mml"} var jax = this.outputJax; if (jax[type] && jax[type].length) { if (renderer !== jax[type][0].id) { jax[type].unshift(MathJax.OutputJax[renderer]); return this.signal.Post(["Renderer Selected",renderer]); } } return null; } }, Queue: function () { return this.queue.Push.apply(this.queue,arguments); }, Typeset: function (element,callback) { if (!MathJax.isReady) return null; var ec = this.elementCallback(element,callback); var queue = MathJax.Callback.Queue(); for (var i = 0, m = ec.elements.length; i < m; i++) { if (ec.elements[i]) { queue.Push( ["PreProcess",this,ec.elements[i]], ["Process",this,ec.elements[i]] ); } } return queue.Push(ec.callback); }, PreProcess: function (element,callback) { var ec = this.elementCallback(element,callback); var queue = MathJax.Callback.Queue(); for (var i = 0, m = ec.elements.length; i < m; i++) { if (ec.elements[i]) { queue.Push( ["Post",this.signal,["Begin PreProcess",ec.elements[i]]], (arguments.callee.disabled? {} : ["Execute",this.preProcessors,ec.elements[i]]), ["Post",this.signal,["End PreProcess",ec.elements[i]]] ); } } return queue.Push(ec.callback); }, Process: function (element,callback) {return this.takeAction("Process",element,callback)}, Update: function (element,callback) {return this.takeAction("Update",element,callback)}, Reprocess: function (element,callback) {return this.takeAction("Reprocess",element,callback)}, Rerender: function (element,callback) {return this.takeAction("Rerender",element,callback)}, takeAction: function (action,element,callback) { var ec = this.elementCallback(element,callback); var queue = MathJax.Callback.Queue(["Clear",this.signal]); for (var i = 0, m = ec.elements.length; i < m; i++) { if (ec.elements[i]) { var state = { scripts: [], // filled in by prepareScripts start: new Date().getTime(), // timer for processing messages i: 0, j: 0, // current script, current jax jax: {}, // scripts grouped by output jax jaxIDs: [] // id's of jax used }; queue.Push( ["Post",this.signal,["Begin "+action,ec.elements[i]]], ["Post",this.signal,["Begin Math",ec.elements[i],action]], ["prepareScripts",this,action,ec.elements[i],state], ["Post",this.signal,["Begin Math Input",ec.elements[i],action]], ["processInput",this,state], ["Post",this.signal,["End Math Input",ec.elements[i],action]], ["prepareOutput",this,state,"preProcess"], ["Post",this.signal,["Begin Math Output",ec.elements[i],action]], ["processOutput",this,state], ["Post",this.signal,["End Math Output",ec.elements[i],action]], ["prepareOutput",this,state,"postProcess"], ["Post",this.signal,["End Math",ec.elements[i],action]], ["Post",this.signal,["End "+action,ec.elements[i]]] ); } } return queue.Push(ec.callback); }, scriptAction: { Process: function (script) {}, Update: function (script) { var jax = script.MathJax.elementJax; if (jax && jax.needsUpdate()) {jax.Remove(true); script.MathJax.state = jax.STATE.UPDATE} else {script.MathJax.state = jax.STATE.PROCESSED} }, Reprocess: function (script) { var jax = script.MathJax.elementJax; if (jax) {jax.Remove(true); script.MathJax.state = jax.STATE.UPDATE} }, Rerender: function (script) { var jax = script.MathJax.elementJax; if (jax) {jax.Remove(true); script.MathJax.state = jax.STATE.OUTPUT} } }, prepareScripts: function (action,element,state) { if (arguments.callee.disabled) return; var scripts = this.elementScripts(element); var STATE = MathJax.ElementJax.STATE; for (var i = 0, m = scripts.length; i < m; i++) { var script = scripts[i]; if (script.type && this.inputJax[script.type.replace(/ *;(.|\n)*/,"")]) { if (script.MathJax) { if (script.MathJax.elementJax && script.MathJax.elementJax.hover) { MathJax.Extension.MathEvents.Hover.ClearHover(script.MathJax.elementJax); } if (script.MathJax.state !== STATE.PENDING) {this.scriptAction[action](script)} } if (!script.MathJax) {script.MathJax = {state: STATE.PENDING}} if (script.MathJax.state !== STATE.PROCESSED) {state.scripts.push(script)} } } }, checkScriptSiblings: function (script) { if (script.MathJax.checked) return; var config = this.config, pre = script.previousSibling; if (pre && pre.nodeName === "#text") { var preJax,postJax, post = script.nextSibling; if (post && post.nodeName !== "#text") {post = null} if (config.preJax) { if (typeof(config.preJax) === "string") {config.preJax = new RegExp(config.preJax+"$")} preJax = pre.nodeValue.match(config.preJax); } if (config.postJax && post) { if (typeof(config.postJax) === "string") {config.postJax = new RegExp("^"+config.postJax)} postJax = post.nodeValue.match(config.postJax); } if (preJax && (!config.postJax || postJax)) { pre.nodeValue = pre.nodeValue.replace (config.preJax,(preJax.length > 1? preJax[1] : "")); pre = null; } if (postJax && (!config.preJax || preJax)) { post.nodeValue = post.nodeValue.replace (config.postJax,(postJax.length > 1? postJax[1] : "")); } if (pre && !pre.nodeValue.match(/\S/)) {pre = pre.previousSibling} } if (config.preRemoveClass && pre && pre.className === config.preRemoveClass) {script.MathJax.preview = pre} script.MathJax.checked = 1; }, processInput: function (state) { var jax, STATE = MathJax.ElementJax.STATE; var script, prev, m = state.scripts.length; try { // // Loop through the scripts // while (state.i < m) { script = state.scripts[state.i]; if (!script) {state.i++; continue} // // Remove previous error marker, if any // prev = script.previousSibling; if (prev && prev.className === "MathJax_Error") {prev.parentNode.removeChild(prev)} // // Check if already processed or needs processing // if (!script.MathJax || script.MathJax.state === STATE.PROCESSED) {state.i++; continue}; if (!script.MathJax.elementJax || script.MathJax.state === STATE.UPDATE) { this.checkScriptSiblings(script); // remove preJax/postJax etc. var type = script.type.replace(/ *;(.|\s)*/,""); // the input jax type jax = this.inputJax[type].Process(script,state); // run the input jax if (typeof jax === 'function') { // if a callback was returned if (jax.called) continue; // go back and call Process() again this.RestartAfter(jax); // wait for the callback } jax.Attach(script,this.inputJax[type].id); // register the jax on the script this.saveScript(jax,state,script,STATE); // add script to state } else if (script.MathJax.state === STATE.OUTPUT) { this.saveScript(script.MathJax.elementJax,state,script,STATE); // add script to state } // // Go on to the next script, and check if we need to update the processing message // state.i++; var now = new Date().getTime(); if (now - state.start > this.processUpdateTime && state.i < state.scripts.length) {state.start = now; this.RestartAfter(MathJax.Callback.Delay(1))} } } catch (err) {return this.processError(err,state,"Input")} // // Put up final message, reset the state and return // if (state.scripts.length && this.config.showProcessingMessages) {MathJax.Message.Set(["ProcessMath","Processing math: %1%%",100],0)} state.start = new Date().getTime(); state.i = state.j = 0; return null; }, saveScript: function (jax,state,script,STATE) { // // Check that output jax exists // if (!this.outputJax[jax.mimeType]) { script.MathJax.state = STATE.UPDATE; throw Error("No output jax registered for "+jax.mimeType); } // // Record the output jax // and put this script in the queue for that jax // jax.outputJax = this.outputJax[jax.mimeType][0].id; if (!state.jax[jax.outputJax]) { if (state.jaxIDs.length === 0) { // use original array until we know there are more (rather than two copies) state.jax[jax.outputJax] = state.scripts; } else { if (state.jaxIDs.length === 1) // get the script so far for the existing jax {state.jax[state.jaxIDs[0]] = state.scripts.slice(0,state.i)} state.jax[jax.outputJax] = []; // start a new array for the new jax } state.jaxIDs.push(jax.outputJax); // save the ID of the jax } if (state.jaxIDs.length > 1) {state.jax[jax.outputJax].push(script)} // // Mark script as needing output // script.MathJax.state = STATE.OUTPUT; }, // // Pre- and post-process scripts by jax // (to get scaling factors, hide/show output, and so on) // Since this can cause the jax to load, we need to trap restarts // prepareOutput: function (state,method) { while (state.j < state.jaxIDs.length) { var id = state.jaxIDs[state.j], JAX = MathJax.OutputJax[id]; if (JAX[method]) { try { var result = JAX[method](state); if (typeof result === 'function') { if (result.called) continue; // go back and try again this.RestartAfter(result); } } catch (err) { if (!err.restart) { MathJax.Message.Set(["PrepError","Error preparing %1 output (%2)",id,method],null,600); MathJax.Hub.lastPrepError = err; state.j++; } return MathJax.Callback.After(["prepareOutput",this,state,method],err.restart); } } state.j++; } return null; }, processOutput: function (state) { var result, STATE = MathJax.ElementJax.STATE, script, m = state.scripts.length; try { // // Loop through the scripts // while (state.i < m) { // // Check that there is an element jax // script = state.scripts[state.i]; if (!script || !script.MathJax || script.MathJax.error) {state.i++; continue} var jax = script.MathJax.elementJax; if (!jax) {state.i++; continue} // // Call the output Jax's Process method (which will be its Translate() // method once loaded). Mark it as complete and remove the preview. // result = MathJax.OutputJax[jax.outputJax].Process(script,state); script.MathJax.state = STATE.PROCESSED; state.i++; if (script.MathJax.preview) {script.MathJax.preview.innerHTML = ""} // // Signal that new math is available // this.signal.Post(["New Math",jax.inputID]); // FIXME: wait for this? (i.e., restart if returns uncalled callback) // // Update the processing message, if needed // var now = new Date().getTime(); if (now - state.start > this.processUpdateTime && state.i < state.scripts.length) {state.start = now; this.RestartAfter(MathJax.Callback.Delay(this.processUpdateDelay))} } } catch (err) {return this.processError(err,state,"Output")} // // Put up the typesetting-complete message // if (state.scripts.length && this.config.showProcessingMessages) { MathJax.Message.Set(["TypesetMath","Typesetting math: %1%%",100],0); MathJax.Message.Clear(0); } state.i = state.j = 0; return null; }, processMessage: function (state,type) { var m = Math.floor(state.i/(state.scripts.length)*100); var message = (type === "Output" ? ["TypesetMath","Typesetting math: %1%%"] : ["ProcessMath","Processing math: %1%%"]); if (this.config.showProcessingMessages) {MathJax.Message.Set(message.concat(m),0)} }, processError: function (err,state,type) { if (!err.restart) { if (!this.config.errorSettings.message) {throw err} this.formatError(state.scripts[state.i],err); state.i++; } this.processMessage(state,type); return MathJax.Callback.After(["process"+type,this,state],err.restart); }, formatError: function (script,err) { // // Get the error message, URL, and line, and save it for // reporting in the Show Math As Error menu // var message = "Error: "+err.message+"\n"; if (err.sourceURL) {message += "\nfile: "+err.sourceURL} if (err.line) {message += "\nline: "+err.line} script.MathJax.error = MathJax.OutputJax.Error.Jax(message,script); // // Create the [Math Processing Error] span // var errorSettings = this.config.errorSettings; var errorText = MathJax.Localization._(errorSettings.messageId,errorSettings.message); var error = MathJax.HTML.Element("span", {className:"MathJax_Error", jaxID:"Error", isMathJax:true},errorText); // // Attach the menu events // if (MathJax.Extension.MathEvents) { error.oncontextmenu = MathJax.Extension.MathEvents.Event.Menu; error.onmousedown = MathJax.Extension.MathEvents.Event.Mousedown; } else { MathJax.Ajax.Require("[MathJax]/extensions/MathEvents.js",function () { error.oncontextmenu = MathJax.Extension.MathEvents.Event.Menu; error.onmousedown = MathJax.Extension.MathEvents.Event.Mousedown; }); } // // Insert the error into the page and remove any preview // script.parentNode.insertBefore(error,script); if (script.MathJax.preview) {script.MathJax.preview.innerHTML = ""} // // Save the error for debugging purposes // Report the error as a signal // this.lastError = err; this.signal.Post(["Math Processing Error",script,err]); }, RestartAfter: function (callback) { throw this.Insert(Error("restart"),{restart: MathJax.Callback(callback)}); }, elementCallback: function (element,callback) { if (callback == null && (element instanceof Array || typeof element === 'function')) {try {MathJax.Callback(element); callback = element; element = null} catch(e) {}} if (element == null) {element = this.config.elements || []} if (!(element instanceof Array)) {element = [element]} element = [].concat(element); // make a copy so the original isn't changed for (var i = 0, m = element.length; i < m; i++) {if (typeof(element[i]) === 'string') {element[i] = document.getElementById(element[i])}} if (!document.body) {document.body = document.getElementsByTagName("body")[0]} if (element.length == 0) {element.push(document.body)} if (!callback) {callback = {}} return {elements: element, callback: callback}; }, elementScripts: function (element) { if (typeof(element) === 'string') {element = document.getElementById(element)} if (!document.body) {document.body = document.getElementsByTagName("body")[0]} if (element == null) {element = document.body} if (element.tagName != null && element.tagName.toLowerCase() === "script") {return [element]} return element.getElementsByTagName("script"); }, Insert: function (dst,src) { for (var id in src) {if (src.hasOwnProperty(id)) { // allow for concatenation of arrays? if (typeof src[id] === 'object' && !(src[id] instanceof Array) && (typeof dst[id] === 'object' || typeof dst[id] === 'function')) { this.Insert(dst[id],src[id]); } else { dst[id] = src[id]; } }} return dst; }, // Old browsers (e.g. Internet Explorer <= 8) do not support trim(). SplitList: ("trim" in String.prototype ? function (list) {return list.trim().split(/\s+/)} : function (list) {return list.replace(/^\s+/,''). replace(/\s+$/,'').split(/\s+/)}) }; MathJax.Hub.Insert(MathJax.Hub.config.styles,MathJax.Message.styles); MathJax.Hub.Insert(MathJax.Hub.config.styles,{".MathJax_Error":MathJax.Hub.config.errorSettings.style}); // // Storage area for extensions and preprocessors // MathJax.Extension = {}; // // Hub Startup code // MathJax.Hub.Configured = MathJax.Callback({}); // called when configuration is complete MathJax.Hub.Startup = { script: "", // the startup script from the SCRIPT call that loads MathJax.js queue: MathJax.Callback.Queue(), // Queue used for startup actions signal: MathJax.Callback.Signal("Startup"), // Signal used for startup events params: {}, // // Load the configuration files // Config: function () { this.queue.Push(["Post",this.signal,"Begin Config"]); // // If a locale is given as a parameter, // set the locale and the default menu value for the locale // if (this.params.locale) { MathJax.Localization.locale = this.params.locale; MathJax.Hub.config.menuSettings.locale = this.params.locale; } // // Check for user cookie configuration // var user = MathJax.HTML.Cookie.Get("user"); if (user.URL || user.Config) { if (confirm( MathJax.Localization._("CookieConfig", "MathJax has found a user-configuration cookie that includes code to "+ "be run. Do you want to run it?\n\n"+ "(You should press Cancel unless you set up the cookie yourself.)") )) { if (user.URL) {this.queue.Push(["Require",MathJax.Ajax,user.URL])} if (user.Config) {this.queue.Push(new Function(user.Config))} } else {MathJax.HTML.Cookie.Set("user",{})} } // // Run the config files, if any are given in the parameter list // if (this.params.config) { var files = this.params.config.split(/,/); for (var i = 0, m = files.length; i < m; i++) { if (!files[i].match(/\.js$/)) {files[i] += ".js"} this.queue.Push(["Require",MathJax.Ajax,this.URL("config",files[i])]); } } // // Run the deprecated configuration script, if any (ignoring return value) // Wait for the startup delay signal // Run the mathjax-config blocks // Load the files in the configuration's config array // if (this.script.match(/\S/)) {this.queue.Push(this.script+";\n1;")} this.queue.Push( ["ConfigDelay",this], ["ConfigBlocks",this], [function (THIS) {return THIS.loadArray(MathJax.Hub.config.config,"config",null,true)},this], ["Post",this.signal,"End Config"] ); }, // // Return the delay callback // ConfigDelay: function () { var delay = this.params.delayStartupUntil || MathJax.Hub.config.delayStartupUntil; if (delay === "onload") {return this.onload} if (delay === "configured") {return MathJax.Hub.Configured} return delay; }, // // Run the scipts of type=text/x-mathajx-config // ConfigBlocks: function () { var scripts = document.getElementsByTagName("script"); var last = null, queue = MathJax.Callback.Queue(); for (var i = 0, m = scripts.length; i < m; i++) { var type = String(scripts[i].type).replace(/ /g,""); if (type.match(/^text\/x-mathjax-config(;.*)?$/) && !type.match(/;executed=true/)) { scripts[i].type += ";executed=true"; last = queue.Push(scripts[i].innerHTML+";\n1;"); } } return last; }, // // Read cookie and set up menu defaults // (set the locale according to the cookie) // (adjust the jax to accommodate renderer preferences) // Cookie: function () { return this.queue.Push( ["Post",this.signal,"Begin Cookie"], ["Get",MathJax.HTML.Cookie,"menu",MathJax.Hub.config.menuSettings], [function (config) { if (config.menuSettings.locale) {MathJax.Localization.locale = config.menuSettings.locale} var renderer = config.menuSettings.renderer, jax = config.jax; if (renderer) { var name = "output/"+renderer; jax.sort(); for (var i = 0, m = jax.length; i < m; i++) { if (jax[i].substr(0,7) === "output/") break; } if (i == m-1) {jax.pop()} else { while (i < m) {if (jax[i] === name) {jax.splice(i,1); break}; i++} } jax.unshift(name); } },MathJax.Hub.config], ["Post",this.signal,"End Cookie"] ); }, // // Setup stylesheets and extra styles // Styles: function () { return this.queue.Push( ["Post",this.signal,"Begin Styles"], ["loadArray",this,MathJax.Hub.config.styleSheets,"config"], ["Styles",MathJax.Ajax,MathJax.Hub.config.styles], ["Post",this.signal,"End Styles"] ); }, // // Load the input and output jax // Jax: function () { var config = MathJax.Hub.config, jax = MathJax.Hub.outputJax; // Save the order of the output jax since they are loading asynchronously for (var i = 0, m = config.jax.length, k = 0; i < m; i++) { var name = config.jax[i].substr(7); if (config.jax[i].substr(0,7) === "output/" && jax.order[name] == null) {jax.order[name] = k; k++} } var queue = MathJax.Callback.Queue(); return queue.Push( ["Post",this.signal,"Begin Jax"], ["loadArray",this,config.jax,"jax","config.js"], ["Post",this.signal,"End Jax"] ); }, // // Load the extensions // Extensions: function () { var queue = MathJax.Callback.Queue(); return queue.Push( ["Post",this.signal,"Begin Extensions"], ["loadArray",this,MathJax.Hub.config.extensions,"extensions"], ["Post",this.signal,"End Extensions"] ); }, // // Initialize the Message system // Message: function () { MathJax.Message.Init(true); }, // // Set the math menu renderer, if it isn't already // (this must come after the jax are loaded) // Menu: function () { var menu = MathJax.Hub.config.menuSettings, jax = MathJax.Hub.outputJax, registered; for (var id in jax) {if (jax.hasOwnProperty(id)) { if (jax[id].length) {registered = jax[id]; break} }} if (registered && registered.length) { if (menu.renderer && menu.renderer !== registered[0].id) {registered.unshift(MathJax.OutputJax[menu.renderer])} menu.renderer = registered[0].id; } }, // // Set the location to the designated hash position // Hash: function () { if (MathJax.Hub.config.positionToHash && document.location.hash && document.body && document.body.scrollIntoView) { var name = document.location.hash.substr(1); var target = document.getElementById(name); if (!target) { var a = document.getElementsByTagName("a"); for (var i = 0, m = a.length; i < m; i++) {if (a[i].name === name) {target = a[i]; break}} } if (target) { while (!target.scrollIntoView) {target = target.parentNode} target = this.HashCheck(target); if (target && target.scrollIntoView) {setTimeout(function () {target.scrollIntoView(true)},1)} } } }, HashCheck: function (target) { if (target.isMathJax) { var jax = MathJax.Hub.getJaxFor(target); if (jax && MathJax.OutputJax[jax.outputJax].hashCheck) {target = MathJax.OutputJax[jax.outputJax].hashCheck(target)} } return target; }, // // Load the Menu and Zoom code, if it hasn't already been loaded. // This is called after the initial typeset, so should no longer be // competing with other page loads, but will make these available // if needed later on. // MenuZoom: function () { if (!MathJax.Extension.MathMenu) { setTimeout( function () { MathJax.Callback.Queue( ["Require",MathJax.Ajax,"[MathJax]/extensions/MathMenu.js",{}], ["loadDomain",MathJax.Localization,"MathMenu"] ) },1000 ); } else { setTimeout( MathJax.Callback(["loadDomain",MathJax.Localization,"MathMenu"]), 1000 ); } if (!MathJax.Extension.MathZoom) { setTimeout( MathJax.Callback(["Require",MathJax.Ajax,"[MathJax]/extensions/MathZoom.js",{}]), 2000 ); } }, // // Setup the onload callback // onLoad: function () { var onload = this.onload = MathJax.Callback(function () {MathJax.Hub.Startup.signal.Post("onLoad")}); if (document.body && document.readyState) if (MathJax.Hub.Browser.isMSIE) { // IE can change from loading to interactive before // full page is ready, so go with complete (even though // that means we may have to wait longer). if (document.readyState === "complete") {return [onload]} } else if (document.readyState !== "loading") {return [onload]} if (window.addEventListener) { window.addEventListener("load",onload,false); if (!this.params.noDOMContentEvent) {window.addEventListener("DOMContentLoaded",onload,false)} } else if (window.attachEvent) {window.attachEvent("onload",onload)} else {window.onload = onload} return onload; }, // // Perform the initial typesetting (or skip if configuration says to) // Typeset: function (element,callback) { if (MathJax.Hub.config.skipStartupTypeset) {return function () {}} return this.queue.Push( ["Post",this.signal,"Begin Typeset"], ["Typeset",MathJax.Hub,element,callback], ["Post",this.signal,"End Typeset"] ); }, // // Create a URL in the MathJax hierarchy // URL: function (dir,name) { if (!name.match(/^([a-z]+:\/\/|\[|\/)/)) {name = "[MathJax]/"+dir+"/"+name} return name; }, // // Load an array of files, waiting for all of them // to be loaded before going on // loadArray: function (files,dir,name,synchronous) { if (files) { if (!(files instanceof Array)) {files = [files]} if (files.length) { var queue = MathJax.Callback.Queue(), callback = {}, file; for (var i = 0, m = files.length; i < m; i++) { file = this.URL(dir,files[i]); if (name) {file += "/" + name} if (synchronous) {queue.Push(["Require",MathJax.Ajax,file,callback])} else {queue.Push(MathJax.Ajax.Require(file,callback))} } return queue.Push({}); // wait for everything to finish } } return null; } }; /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME], ROOT = "["+BASENAME+"]"; var HUB = BASE.Hub, AJAX = BASE.Ajax, CALLBACK = BASE.Callback; var JAX = MathJax.Object.Subclass({ JAXFILE: "jax.js", require: null, // array of files to load before jax.js is complete config: {}, // // Make a subclass and return an instance of it. // (FIXME: should we replace config with a copy of the constructor's // config? Otherwise all subclasses share the same config structure.) // Init: function (def,cdef) { if (arguments.length === 0) {return this} return (this.constructor.Subclass(def,cdef))(); }, // // Augment by merging with class definition (not replacing) // Augment: function (def,cdef) { var cObject = this.constructor, ndef = {}; if (def != null) { for (var id in def) {if (def.hasOwnProperty(id)) { if (typeof def[id] === "function") {cObject.protoFunction(id,def[id])} else {ndef[id] = def[id]} }} // MSIE doesn't list toString even if it is not native so handle it separately if (def.toString !== cObject.prototype.toString && def.toString !== {}.toString) {cObject.protoFunction('toString',def.toString)} } HUB.Insert(cObject.prototype,ndef); cObject.Augment(null,cdef); return this; }, Translate: function (script,state) { throw Error(this.directory+"/"+this.JAXFILE+" failed to define the Translate() method"); }, Register: function (mimetype) {}, Config: function () { this.config = HUB.CombineConfig(this.id,this.config); if (this.config.Augment) {this.Augment(this.config.Augment)} }, Startup: function () {}, loadComplete: function (file) { if (file === "config.js") { return AJAX.loadComplete(this.directory+"/"+file); } else { var queue = CALLBACK.Queue(); queue.Push( HUB.Register.StartupHook("End Config",{}), // wait until config complete ["Post",HUB.Startup.signal,this.id+" Jax Config"], ["Config",this], ["Post",HUB.Startup.signal,this.id+" Jax Require"], // Config may set the required and extensions array, // so use functions to delay making the reference until needed [function (THIS) {return MathJax.Hub.Startup.loadArray(THIS.require,this.directory)},this], [function (config,id) {return MathJax.Hub.Startup.loadArray(config.extensions,"extensions/"+id)},this.config||{},this.id], ["Post",HUB.Startup.signal,this.id+" Jax Startup"], ["Startup",this], ["Post",HUB.Startup.signal,this.id+" Jax Ready"] ); if (this.copyTranslate) { queue.Push( [function (THIS) { THIS.preProcess = THIS.preTranslate; THIS.Process = THIS.Translate; THIS.postProcess = THIS.postTranslate; },this.constructor.prototype] ); } return queue.Push(["loadComplete",AJAX,this.directory+"/"+file]); } } },{ id: "Jax", version: "2.2", directory: ROOT+"/jax", extensionDir: ROOT+"/extensions" }); /***********************************/ BASE.InputJax = JAX.Subclass({ elementJax: "mml", // the element jax to load for this input jax sourceMenuTitle: /*_(MathMenu)*/ ["OriginalForm","Original Form"], copyTranslate: true, Process: function (script,state) { var queue = CALLBACK.Queue(), file; // Load any needed element jax var jax = this.elementJax; if (!(jax instanceof Array)) {jax = [jax]} for (var i = 0, m = jax.length; i < m; i++) { file = BASE.ElementJax.directory+"/"+jax[i]+"/"+this.JAXFILE; if (!this.require) {this.require = []} else if (!(this.require instanceof Array)) {this.require = [this.require]}; this.require.push(file); // so Startup will wait for it to be loaded queue.Push(AJAX.Require(file)); } // Load the input jax file = this.directory+"/"+this.JAXFILE; var load = queue.Push(AJAX.Require(file)); if (!load.called) { this.constructor.prototype.Process = function () { if (!load.called) {return load} throw Error(file+" failed to load properly"); } } // Load the associated output jax jax = HUB.outputJax["jax/"+jax[0]]; if (jax) {queue.Push(AJAX.Require(jax[0].directory+"/"+this.JAXFILE))} return queue.Push({}); }, needsUpdate: function (jax) { var script = jax.SourceElement(); return (jax.originalText !== BASE.HTML.getScript(script)); }, Register: function (mimetype) { if (!HUB.inputJax) {HUB.inputJax = {}} HUB.inputJax[mimetype] = this; } },{ id: "InputJax", version: "2.2", directory: JAX.directory+"/input", extensionDir: JAX.extensionDir }); /***********************************/ BASE.OutputJax = JAX.Subclass({ copyTranslate: true, preProcess: function (state) { var load, file = this.directory+"/"+this.JAXFILE; this.constructor.prototype.preProcess = function (state) { if (!load.called) {return load} throw Error(file+" failed to load properly"); } load = AJAX.Require(file); return load; }, Register: function (mimetype) { var jax = HUB.outputJax; if (!jax[mimetype]) {jax[mimetype] = []} // If the output jax is earlier in the original configuration list, put it first here if (jax[mimetype].length && (this.id === HUB.config.menuSettings.renderer || (jax.order[this.id]||0) < (jax.order[jax[mimetype][0].id]||0))) {jax[mimetype].unshift(this)} else {jax[mimetype].push(this)} // Make sure the element jax is loaded before Startup is called if (!this.require) {this.require = []} else if (!(this.require instanceof Array)) {this.require = [this.require]}; this.require.push(BASE.ElementJax.directory+"/"+(mimetype.split(/\//)[1])+"/"+this.JAXFILE); }, Remove: function (jax) {} },{ id: "OutputJax", version: "2.2", directory: JAX.directory+"/output", extensionDir: JAX.extensionDir, fontDir: ROOT+(BASE.isPacked?"":"/..")+"/fonts", imageDir: ROOT+(BASE.isPacked?"":"/..")+"/images" }); /***********************************/ BASE.ElementJax = JAX.Subclass({ // make a subclass, not an instance Init: function (def,cdef) {return this.constructor.Subclass(def,cdef)}, inputJax: null, outputJax: null, inputID: null, originalText: "", mimeType: "", sourceMenuTitle: /*_(MathMenu)*/ ["MathMLcode","MathML Code"], Text: function (text,callback) { var script = this.SourceElement(); BASE.HTML.setScript(script,text); script.MathJax.state = this.STATE.UPDATE; return HUB.Update(script,callback); }, Reprocess: function (callback) { var script = this.SourceElement(); script.MathJax.state = this.STATE.UPDATE; return HUB.Reprocess(script,callback); }, Update: function (callback) {return this.Rerender(callback)}, Rerender: function (callback) { var script = this.SourceElement(); script.MathJax.state = this.STATE.OUTPUT; return HUB.Process(script,callback); }, Remove: function (keep) { if (this.hover) {this.hover.clear(this)} BASE.OutputJax[this.outputJax].Remove(this); if (!keep) { HUB.signal.Post(["Remove Math",this.inputID]); // wait for this to finish? this.Detach(); } }, needsUpdate: function () { return BASE.InputJax[this.inputJax].needsUpdate(this); }, SourceElement: function () {return document.getElementById(this.inputID)}, Attach: function (script,inputJax) { var jax = script.MathJax.elementJax; if (script.MathJax.state === this.STATE.UPDATE) { jax.Clone(this); } else { jax = script.MathJax.elementJax = this; if (script.id) {this.inputID = script.id} else {script.id = this.inputID = BASE.ElementJax.GetID(); this.newID = 1} } jax.originalText = BASE.HTML.getScript(script); jax.inputJax = inputJax; if (jax.root) {jax.root.inputID = jax.inputID} return jax; }, Detach: function () { var script = this.SourceElement(); if (!script) return; try {delete script.MathJax} catch(err) {script.MathJax = null} if (this.newID) {script.id = ""} }, Clone: function (jax) { var id; for (id in this) { if (!this.hasOwnProperty(id)) continue; if (typeof(jax[id]) === 'undefined' && id !== 'newID') {delete this[id]} } for (id in jax) { if (!jax.hasOwnProperty(id)) continue; if (typeof(this[id]) === 'undefined' || (this[id] !== jax[id] && id !== 'inputID')) {this[id] = jax[id]} } } },{ id: "ElementJax", version: "2.2", directory: JAX.directory+"/element", extensionDir: JAX.extensionDir, ID: 0, // jax counter (for IDs) STATE: { PENDING: 1, // script is identified as math but not yet processed PROCESSED: 2, // script has been processed UPDATE: 3, // elementJax should be updated OUTPUT: 4 // output should be updated (input is OK) }, GetID: function () {this.ID++; return "MathJax-Element-"+this.ID}, Subclass: function () { var obj = JAX.Subclass.apply(this,arguments); obj.loadComplete = this.prototype.loadComplete; return obj; } }); BASE.ElementJax.prototype.STATE = BASE.ElementJax.STATE; // // Some "Fake" jax used to allow menu access for "Math Processing Error" messages // BASE.OutputJax.Error = { id: "Error", version: "2.2", config: {}, ContextMenu: function () {return BASE.Extension.MathEvents.Event.ContextMenu.apply(BASE.Extension.MathEvents.Event,arguments)}, Mousedown: function () {return BASE.Extension.MathEvents.Event.AltContextMenu.apply(BASE.Extension.MathEvents.Event,arguments)}, getJaxFromMath: function (math) {return (math.nextSibling.MathJax||{}).error}, Jax: function (text,script) { var jax = MathJax.Hub.inputJax[script.type.replace(/ *;(.|\s)*/,"")]; return { inputJax: (jax||{id:"Error"}).id, // Use Error InputJax as fallback outputJax: "Error", sourceMenuTitle: /*_(MathMenu)*/ ["ErrorMessage","Error Message"], sourceMenuFormat: "Error", originalText: MathJax.HTML.getScript(script), errorText: text } } }; BASE.InputJax.Error = { id: "Error", version: "2.2", config: {}, sourceMenuTitle: /*_(MathMenu)*/ ["OriginalForm","Original Form"] }; })("MathJax"); /**********************************************************/ (function (BASENAME) { var BASE = window[BASENAME]; if (!BASE) {BASE = window[BASENAME] = {}} var HUB = BASE.Hub; var STARTUP = HUB.Startup; var CONFIG = HUB.config; var HEAD = document.getElementsByTagName("head")[0]; if (!HEAD) {HEAD = document.childNodes[0]}; var scripts = (document.documentElement || document).getElementsByTagName("script"); var namePattern = new RegExp("(^|/)"+BASENAME+"\\.js(\\?.*)?$"); for (var i = scripts.length-1; i >= 0; i--) { if ((scripts[i].src||"").match(namePattern)) { STARTUP.script = scripts[i].innerHTML; if (RegExp.$2) { var params = RegExp.$2.substr(1).split(/\&/); for (var j = 0, m = params.length; j < m; j++) { var KV = params[j].match(/(.*)=(.*)/); if (KV) {STARTUP.params[unescape(KV[1])] = unescape(KV[2])} } } CONFIG.root = scripts[i].src.replace(/(^|\/)[^\/]*(\?.*)?$/,''); break; } } BASE.Ajax.config = CONFIG; var BROWSERS = { isMac: (navigator.platform.substr(0,3) === "Mac"), isPC: (navigator.platform.substr(0,3) === "Win"), isMSIE: (window.ActiveXObject != null && window.clipboardData != null), isFirefox: (navigator.userAgent.match(/Gecko/) != null && navigator.userAgent.match(/KHTML/) == null), isSafari: (navigator.userAgent.match(/ (Apple)?WebKit\//) != null && (!window.chrome || window.chrome.loadTimes == null)), isChrome: (window.chrome != null && window.chrome.loadTimes != null), isOpera: (window.opera != null && window.opera.version != null), isKonqueror: (window.hasOwnProperty && window.hasOwnProperty("konqueror") && navigator.vendor == "KDE"), versionAtLeast: function (v) { var bv = (this.version).split('.'); v = (new String(v)).split('.'); for (var i = 0, m = v.length; i < m; i++) {if (bv[i] != v[i]) {return parseInt(bv[i]||"0") >= parseInt(v[i])}} return true; }, Select: function (choices) { var browser = choices[HUB.Browser]; if (browser) {return browser(HUB.Browser)} return null; } }; var AGENT = navigator.userAgent .replace(/^Mozilla\/(\d+\.)+\d+ /,"") // remove initial Mozilla, which is never right .replace(/[a-z][-a-z0-9._: ]+\/\d+[^ ]*-[^ ]*\.([a-z][a-z])?\d+ /i,"") // remove linux version .replace(/Gentoo |Ubuntu\/(\d+\.)*\d+ (\([^)]*\) )?/,""); // special case for these HUB.Browser = HUB.Insert(HUB.Insert(new String("Unknown"),{version: "0.0"}),BROWSERS); for (var browser in BROWSERS) {if (BROWSERS.hasOwnProperty(browser)) { if (BROWSERS[browser] && browser.substr(0,2) === "is") { browser = browser.slice(2); if (browser === "Mac" || browser === "PC") continue; HUB.Browser = HUB.Insert(new String(browser),BROWSERS); var VERSION = new RegExp( ".*(Version)/((?:\\d+\\.)+\\d+)|" + // for Safari and Opera10 ".*("+browser+")"+(browser == "MSIE" ? " " : "/")+"((?:\\d+\\.)*\\d+)|"+ // for one of the main browser "(?:^|\\(| )([a-z][-a-z0-9._: ]+|(?:Apple)?WebKit)/((?:\\d+\\.)+\\d+)"); // for unrecognized browser var MATCH = VERSION.exec(AGENT) || ["","","","unknown","0.0"]; HUB.Browser.name = (MATCH[1] == "Version" ? browser : (MATCH[3] || MATCH[5])); HUB.Browser.version = MATCH[2] || MATCH[4] || MATCH[6]; break; } }}; // // Initial browser-specific info (e.g., touch up version or name) // HUB.Browser.Select({ Safari: function (browser) { var v = parseInt((String(browser.version).split("."))[0]); if (v > 85) {browser.webkit = browser.version} if (v >= 534) {browser.version = "5.1"} else if (v >= 533) {browser.version = "5.0"} else if (v >= 526) {browser.version = "4.0"} else if (v >= 525) {browser.version = "3.1"} else if (v > 500) {browser.version = "3.0"} else if (v > 400) {browser.version = "2.0"} else if (v > 85) {browser.version = "1.0"} browser.isMobile = (navigator.appVersion.match(/Mobile/i) != null); browser.noContextMenu = browser.isMobile; }, Firefox: function (browser) { if ((browser.version === "0.0" || navigator.userAgent.match(/Firefox/) == null) && navigator.product === "Gecko") { var rv = navigator.userAgent.match(/[\/ ]rv:(\d+\.\d.*?)[\) ]/); if (rv) {browser.version = rv[1]} else { var date = (navigator.buildID||navigator.productSub||"0").substr(0,8); if (date >= "20111220") {browser.version = "9.0"} else if (date >= "20111120") {browser.version = "8.0"} else if (date >= "20110927") {browser.version = "7.0"} else if (date >= "20110816") {browser.version = "6.0"} else if (date >= "20110621") {browser.version = "5.0"} else if (date >= "20110320") {browser.version = "4.0"} else if (date >= "20100121") {browser.version = "3.6"} else if (date >= "20090630") {browser.version = "3.5"} else if (date >= "20080617") {browser.version = "3.0"} else if (date >= "20061024") {browser.version = "2.0"} } } browser.isMobile = (navigator.appVersion.match(/Android/i) != null || navigator.userAgent.match(/ Fennec\//) != null || navigator.userAgent.match(/Mobile/) != null); }, Opera: function (browser) {browser.version = opera.version()}, MSIE: function (browser) { browser.isIE9 = !!(document.documentMode && (window.performance || window.msPerformance)); MathJax.HTML.setScriptBug = !browser.isIE9 || document.documentMode < 9; var MathPlayer = false; try {new ActiveXObject("MathPlayer.Factory.1"); browser.hasMathPlayer = MathPlayer = true} catch (err) {} try { if (MathPlayer && !STARTUP.params.NoMathPlayer) { var mathplayer = document.createElement("object"); mathplayer.id = "mathplayer"; mathplayer.classid = "clsid:32F66A20-7614-11D4-BD11-00104BD3F987"; document.getElementsByTagName("head")[0].appendChild(mathplayer); document.namespaces.add("m","http://www.w3.org/1998/Math/MathML"); browser.mpNamespace = true; if (document.readyState && (document.readyState === "loading" || document.readyState === "interactive")) { document.write(''); browser.mpImported = true; } } else { // Adding any namespace avoids a crash in IE9 in IE9-standards mode // (any reference to document.namespaces before document.readyState is // "complete" causes an "unspecified error" to be thrown) document.namespaces.add("mjx_IE_fix","http://www.w3.org/1999/xlink"); } } catch (err) {} } }); HUB.Browser.Select(MathJax.Message.browsers); HUB.queue = BASE.Callback.Queue(); HUB.queue.Push( ["Post",STARTUP.signal,"Begin"], ["Config",STARTUP], ["Cookie",STARTUP], ["Styles",STARTUP], ["Message",STARTUP], function () { // Do Jax and Extensions in parallel, but wait for them all to complete var queue = BASE.Callback.Queue( STARTUP.Jax(), STARTUP.Extensions() ); return queue.Push({}); }, ["Menu",STARTUP], STARTUP.onLoad(), function () {MathJax.isReady = true}, // indicates that MathJax is ready to process math ["Typeset",STARTUP], ["Hash",STARTUP], ["MenuZoom",STARTUP], ["Post",STARTUP.signal,"End"] ); })("MathJax"); }}