-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
274 lines (252 loc) · 10.2 KB
/
index.html
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>MathCAT Demo</title>
<!-- MathJax-related stuff -->
<script>
MathJax = {
loader: {load: ['input/tex', '[tex]/mhchem', 'input/asciimath', '[mml]/mml3']},
tex: {packages: {'[+]': ['mhchem']}},
options: {
enableMenu: false, // interferes with navigation
// enableAssistiveMml: true,
enableEnrichment: false,
enableExplorer: false,
}
};
// Convert MathML, TeX (properly delimitated), and ASCIIMath (properly delimitated)
function ConvertToMathML(math_str, math_format) {
MathJax.startup.defaultReady();
let options = {display: true};
let mathml;
try {
if (math_format == 'ASCIIMath') {
mathml = MathJax.asciimath2mml(math_str, options);
} else if (math_format == 'TeX') {
mathml = MathJax.tex2mml(math_str, options);
} else { // should be "MathML"
mathml = MathJax.mathml2mml(math_str, options);
};
} catch (e) {
console.error("MathJax conversion error: ", e);
mathml = "<math><merror><mtext>MathJax conversion error</mtext></merror></math>"
}
console.log("ConvertToMathML:\n" + mathml.toString());
return mathml;
}
// Get the MathJax version of the MathML
function ConvertToCHTML(mathml) {
MathJax.startup.defaultReady();
return MathJax.mathml2chtml(mathml);
}
// helper function because Rust-side wasn't working per docs
function GetTextOfElement(id) {
let element = document.getElementById(id);
if (element) {
return element.value;
} else {
return "";
}
}
function HighlightNavigationElement(id) {
if (window['MathCAT'].navigationMark) {
removeHighlight(window['MathCAT'].navigationMark, 'nav-highlight');
};
window['MathCAT'].navigationMark = id
highlightID(id, 'nav-highlight');
}
function RemoveFocus(id) {
document.getElementById(id).dispatchEvent( new Event("focusout") )
}
function SetCookie(cookie_value) {
let answer = document.cookie;
if (cookie_value) {
let cookieArray = cookie_value.split(";");
for(let i=0; i<cookieArray.length; i++){
document.cookie=cookieArray[i] + "; max-age=31536000; SameSite=Lax";
}
}
return answer;
}
function RustInit(str) {
eval(str);
}
// For debugging, popup a file selector to reload the file instead of recompiling and waiting
function GetFile() {
var input = document.createElement('input');
input.type = 'file';
input.accept='.yaml';
input.onchange = e => {
var file = e.target.files[0];
var reader = new FileReader();
reader.readAsText(file,'UTF-8');
reader.onload = readerEvent => {
var content = readerEvent.target.result; // this is the content of the file
load_yaml_file(file.name, content ); // 'file.name' is only the last component (security reasons)
}
}
input.click(); // triggers 'onchange'
}
</script>
<script id="MathJax-script" async="" src="https://cdn.jsdelivr.net/npm/[email protected]/tex-mml-chtml.js"></script>
<!-- AWS Poly speech/sync highlighting stuff -->
<script src="https://sdk.amazonaws.com/js/aws-sdk-2.1033.0.min.js"></script>
<script>
window['MathCAT'] = {};
// Function invoked by button click
function SpeakText(text, lang) {
if (window['MathCAT'].readAloudAudio) {
// pause current playback to allow new playback
window['MathCAT'].readAloudAudio.pause();
removeHighlight(window['MathCAT'].readAloudCurrentMark, 'sync-highlight');
console.warn("paused playback");
}
// empty text is a way of stopping speech
if (!text) {
return;
}
const CAP_LETTER = /(<say-as interpret-as='characters'>[A-Z]<\/say-as>)/g;
const PITCH_CHANGE = "<prosody pitch='+90%'>$1</prosody>"
text = text.replace(CAP_LETTER, PITCH_CHANGE);
console.log("TTS:", text);
// It doesn't seem like I can specify a language and Polly pick the voice, so I'm hardcoding a voice mapping
langToVoice = {
"da": "Naja",
"de": "Vicki",
"en": "Joanna",
"es": "Lucia",
"fi": "Suvi",
"fr": "Léa",
// no id (Indonesian) voice
"is": "Dora",
"it": "Bianca",
"nb": "Ida",
"nl": "Laura",
"pl": "Ola",
"pt": "Vitoria",
"sv": "Astrid",
// no vi (Vietnamese) voice
"zh-cn": "Hiujin",
"zh-tw": "Zhiyu",
}
// Create the JSON parameters for getSynthesizeSpeechUrl
var speechParams = {
OutputFormat: "json",
Text: "<speak>" + text + "</speak>",
TextType: "ssml",
SpeechMarkTypes: ["ssml"],
VoiceId: langToVoice[lang] || "Joanna",
Engine: "standard", // The neural engine currently does not support ssml marks
};
// Create the Polly service object and presigner object
speakTextWithPolly(speechParams);
}
// the following is adapted from https://github.com/arnog/mathlive/blob/master/src/editor/speech-read-aloud.ts
function speakTextWithPolly(speechParams) {
var polly = new AWS.Polly({apiVersion: '2016-06-10'});
var signer = new AWS.Polly.Presigner(speechParams, polly);
polly.synthesizeSpeech(speechParams, (err, data) => {
if (err) {
console.warn('speakTextWithPolly error:', err, err.stack);
return;
}
if (!data || !data.AudioStream) {
return;
}
const response = new TextDecoder('utf-8').decode(
new Uint8Array(data.AudioStream)
);
// window['MathCAT'] = {};
window['MathCAT'].readAloudMarks = response
.split('\n')
.map((x) => (x ? JSON.parse(x) : {}));
window['MathCAT'].readAloudTokens = [];
for (const mark of window['MathCAT'].readAloudMarks) {
if (mark.value) {
window['MathCAT'].readAloudTokens.push(mark.value);
}
}
window['MathCAT'].readAloudCurrentMark = '';
// Request the audio
speechParams.OutputFormat = 'mp3';
speechParams.SpeechMarkTypes = [];
polly.synthesizeSpeech(speechParams, function (err, data) {
if (err) {
console.warn('signer.synthesizeSpeech(' ,text, ') error:', err, err.stack);
return;
}
if (!data || !data.AudioStream) {
return;
}
const uInt8Array = new Uint8Array(data.AudioStream);
const blob = new Blob([uInt8Array.buffer], {
type: 'audio/mpeg',
});
const url = URL.createObjectURL(blob);
if (!window['MathCAT'].readAloudAudio) {
window['MathCAT'].readAloudAudio = new Audio();
window['MathCAT'].readAloudAudio.addEventListener(
'ended',
() => {
removeHighlight(window['MathCAT'].readAloudCurrentMark, 'sync-highlight');
window['MathCAT'].readAloudAudio = null; // flag indicate not currently speaking
}
);
window['MathCAT'].readAloudAudio.addEventListener(
'timeupdate',
() => {
let value = '';
// The target, the atom we're looking for, is the one matching the current audio
// plus 100 ms. By anticipating it a little bit, it feels more natural, otherwise it
// feels like the highlighting is trailing the audio.
const target =
window['MathCAT'].readAloudAudio.currentTime *
1000 +
100;
// Find the smallest element which is bigger than the target time
for (const mark of window['MathCAT'].readAloudMarks) {
if (mark.time < target) {
value = mark.value;
}
}
if (window['MathCAT'].readAloudCurrentMark !== value) {
removeHighlight(window['MathCAT'].readAloudCurrentMark, 'sync-highlight');
window['MathCAT'].readAloudCurrentMark = value;
highlightID( window['MathCAT'].readAloudCurrentMark, 'sync-highlight');
}
}
);
} else {
window['MathCAT'].readAloudAudio.pause();
}
// finally, set up the audio source and play it, trigging the 'timeupdate' events.
window['MathCAT'].readAloudAudio.src = url;
window['MathCAT'].readAloudAudio.play();
});
});
}
function removeHighlight(id, className) {
if (id) {
let element = document.getElementById(id);
if (element) {
element.classList.remove(className);
}
}
}
function highlightID(id, className) {
let element = document.getElementById(id);
if (element) {
element.classList.add(className);
}
}
</script>
<script type="module">
import {load_yaml_file} from '/MathCATDemo/index-844b68763f8ae55f.js';
window.load_yaml_file = load_yaml_file; // put in global scope so it can be called from global scope functions
</script>
<link data-trunk rel="sass" href="index.scss" />
<!-- for debugging -->
<link data-trunk rel="rust" data-keep-debug data-wasm-opt="0" />
</head>
</html>