From f3273f6e6297a8f6ad72f432236c695636bcb39b Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 23 Jul 2015 11:33:07 -0400 Subject: [PATCH 1/8] First draft of assistive MathML extension. Still needs configuration, and we need to make sure the mml2jax exteion doesn't try to process the hidden MathML. --- unpacked/extensions/AssistiveMML.js | 109 ++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 unpacked/extensions/AssistiveMML.js diff --git a/unpacked/extensions/AssistiveMML.js b/unpacked/extensions/AssistiveMML.js new file mode 100644 index 000000000..299844018 --- /dev/null +++ b/unpacked/extensions/AssistiveMML.js @@ -0,0 +1,109 @@ +/* -*- Mode: Javascript; indent-tabs-mode:nil; js-indent-level: 2 -*- */ +/* vim: set ts=2 et sw=2 tw=80: */ + +/************************************************************* + * + * MathJax/extensions/AssistiveMML.js + * + * Implements an extension that inserts hidden MathML into the + * page for screen readers or other asistive technology. + * + * --------------------------------------------------------------------- + * + * Copyright (c) 2015 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. + */ + +(function (AJAX,CALLBACK,HUB,HTML) { + + var AssistiveMML = MathJax.Extension["AssistiveMML"] = { + version: "2.6", + + // + // For each jax in the state, look up the frame. + // If the jax doesn't use NativeMML and hasn't already been handled: + // Get the MathML for the jax, taking resets into account. + // Add a data-mathml attribute to the frame, and + // Create a span that is not visible on screen and put the MathML in it, + // and add it to the frame. + // When all the jax are processed, call the callback. + // + HandleMML: function (state) { + var m = state.jax.length, jax, mml, frame, span; + while (state.i < m) { + jax = state.jax[state.i]; + frame = document.getElementById(jax.inputID+"-Frame"); + if (jax.outputJax !== "NativeMML" && frame && !frame.getAttribute("data-mathml")) { + try { + mml = jax.root.toMathML("").replace(/\n */g,"").replace(//g,""); + } catch (err) { + if (!err.restart) throw err; // an actual error + return MathJax.Callback.After(["HandleMML",this,state],err.restart); + } + frame.setAttribute("data-mathml",mml); + span = HTML.Element("span",{ + isMathJax: true, + style:{ + position:"absolute!important", + left:"-100000px", top:"auto", height:"1px", width:"1px", + "padding-top":"1px", display:"block" + } + }); + span.innerHTML = mml; + frame.appendChild(span); + } + state.i++; + } + state.callback(); + }, + + // + // The hook for the End Math signal. + // This sets up a state object that lists the jax and index into the jax, + // and a dummy callback that is used to synchronizing with MathJax. + // It will be called when the jax are all processed, and that will + // let the MathJax queue continue (it will block until then). + // + EndMathHook: function (node) { + var state = { + jax: HUB.getAllJax(node), i: 0, + callback: MathJax.Callback(function () { + console.log("MathML time: "+((new Date().getTime())-state.start)); + }), + start: new Date().getTime() + }; + this.HandleMML(state); + return state.callback; + } + + }; + + // + // Call the hook when math has been processed. + // + HUB.Register.MessageHook("End Math",function (msg) {AssistiveMML.EndMathHook(msg[1])}); + + HUB.Startup.signal.Post("AssistiveMML Ready"); + +})(MathJax.Ajax,MathJax.Callback,MathJax.Hub,MathJax.HTML); + +// +// Make sure the toMathML extension is loaded before we signal +// the load complete for this extension. +// +MathJax.Callback.Queue( + ["Require",MathJax.Ajax,"[MathJax]/extensions/toMathML.js"], + ["loadComplete",MathJax.Ajax,"[MathJax]/extensions/AssistiveMML.js"] +); + From d8357a2d03546576a368a25a9d860e1e796b1cfe Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 23 Jul 2015 15:14:02 -0400 Subject: [PATCH 2/8] Change CSS to that recommended by the Yahoo Accessibility team at https://developer.yahoo.com/blogs/ydn/clip-hidden-content-better-accessibility-53456.html --- unpacked/extensions/AssistiveMML.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/unpacked/extensions/AssistiveMML.js b/unpacked/extensions/AssistiveMML.js index 299844018..f668e553a 100644 --- a/unpacked/extensions/AssistiveMML.js +++ b/unpacked/extensions/AssistiveMML.js @@ -55,9 +55,15 @@ span = HTML.Element("span",{ isMathJax: true, style:{ - position:"absolute!important", - left:"-100000px", top:"auto", height:"1px", width:"1px", - "padding-top":"1px", display:"block" + position:"absolute", + clip: (HUB.Browser.isMSIE && (document.documentMode||0) < 8 ? + "rect(1px 1px 1px 1px)" : "rect(1px, 1px, 1px, 1px)"), + padding: "1 0 0 0", + border: "0", + height: "1px", + width: "1px", + overflow: "hidden", + display:"block" } }); span.innerHTML = mml; From f0cc437b1e61d05c6c46d6b64a2a947c815272bf Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 23 Jul 2015 16:40:18 -0400 Subject: [PATCH 3/8] Refactor code to make a separate configuration section with dynamically generated stylesheet. Use a class for the added MathML rather than explicit CSS. --- unpacked/extensions/AssistiveMML.js | 97 ++++++++++++++++------------- 1 file changed, 55 insertions(+), 42 deletions(-) diff --git a/unpacked/extensions/AssistiveMML.js b/unpacked/extensions/AssistiveMML.js index f668e553a..59513e8f2 100644 --- a/unpacked/extensions/AssistiveMML.js +++ b/unpacked/extensions/AssistiveMML.js @@ -26,9 +26,54 @@ */ (function (AJAX,CALLBACK,HUB,HTML) { - + var SETTINGS = HUB.config.menuSettings; + var AssistiveMML = MathJax.Extension["AssistiveMML"] = { version: "2.6", + + config: { + disabled: false, + styles: { + ".MJX_Assistive_MathML": { + position:"absolute!important", + clip: (HUB.Browser.isMSIE && (document.documentMode||0) < 8 ? + "rect(1px 1px 1px 1px)" : "rect(1px, 1px, 1px, 1px)"), + padding: "1px 0 0 0!important", + border: "0!important", + height: "1px!important", + width: "1px!important", + overflow: "hidden!important", + display:"block!important" + } + } + }, + + Config: function () { + if (!this.config.disabled && SETTINGS.assistiveMML == null) + HUB.Config({menuSettings:{assistiveMML:true}}); + AJAX.Styles(this.config.styles); + HUB.Register.MessageHook("End Math",function (msg) {AssistiveMML.EndMathHook(msg[1])}); + }, + + // + // The hook for the End Math signal. + // This sets up a state object that lists the jax and index into the jax, + // and a dummy callback that is used to synchronizing with MathJax. + // It will be called when the jax are all processed, and that will + // let the MathJax queue continue (it will block until then). + // + EndMathHook: function (node) { + if (!SETTINGS.assistiveMML) return; + var state = { + jax: HUB.getAllJax(node), i: 0, + callback: MathJax.Callback(function () { + console.log("MathML time: "+((new Date().getTime())-state.start)); + }), + start: new Date().getTime() + }; + this.HandleMML(state); + return state.callback; + }, // // For each jax in the state, look up the frame. @@ -52,64 +97,32 @@ return MathJax.Callback.After(["HandleMML",this,state],err.restart); } frame.setAttribute("data-mathml",mml); - span = HTML.Element("span",{ - isMathJax: true, - style:{ - position:"absolute", - clip: (HUB.Browser.isMSIE && (document.documentMode||0) < 8 ? - "rect(1px 1px 1px 1px)" : "rect(1px, 1px, 1px, 1px)"), - padding: "1 0 0 0", - border: "0", - height: "1px", - width: "1px", - overflow: "hidden", - display:"block" - } - }); + span = HTML.addElement(frame,"span",{ + isMathJax: true, className: "MJX_Assistive_MathML" + }); span.innerHTML = mml; - frame.appendChild(span); } state.i++; } state.callback(); - }, - - // - // The hook for the End Math signal. - // This sets up a state object that lists the jax and index into the jax, - // and a dummy callback that is used to synchronizing with MathJax. - // It will be called when the jax are all processed, and that will - // let the MathJax queue continue (it will block until then). - // - EndMathHook: function (node) { - var state = { - jax: HUB.getAllJax(node), i: 0, - callback: MathJax.Callback(function () { - console.log("MathML time: "+((new Date().getTime())-state.start)); - }), - start: new Date().getTime() - }; - this.HandleMML(state); - return state.callback; } }; - // - // Call the hook when math has been processed. - // - HUB.Register.MessageHook("End Math",function (msg) {AssistiveMML.EndMathHook(msg[1])}); - HUB.Startup.signal.Post("AssistiveMML Ready"); })(MathJax.Ajax,MathJax.Callback,MathJax.Hub,MathJax.HTML); // // Make sure the toMathML extension is loaded before we signal -// the load complete for this extension. +// the load complete for this extension. Then wait for the end +// of the user configuration before configuring this extension. // MathJax.Callback.Queue( ["Require",MathJax.Ajax,"[MathJax]/extensions/toMathML.js"], - ["loadComplete",MathJax.Ajax,"[MathJax]/extensions/AssistiveMML.js"] + ["loadComplete",MathJax.Ajax,"[MathJax]/extensions/AssistiveMML.js"], + function () { + MathJax.Hub.Register.StartupHook("End Config",["Config",MathJax.Extension.AssistiveMML]); + } ); From 82e0daf2c6c369aaa55a85fa646be427c18fbe89 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Thu, 23 Jul 2015 17:09:43 -0400 Subject: [PATCH 4/8] Add a hidden menu item that controls the AssistiveMML extension. --- unpacked/extensions/MathMenu.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/unpacked/extensions/MathMenu.js b/unpacked/extensions/MathMenu.js index 08b32b2c6..2c910a29d 100644 --- a/unpacked/extensions/MathMenu.js +++ b/unpacked/extensions/MathMenu.js @@ -1115,7 +1115,8 @@ ITEM.RADIO("MathML", "renderer", {action: MENU.Renderer, value:"NativeMML"}), ITEM.RADIO("SVG", "renderer", {action: MENU.Renderer}), ITEM.RULE(), - ITEM.CHECKBOX("Fast Preview", "CHTMLpreview") + ITEM.CHECKBOX("Fast Preview", "CHTMLpreview"), + ITEM.CHECKBOX("Assistive MathML", "assistiveMML", {hidden:!CONFIG.showAssistiveMML}) ), ITEM.SUBMENU("MathPlayer", {hidden:!HUB.Browser.isMSIE || !CONFIG.showMathPlayer, disabled:!HUB.Browser.hasMathPlayer}, @@ -1202,6 +1203,10 @@ MENU.cookie.showLocale = CONFIG.showLocale = show; MENU.saveCookie(); MENU.menu.Find("Language").hidden = !show; }; + MENU.showAssistiveMML = function (show) { + MENU.cookie.showAssistiveMML = CONFIG.showAssistiveMML = show; MENU.saveCookie(); + MENU.menu.Find("Math Settings","Math Renderer","Assistive MathML").hidden = !show; + }; MathJax.Hub.Register.StartupHook("HTML-CSS Jax Ready",function () { if (!MathJax.OutputJax["HTML-CSS"].config.imageFont) From 1dff53daa8447c5f4f250879e614ca531c34c51a Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 24 Jul 2015 07:19:27 -0400 Subject: [PATCH 5/8] Make sure mml2jax doesn't process the assistive MathML. --- unpacked/extensions/mml2jax.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unpacked/extensions/mml2jax.js b/unpacked/extensions/mml2jax.js index a348bc55e..8edb8d5c1 100644 --- a/unpacked/extensions/mml2jax.js +++ b/unpacked/extensions/mml2jax.js @@ -99,8 +99,8 @@ MathJax.Extension.mml2jax = { } for (var i = 0, m = math.length; i < m; i++) { var parent = math[i].parentNode; - if (parent && parent.className !== preview && !math[i].prefix === !namespace) - {array.push(math[i])} + if (parent && parent.className !== preview && + !parent.isMathJax && !math[i].prefix === !namespace) array.push(math[i]); } }, From 8d38947024039a2833eaa155a47d8cbc8ed3a8c3 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 24 Jul 2015 07:20:10 -0400 Subject: [PATCH 6/8] Add aria attributes for the normal and assistive output. --- unpacked/extensions/AssistiveMML.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unpacked/extensions/AssistiveMML.js b/unpacked/extensions/AssistiveMML.js index 59513e8f2..ca89fd072 100644 --- a/unpacked/extensions/AssistiveMML.js +++ b/unpacked/extensions/AssistiveMML.js @@ -101,6 +101,8 @@ isMathJax: true, className: "MJX_Assistive_MathML" }); span.innerHTML = mml; + frame.firstChild.setAttribute("aria-hidden","true"); + span.setAttribute("aria-readonly","true"); } state.i++; } From 4149a87a962c08d7b636c65ae541927622caad7b Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 24 Jul 2015 09:25:11 -0400 Subject: [PATCH 7/8] Autoload AssisitveMML if the menu item is set and the extension isn't loaded. --- unpacked/MathJax.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unpacked/MathJax.js b/unpacked/MathJax.js index 01d5b81d9..8c181f416 100644 --- a/unpacked/MathJax.js +++ b/unpacked/MathJax.js @@ -2554,6 +2554,8 @@ MathJax.Hub.Startup = { } if (config.menuSettings.CHTMLpreview && !MathJax.Extension["CHTML-preview"]) {MathJax.Hub.config.extensions.push("CHTML-preview.js")} + if (config.menuSettings.assistiveMML && !MathJax.Extension.AssistiveMML) + {MathJax.Hub.config.extensions.push("AssistiveMML.js")} },MathJax.Hub.config], ["Post",this.signal,"End Cookie"] ); From 89f15693f7a86b942c5750e3a33922b414c355e9 Mon Sep 17 00:00:00 2001 From: "Davide P. Cervone" Date: Fri, 11 Sep 2015 12:26:28 -0400 Subject: [PATCH 8/8] Remove debugging log message and timer --- unpacked/extensions/AssistiveMML.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/unpacked/extensions/AssistiveMML.js b/unpacked/extensions/AssistiveMML.js index ca89fd072..59b1c795f 100644 --- a/unpacked/extensions/AssistiveMML.js +++ b/unpacked/extensions/AssistiveMML.js @@ -66,10 +66,7 @@ if (!SETTINGS.assistiveMML) return; var state = { jax: HUB.getAllJax(node), i: 0, - callback: MathJax.Callback(function () { - console.log("MathML time: "+((new Date().getTime())-state.start)); - }), - start: new Date().getTime() + callback: MathJax.Callback({}) }; this.HandleMML(state); return state.callback;