forked from agrbin/svgtex
-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.js
executable file
·339 lines (296 loc) · 9.33 KB
/
main.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
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
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
// Version
var VERSION = '0.1-dev';
var system = require('system');
var args = system.args;
var server = require('webserver').create();
var page = require('webpage').create();
var fs = require('fs');
var usage =
'Usage: phantomjs main.js [options]\n' +
'Options:\n' +
' -h,--help Print this usage message and exit\n' +
' -v,--version Print the version number and exit\n' +
' -p,--port <port> IP port on which to start the server\n' +
' -r,--requests <num> Process this many requests and then exit. -1 means \n' +
' never stop.\n' +
' -b,--bench <page> Use alternate bench page (default is index.html)\n' +
' -d,--debug Enable verbose debug messages\n';
var port = 16000;
var requests_to_serve = -1;
var bench_page = 'index.html';
var debug = false;
// Parse command-line options. This keeps track of which one we are on
var arg_num = 1;
// Helper function for option parsing. Allow option/arg in any of
// these forms:
// 1: -p 1234
// 2: --po 1234
// 3: --po=1234
// Returns:
// 1 or 2: true
// 3: '1234'
// else: false
function option_match(name, takes_optarg, arg) {
var ieq = arg.indexOf('=');
var arg_key;
if (arg.substr(0, 2) == '--' && (takes_optarg && ieq != -1)) { // form #3
arg_key = arg.substring(2, ieq);
if (name.substr(0, arg_key.length) == arg_key) {
return arg.substr(ieq + 1);
}
return false;
}
if (arg.substr(0, 2) == '--') {
arg_key = arg.substr(2);
}
else if (arg.substr(0, 1) == '-') {
arg_key = arg.substr(1);
}
else {
return false;
}
return name.substr(0, arg_key.length) == arg_key;
}
// This helper handles one option that takes an option-argument
function option_arg_parse(name) {
var arg = args[arg_num];
match = option_match(name, true, arg);
if (!match) return false;
if (typeof match != 'string') {
if (arg_num + 1 < args.length) {
arg_num++;
match = args[arg_num];
}
else {
phantom.exit(1);
}
}
if (name == 'port') {
port = match - 0;
}
else if (name == 'requests') { requests_to_serve = match - 0; }
else if (name == 'bench') { bench_page = match; }
arg_num++;
return true;
}
var to_exit = false;
while (arg_num < args.length) {
var arg = args[arg_num];
if (option_match('help', false, arg)) {
console.log(usage);
phantom.exit(0);
break;
}
if (option_match('version', false, arg)) {
console.log('svgtex version ' + VERSION);
phantom.exit(0);
break;
}
if (option_match('debug', false, arg)) {
debug = true;
arg_num++;
continue;
}
if (option_arg_parse('port')) {
continue;
}
if (option_arg_parse('requests')) {
continue;
}
if (option_arg_parse('bench')) {
continue;
}
console.error("Unrecognized argument: '" + arg + "'. Use '--help' for usage info.");
phantom.exit(1);
break;
}
// activeRequests holds information about any active MathJax requests. It is
// a hash, with a sequential number as the key. request_num gets incremented
// for *every* HTTP request, but only requests that get passed to MathJax have an
// entry in activeRequests. Each element of activeRequests
// is an array of [<response object>, <start time>].
var request_num = 0;
var activeRequests = {};
// This will hold the test HTML form, which is read once, the first time it is
// requested, from test.html.
var test_form_filename = 'test.html';
var test_form = null;
var service = null;
// thanks to:
// stackoverflow.com/questions/5515869/string-length-in-bytes-in-javascript
function utf8_strlen(str) {
var m = encodeURIComponent(str).match(/%[89ABab]/g);
return str.length + (m ? m.length : 0);
}
// This is the callback that gets invoked after the math has been converted.
// The argument, data, is an array that holds the three arguments from the
// process() function in engine.js: the request number, the original
// text, and either the converted svg, or an array of one element
// that holds an error message.
page.onCallback = function(data) {
var num = data[0],
src = data[1],
svg_or_error = data[2],
record = activeRequests[num],
resp = record[0],
start_time = record[1],
duration = (new Date()).getTime() - start_time,
duration_msg = ', took ' + duration + 'ms.';
if ((typeof svg_or_error) === 'string') {
resp.statusCode = 200;
resp.setHeader("Content-Type", "image/svg+xml; charset=utf-8");
resp.setHeader("Content-Length", utf8_strlen(svg_or_error));
resp.write(svg_or_error);
console.log(num + ': ' + src.substr(0, 30) + '.. ' +
src.length + 'B query, OK ' + svg_or_error.length + 'B result' +
duration_msg);
}
else {
resp.statusCode = 400; // bad request
resp.write(svg_or_error[0]);
console.log(num, src.substr(0, 30) + '.. ' +
src.length + 'B query, error: ' + svg_or_error[0] + duration_msg);
}
resp.close();
delete(activeRequests[num]);
if (!(--requests_to_serve)) {
phantom.exit();
}
}
// Parse the request and return an object with the parsed values.
// It will either have an error indication, e.g.
// { num: 5, status_code: 400, error: "message" }
// Or indicate that the test form should be returned, e.g.
// { num: 5, test_form: 1 }
// or a valid request, e.g.
// { num: 5, type: 'tex', q: 'n^2', width: '500' }
function parse_request(req) {
// Set any defaults here:
var query = {
num: request_num++,
type: 'tex',
width: null
};
if (debug) {
if (req.method == 'POST') {
console.log(" req.postRaw = '" + req.postRaw + "'");
}
else {
console.log(" req.url = '" + req.url + "'");
}
}
var qs; // will store the content of the (tex or mml) math
if (req.method == 'GET') {
var url = req.url;
if (url == '' || url == '/') {
// User has requested the test form
if (test_form == null && fs.isReadable(test_form_filename)) {
test_form = fs.read(test_form_filename); // set the global variable
}
if (test_form != null) {
query.test_form = 1;
}
else {
query.status_code = 500; // Internal server error
query.error = "Can't find test form";
}
return query;
}
var iq = url.indexOf("?");
if (iq == -1) { // no query string
query.status_code = 400; // bad request
query.error = "Missing query string";
return query;
}
qs = url.substr(iq+1);
}
else if (req.method == 'POST') {
if (typeof req.postRaw !== 'string') { // which can happen
query.status_code = 400; // bad request
query.error = "Missing post content";
return query;
}
qs = req.postRaw;
}
else { // method is not GET or POST
query.status_code = 400; // bad request
query.error = "Method " + req.method + " not supported";
return query;
}
var param_strings = qs.split(/&/);
var num_param_strings = param_strings.length;
for (var i = 0; i < num_param_strings; ++i) {
var ps = param_strings[i];
var ie = ps.indexOf('=');
if (ie == -1) {
query.status_code = 400; // bad request
query.error = "Can't decipher request parameter";
return query;
}
var key = ps.substr(0, ie);
var val = decodeURIComponent(ps.substr(ie+1).replace(/\+/g, ' '));
if (key == 'type') {
query.type = val;
}
else if (key == 'q') {
query.q = val;
}
else if (key == 'width') {
query.width = parseInt(val) || null;
}
else {
query.status_code = 400; // bad request
query.error = "Unrecognized parameter name: " + key;
return query;
}
}
if (!query.q) { // no source math
query.status_code = 400; // bad request
query.error = "No source math detected in input";
return query;
}
return query;
}
function listenLoop() {
// Set up the listener that will respond to every new request
service = server.listen('127.0.0.1:' + port, function(req, resp) {
var query = parse_request(req);
var request_num = query.num;
console.log(request_num + ': ' + "received: " + req.method + " " +
req.url.substr(0, 30) + " ..");
if (query.test_form) {
console.log(request_num + ": returning test form");
resp.write(test_form);
resp.close();
}
else {
if (query.error) {
console.log(request_num + ": error: " + query.error);
resp.statusCode = query.status_code;
resp.write(query.error);
resp.close();
}
else {
// The following evaluates the function argument in the page's context,
// with query -> _query. That, in turn, calls the process() function in
// engine.js, which causes MathJax to render the math. The callback is
// PhantomJS's callPhantom() function, which in turn calls page.onCallback(),
// above. This just queues up the call, and will return at once.
activeRequests[request_num] = [resp, (new Date()).getTime()];
page.evaluate(function(_query) {
window.engine.process(_query, window.callPhantom);
}, query);
}
}
});
if (!service) {
console.log("server failed to start on port " + port);
phantom.exit(1);
}
else {
console.log("Server started on port " + port);
console.log("Point your browser at http://localhost:" + port + " for a test form.");
}
}
console.log("Loading bench page " + bench_page);
page.open(bench_page, listenLoop);