-
Notifications
You must be signed in to change notification settings - Fork 43
/
Copy pathaction.js
152 lines (141 loc) · 4.36 KB
/
action.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
require('mathjax-full/js/util/asyncLoad/node.js');
// This is loaded to ensure `STATE.ENRICHED` is set.
require('mathjax-full/js/a11y/semantic-enrich.js');
const {newState, STATE} = require('mathjax-full/js/core/MathItem.js');
//
// Remove the data-semantic-* attributes other than data-semantic-speech
//
function removeSemanticData(math) {
math.root.walkTree(node => {
const attributes = node.attributes.getAllAttributes();
delete attributes.xmlns; // some internal nodes get this attribute for some reason
for (const name of Object.keys(attributes)) {
if (name.substr(0, 14) === 'data-semantic-' && name !== 'data-semantic-speech') {
delete attributes[name];
}
}
});
}
//
// Moves all speech elements into aria-labels for SVG output. This allows for
// elements without containers to have some limited exploration.
//
// Note, that here we cannot walk the source tree, as alterations are to be
// done on the typeset element, if it is a SVG node.
//
function speechToRoles(math) {
const root = math.typesetRoot;
const adaptor = math.adaptor;
const svg = adaptor.tags(root, 'svg')[0];
if (!svg) return;
adaptor.removeAttribute(svg, 'aria-hidden');
adaptor.removeAttribute(svg, 'role');
const children = [svg];
while (children.length) {
let child = children.shift();
if (adaptor.kind(child) === '#text') continue;
if (adaptor.hasAttribute(child, 'data-semantic-speech')) {
let text = adaptor.getAttribute(child, 'data-semantic-speech');
adaptor.setAttribute(child, 'aria-label', text);
adaptor.setAttribute(child, 'role', 'img');
adaptor.removeAttribute(child, 'data-semantic-speech');
}
children.push(...adaptor.childNodes(child));
}
}
//
// Sets the rendered roots role to image if their is an aria-label to speak
// custom elements.
//
function roleImg(math) {
const adaptor = math.adaptor;
const root = math.typesetRoot;
if (adaptor.hasAttribute(root, 'aria-label')) {
adaptor.setAttribute(root, 'role', 'img');
}
}
//
// Configures SRE from key value pairs by populating MathJax's config options.
//
exports.dataPairs = function(data) {
const config = {};
if (data) {
for (let i = 0, key; key = data[i]; i++) {
let value = data[++i];
config[key] = value || false;
}
}
return config;
};
exports.sreconfig = function(data) {
const config = exports.dataPairs(data);
if (!MathJax.config.options) {
MathJax.config.options = {};
}
if (!MathJax.config.options.sre) {
MathJax.config.options.sre = {};
}
Object.assign(MathJax.config.options.sre, config);
};
//
// Let's define some new states for enlisting new renderActions into the queue.
//
// STATE.ENRICHED is the priority of the enrichment. Enrichment happens before
// elements are rendered (i.e., typeset) and therefore all attributes added during
// enrichment will be part of the MathItem.
//
// STATE 1000 is so hight that any other render action should be done by
// then. That is, the MathItem is fully typeset in the document.
//
newState('SIMPLIFY', STATE.ENRICHED + 1);
newState('ROLE', 1000);
newState('DESCRIBE', STATE.ROLE + 1);
//
// The renderActions needed for manipulating MathItems with speech entries.
// We define three render actions, each with two functions:
// The first function is the one for when the document's render() method is called.
// The second is for when a MathItem's render(), rerender() or convert() method is called.
//
// simplify: Removes the data-semantic-attributes except speech directly after enrichment.
//
// role: Adds an aria role to the container element so aria-labels are spoken on custom elements.
// This happens after typesetting.
//
// describe: Rewrites speech attributes into aria-labels with img roles in SVGs.
// This happens after container elements are rewritten.
//
exports.speechAction = {
simplify: [
STATE.SIMPLIFY,
(doc) => {
for (const math of doc.math) {
removeSemanticData(math);
}
},
(math, doc) => {
removeSemanticData(math);
}
],
role: [
STATE.ROLE,
(doc) => {
for (const math of doc.math) {
roleImg(math);
}
},
(math, doc) => {
roleImg(math);
}
],
describe: [
STATE.DESCRIBE,
(doc) => {
for (const math of doc.math) {
speechToRoles(math);
}
},
(math, doc) => {
speechToRoles(math);
}
]
};