Fixed module installation code.
Ylian Saint-Hilaire committed
Apr 5, 2019 at 09:21 UTC
1e930c60f2ee1cbfac835b77aabdd5d586c0b865
3 files changed
+223
-164
meshcentral.js
+7
-3
@@ -66,15 +66,15 @@ function CreateMeshCentralServer(config, args) {
66
67
// Setup the default configuration and files paths
68
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
69
+ obj.parentpath = obj.path.join(__dirname, '../..');
70
obj.datapath = obj.path.join(__dirname, '../../meshcentral-data');
71
obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
71
- obj.parentpath = obj.path.join(__dirname, '../..');
72
if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsPath = obj.path.join(__dirname, '../../meshcentral-web/views'); } else { obj.webViewsPath = obj.path.join(__dirname, 'views'); }
73
if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicPath = obj.path.join(__dirname, '../../meshcentral-web/public'); } else { obj.webPublicPath = obj.path.join(__dirname, 'public'); }
74
} else {
75
+ obj.parentpath = __dirname;
76
obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
77
obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
77
- obj.parentpath = obj.path.join(__dirname, '..');
78
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsPath = obj.path.join(__dirname, '../meshcentral-web/views'); } else { obj.webViewsPath = obj.path.join(__dirname, 'views'); }
79
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicPath = obj.path.join(__dirname, '../meshcentral-web/public'); } else { obj.webPublicPath = obj.path.join(__dirname, 'public'); }
80
}
@@ -1646,9 +1646,13 @@ var InstallModuleChildProcess = null;
1646
function InstallModule(modulename, func, tag1, tag2) {
1647
console.log('Installing ' + modulename + '...');
1648
var child_process = require('child_process');
1649
+ var parentpath = __dirname;
1650
+
1651
+ // Get the working directory
1652
+ if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
1653
1654
// Looks like we need to keep a global reference to the child process object for this to work correctly.
1651
- InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 10000, cwd: obj.parentpath }, function (error, stdout, stderr) {
1655
+ InstallModuleChildProcess = child_process.exec('npm install --no-optional --save ' + modulename, { maxBuffer: 512000, timeout: 10000, cwd: parentpath }, function (error, stdout, stderr) {
1656
InstallModuleChildProcess = null;
1657
if (error != null) {
1658
console.log('ERROR: Unable to install required module "' + modulename + '". MeshCentral may not have access to npm, or npm may not have suffisent rights to load the new module. Try "npm install ' + modulename + '" to manualy install this module.\r\n');
package.json
+1
-1
@@ -1,6 +1,6 @@
1
{
2
"name": "meshcentral",
3
- "version": "0.3.1-u",
3
+ "version": "0.3.1-w",
4
"keywords": [
5
"Remote Management",
6
"Intel AMT",
public/scripts/amt-terminal-0.0.2.js
+215
-160
@@ -4,6 +4,8 @@
4
* @version v0.0.2c
5
*/
6
7
+// https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
8
+
9
// Construct a MeshServer object
10
var CreateAmtRemoteTerminal = function (divid) {
11
var obj = {};
@@ -32,10 +34,14 @@ var CreateAmtRemoteTerminal = function (divid) {
34
var _termstate = 0;
35
var _escNumber = [];
36
var _escNumberPtr = 0;
37
+ var _escNumberMode = 0;
38
var _scratt = [];
39
var _tscreen = [];
40
var _VTUNDERLINE = 1;
41
var _VTREVERSE = 2;
42
+ var _backSpaceErase = false;
43
+ var _cursorVisible = true;
44
+ var _scrollRegion = [0, 25];
45
46
obj.Start = function () { }
47
@@ -72,6 +78,9 @@ var CreateAmtRemoteTerminal = function (divid) {
78
switch (c) {
79
case 27: // ESC
80
_termstate = 1;
81
+ _escNumber = [];
82
+ _escNumberPtr = 0;
83
+ _escNumberMode = 0;
84
break;
85
default:
86
// Process a single char
@@ -82,8 +91,6 @@ var CreateAmtRemoteTerminal = function (divid) {
91
case 1:
92
switch (b) {
93
case '[':
85
- _escNumberPtr = 0;
86
- _escNumber = [];
94
_termstate = 2;
95
break;
96
case '(':
@@ -92,6 +99,14 @@ var CreateAmtRemoteTerminal = function (divid) {
99
case ')':
100
_termstate = 5;
101
break;
102
+ case '=':
103
+ // Set alternate keypad mode
104
+ _termstate = 0;
105
+ break;
106
+ case '>':
107
+ // Set numeric keypad mode
108
+ _termstate = 0;
109
+ break;
110
default:
111
_termstate = 0;
112
break;
@@ -107,16 +122,17 @@ var CreateAmtRemoteTerminal = function (divid) {
122
_escNumber[_escNumberPtr] = ((_escNumber[_escNumberPtr] * 10) + (b - '0'));
123
}
124
break;
110
- }
111
- else if (b == ';') {
125
+ } else if (b == ';') {
126
// New number
127
_escNumberPtr++;
128
break;
115
- }
116
- else {
129
+ } else if (b == '?') {
130
+ _escNumberMode = 1;
131
+ break;
132
+ } else {
133
// Process Escape Sequence
134
if (!_escNumber[0]) _escNumber[0] = 0;
119
- _ProcessEscapeHandler(b, _escNumber, _escNumberPtr + 1);
135
+ _ProcessEscapeHandler(b, _escNumber, _escNumberPtr + 1, _escNumberMode);
136
_termstate = 0;
137
}
138
break;
@@ -129,151 +145,195 @@ var CreateAmtRemoteTerminal = function (divid) {
145
}
146
}
147
132
- function _ProcessEscapeHandler(code, args, argslen) {
133
- var i;
134
- switch (code) {
135
- case 'c': // ResetDevice
136
- // Reset
137
- obj.TermResetScreen();
138
- break;
139
- case 'A': // Move cursor up n lines
140
- if (argslen == 1) {
141
- _termy -= args[0];
142
- if (_termy < 0) _termy = 0;
143
- }
144
- break;
145
- case 'B': // Move cursor down n lines
146
- if (argslen == 1) {
147
- _termy += args[0];
148
- if (_termy > obj.height) _termy = obj.height;
149
- }
150
- break;
151
- case 'C': // Move cursor right n lines
152
- if (argslen == 1) {
153
- _termx += args[0];
154
- if (_termx > obj.width) _termx = obj.width;
155
- }
156
- break;
157
- case 'D': // Move cursor left n lines
158
- if (argslen == 1) {
159
- _termx -= args[0];
160
- if (_termx < 0) _termx = 0;
161
- }
162
- break;
163
- case 'd': // Set cursor to line n
164
- if (argslen == 1) {
165
- _termy = args[0] - 1;
166
- if (_termy > obj.height) _termy = obj.height;
167
- if (_termy < 0) _termy = 0;
168
- }
169
- break;
170
- case 'G': // Set cursor to col n
171
- if (argslen == 1) {
172
- _termx = args[0] - 1;
173
- if (_termx < 0) _termx = 0;
174
- if (_termx > 79) _termx = 79;
175
- }
176
- break;
177
- case 'J': // ClearScreen:
178
- if (argslen == 1 && args[0] == 2) {
179
- obj.TermClear((_TermCurrentBColor << 12) + (_TermCurrentFColor << 6)); // Erase entire screen
180
- _termx = 0;
181
- _termy = 0;
182
- }
183
- else if (argslen == 0 || argslen == 1 && args[0] == 0) // Erase cursor down
184
- {
185
- _EraseCursorToEol();
186
- for (i = _termy + 1; i < obj.height; i++) _EraseLine(i);
187
- }
188
- else if (argslen == 1 && args[0] == 1) // Erase cursor up
189
- {
190
- _EraseCursorToEol();
191
- for (i = 0; i < _termy - 1; i++) _EraseLine(i);
192
- }
193
- break;
194
- case 'H': // MoveCursor:
195
- if (argslen == 2) {
196
- if (args[0] < 1) args[0] = 1;
197
- if (args[1] < 1) args[1] = 1;
198
- if (args[0] > obj.height) args[0] = obj.height;
199
- if (args[1] > obj.width) args[1] = obj.width;
200
- _termy = args[0] - 1;
201
- _termx = args[1] - 1;
202
- }
203
- else {
204
- _termy = 0;
205
- _termx = 0;
206
- }
207
- break;
208
- case 'm': // ScreenAttribs:
209
- // Change attributes
210
- for (i = 0; i < argslen; i++) {
211
- if (!args[i] || args[i] == 0) {
212
- // Reset Attributes
213
- _TermCurrentBColor = 0;
214
- _TermCurrentFColor = 7;
215
- _TermCurrentReverse = 0;
216
- }
217
- else if (args[i] == 1) {
218
- // Bright
219
- if (_TermCurrentFColor < 8) _TermCurrentFColor += 8;
148
+ function _ProcessEscapeHandler(code, args, argslen, mode) {
149
+ //console.log('process', code, args, mode);
150
+ if (mode == 1) {
151
+ switch (code) {
152
+ case 'l': // Hide the cursor
153
+ if (args[0] == 25) { _cursorVisible = false; }
154
+ break;
155
+ case 'h': // Show the cursor
156
+ if (args[0] == 25) { _cursorVisible = true; }
157
+ break;
158
+ }
159
+ } else if (mode == 0) {
160
+ var i;
161
+ switch (code) {
162
+ case 'c': // ResetDevice
163
+ // Reset
164
+ obj.TermResetScreen();
165
+ break;
166
+ case 'A': // Move cursor up n lines
167
+ if (argslen == 1) {
168
+ if (args[0] == 0) { _termy--; } else { _termy -= args[0]; }
169
+ if (_termy < 0) _termy = 0;
170
}
221
- else if (args[i] == 2 || args[i] == 22) {
222
- // Dim
223
- if (_TermCurrentFColor >= 8) _TermCurrentFColor -= 8;
171
+ break;
172
+ case 'B': // Move cursor down n lines
173
+ if (argslen == 1) {
174
+ if (args[0] == 0) { _termy++; } else { _termy += args[0]; }
175
+ if (_termy > obj.height) _termy = obj.height;
176
}
225
- else if (args[i] == 7) {
226
- // Set Reverse attribute true
227
- _TermCurrentReverse = 2;
177
+ break;
178
+ case 'C': // Move cursor right n lines
179
+ if (argslen == 1) {
180
+ if (args[0] == 0) { _termx++; } else { _termx += args[0]; }
181
+ if (_termx > obj.width) _termx = obj.width;
182
}
229
- else if (args[i] == 27) {
230
- // Set Reverse attribute false
231
- _TermCurrentReverse = 0;
183
+ break;
184
+ case 'D': // Move cursor left n lines
185
+ if (argslen == 1) {
186
+ if (args[0] == 0) { _termx--; } else { _termx -= args[0]; }
187
+ if (_termx < 0) _termx = 0;
188
}
233
- else if (args[i] >= 30 && args[i] <= 37) {
234
- // Set Foreground Color
235
- var bright = (_TermCurrentFColor >= 8);
236
- _TermCurrentFColor = (args[i] - 30);
237
- if (bright && _TermCurrentFColor <= 8) _TermCurrentFColor += 8;
189
+ break;
190
+ case 'd': // Set cursor to line n
191
+ if (argslen == 1) {
192
+ _termy = args[0] - 1;
193
+ if (_termy > obj.height) _termy = obj.height;
194
+ if (_termy < 0) _termy = 0;
195
}
239
- else if (args[i] >= 40 && args[i] <= 47) {
240
- // Set Background Color
241
- _TermCurrentBColor = (args[i] - 40);
196
+ break;
197
+ case 'G': // Set cursor to col n
198
+ if (argslen == 1) {
199
+ _termx = args[0] - 1;
200
+ if (_termx < 0) _termx = 0;
201
+ if (_termx > 79) _termx = 79;
202
}
243
- else if (args[i] >= 90 && args[i] <= 99) {
244
- // Set Bright Foreground Color
245
- _TermCurrentFColor = (args[i] - 82);
203
+ break;
204
+ case 'P': // Move the rest of the line left, this is a guess as there is no documentation about this code (???)
205
+ if (argslen == 1) {
206
+ for (i = _termx; i < (obj.width - args[0]) ; i++) {
207
+ _tscreen[_termy][i] = _tscreen[_termy][i + args[0]]
208
+ _scratt[_termy][i] = _scratt[_termy][i + args[0]];
209
+ }
210
}
247
- else if (args[i] >= 100 && args[i] <= 109) {
248
- // Set Bright Background Color
249
- _TermCurrentBColor = (args[i] - 92);
211
+ break;
212
+ case 'J': // ClearScreen:
213
+ if (argslen == 1 && args[0] == 2) {
214
+ obj.TermClear((_TermCurrentBColor << 12) + (_TermCurrentFColor << 6)); // Erase entire screen
215
+ _termx = 0;
216
+ _termy = 0;
217
}
251
- }
252
- break;
253
- case 'K': // EraseLine:
254
- if (argslen == 0 || (argslen == 1 && (!args[0] || args[0] == 0))) {
255
- _EraseCursorToEol(); // Erase from the cursor to the end of the line
256
- }
257
- else if (argslen == 1) {
258
- if (args[0] == 1) // Erase from the beginning of the line to the cursor
218
+ else if (argslen == 0 || argslen == 1 && args[0] == 0) // Erase cursor down
219
{
260
- _EraseBolToCursor();
220
+ _EraseCursorToEol();
221
+ for (i = _termy + 1; i < obj.height; i++) _EraseLine(i);
222
}
262
- else if (args[0] == 2) // Erase the line with the cursor
223
+ else if (argslen == 1 && args[0] == 1) // Erase cursor up
224
{
264
- _EraseLine(_termy);
225
+ _EraseCursorToEol();
226
+ for (i = 0; i < _termy - 1; i++) _EraseLine(i);
227
}
266
- }
267
- break;
268
- case 'h': // EnableLineWrap:
269
- _TermLineWrap = true;
270
- break;
271
- case 'l': // DisableLineWrap:
272
- _TermLineWrap = false;
273
- break;
274
- default:
275
- //if (code != '@') alert(code);
276
- break;
228
+ break;
229
+ case 'H': // MoveCursor:
230
+ if (argslen == 2) {
231
+ if (args[0] < 1) args[0] = 1;
232
+ if (args[1] < 1) args[1] = 1;
233
+ if (args[0] > obj.height) args[0] = obj.height;
234
+ if (args[1] > obj.width) args[1] = obj.width;
235
+ _termy = args[0] - 1;
236
+ _termx = args[1] - 1;
237
+ }
238
+ else {
239
+ _termy = 0;
240
+ _termx = 0;
241
+ }
242
+ break;
243
+ case 'm': // ScreenAttribs:
244
+ // Change attributes
245
+ for (i = 0; i < argslen; i++) {
246
+ if (!args[i] || args[i] == 0) {
247
+ // Reset Attributes
248
+ _TermCurrentBColor = 0;
249
+ _TermCurrentFColor = 7;
250
+ _TermCurrentReverse = 0;
251
+ }
252
+ else if (args[i] == 1) {
253
+ // Bright
254
+ if (_TermCurrentFColor < 8) _TermCurrentFColor += 8;
255
+ }
256
+ else if (args[i] == 2 || args[i] == 22) {
257
+ // Dim
258
+ if (_TermCurrentFColor >= 8) _TermCurrentFColor -= 8;
259
+ }
260
+ else if (args[i] == 7) {
261
+ // Set Reverse attribute true
262
+ _TermCurrentReverse = 2;
263
+ }
264
+ else if (args[i] == 27) {
265
+ // Set Reverse attribute false
266
+ _TermCurrentReverse = 0;
267
+ }
268
+ else if (args[i] >= 30 && args[i] <= 37) {
269
+ // Set Foreground Color
270
+ var bright = (_TermCurrentFColor >= 8);
271
+ _TermCurrentFColor = (args[i] - 30);
272
+ if (bright && _TermCurrentFColor <= 8) _TermCurrentFColor += 8;
273
+ }
274
+ else if (args[i] >= 40 && args[i] <= 47) {
275
+ // Set Background Color
276
+ _TermCurrentBColor = (args[i] - 40);
277
+ }
278
+ else if (args[i] >= 90 && args[i] <= 99) {
279
+ // Set Bright Foreground Color
280
+ _TermCurrentFColor = (args[i] - 82);
281
+ }
282
+ else if (args[i] >= 100 && args[i] <= 109) {
283
+ // Set Bright Background Color
284
+ _TermCurrentBColor = (args[i] - 92);
285
+ }
286
+ }
287
+ break;
288
+ case 'K': // EraseLine:
289
+ if (argslen == 0 || (argslen == 1 && (!args[0] || args[0] == 0))) {
290
+ _EraseCursorToEol(); // Erase from the cursor to the end of the line
291
+ }
292
+ else if (argslen == 1) {
293
+ if (args[0] == 1) // Erase from the beginning of the line to the cursor
294
+ {
295
+ _EraseBolToCursor();
296
+ }
297
+ else if (args[0] == 2) // Erase the line with the cursor
298
+ {
299
+ _EraseLine(_termy);
300
+ }
301
+ }
302
+ break;
303
+ case 'h': // EnableLineWrap:
304
+ _TermLineWrap = true;
305
+ break;
306
+ case 'l': // DisableLineWrap:
307
+ _TermLineWrap = false;
308
+ break;
309
+ case 'r': // Set the scroll region
310
+ if (argslen == 2) { _scrollRegion = [args[0] - 1, args[1] - 1]; }
311
+ break;
312
+ case 'S': // Scroll up the scroll region X lines, default 1
313
+ var x = 1;
314
+ if (argslen == 1) { x = args[0] }
315
+ for (var y = _scrollRegion[0]; y <= _scrollRegion[1] - x; y++) {
316
+ for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = _tscreen[y + x][z]; _scratt[y][z] = _scratt[y + x][z]; }
317
+ }
318
+ for (var y = _scrollRegion[1] - x + 1; y < _scrollRegion[1]; y++) {
319
+ for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
320
+ }
321
+ break;
322
+ case 'T': // Scroll down the scroll region X lines, default 1
323
+ var x = 1;
324
+ if (argslen == 1) { x = args[0] }
325
+ for (var y = _scrollRegion[1]; y > _scrollRegion[0] + x; y--) {
326
+ for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = _tscreen[y - x][z]; _scratt[y][z] = _scratt[y - x][z]; }
327
+ }
328
+ for (var y = _scrollRegion[0] + x; y > _scrollRegion[0]; y--) {
329
+ for (var z = 0; z < obj.width; z++) { _tscreen[y][z] = ' '; _scratt[y][z] = (7 << 6); }
330
+ }
331
+ break;
332
+ default:
333
+ //if (code != '@') alert(code);
334
+ //console.log('unknown process', code, args, mode);
335
+ break;
336
+ }
337
}
338
}
339
@@ -366,6 +426,7 @@ var CreateAmtRemoteTerminal = function (divid) {
426
function _ProcessVt100Char(c) {
427
if (c == '\0' || c.charCodeAt() == 7) return; // Ignore null & bell
428
var ch = c.charCodeAt();
429
+ //console.log('_ProcessVt100Char', ch, c);
430
431
// ###BEGIN###{Terminal-Enumation-All}
432
// UTF8 Terminal
@@ -401,8 +462,8 @@ var CreateAmtRemoteTerminal = function (divid) {
462
switch (c) {
463
case '\b': // Backspace
464
if (_termx > 0) {
404
- _termx = _termx - 1;
405
- _TermDrawChar(' ');
465
+ _termx--;
466
+ if (_backSpaceErase) { _TermDrawChar(' '); }
467
}
468
break;
469
case '\t': // tab
@@ -411,7 +472,7 @@ var CreateAmtRemoteTerminal = function (divid) {
472
break;
473
case '\n': // Linefeed
474
_termy++;
414
- if (_termy > (obj.height - 1)) {
475
+ if (_termy > (_scrollRegion[1] - 1)) {
476
// Move everything up one line
477
_TermMoveUp(1);
478
_termy = (obj.height - 1);
@@ -459,7 +520,7 @@ var CreateAmtRemoteTerminal = function (divid) {
520
}
521
522
function _EraseCursorToEol() {
462
- var t = (_TermCurrentBColor << 12);
523
+ var t = (_TermCurrentFColor << 6) + (_TermCurrentBColor << 12) + _TermCurrentReverse;
524
for (var x = _termx; x < obj.width; x++) {
525
_tscreen[_termy][x] = ' ';
526
_scratt[_termy][x] = t;
@@ -467,7 +528,7 @@ var CreateAmtRemoteTerminal = function (divid) {
528
}
529
530
function _EraseBolToCursor() {
470
- var t = (_TermCurrentBColor << 12);
531
+ var t = (_TermCurrentFColor << 6) + (_TermCurrentBColor << 12) + _TermCurrentReverse;
532
for (var x = 0; x < _termx; x++) {
533
_tscreen[_termy][x] = ' ';
534
_scratt[_termy][x] = t;
@@ -475,7 +536,7 @@ var CreateAmtRemoteTerminal = function (divid) {
536
}
537
538
function _EraseLine(line) {
478
- var t = (_TermCurrentBColor << 12);
539
+ var t = (_TermCurrentFColor << 6) + (_TermCurrentBColor << 12) + _TermCurrentReverse;
540
for (var x = 0; x < obj.width; x++) {
541
_tscreen[line][x] = ' ';
542
_scratt[line][x] = t;
@@ -487,11 +548,11 @@ var CreateAmtRemoteTerminal = function (divid) {
548
549
function _TermMoveUp(linecount) {
550
var x, y;
490
- for (y = 0; y < obj.height - linecount; y++) {
551
+ for (y = _scrollRegion[0]; y <= _scrollRegion[1] - linecount; y++) {
552
_tscreen[y] = _tscreen[y + linecount];
553
_scratt[y] = _scratt[y + linecount];
554
}
494
- for (y = obj.height - linecount; y < obj.height; y++) {
555
+ for (y = _scrollRegion[1] - linecount + 1; y <= _scrollRegion[1]; y++) {
556
_tscreen[y] = [];
557
_scratt[y] = [];
558
for (x = 0; x < obj.width; x++) {
@@ -569,7 +630,7 @@ var CreateAmtRemoteTerminal = function (divid) {
630
for (var y = 0; y < obj.height; ++y) {
631
for (var x = 0; x < obj.width; ++x) {
632
newat = _scratt[y][x];
572
- if (_termx == x && _termy == y) { newat |= _VTREVERSE; } // If this is the cursor location, reverse the color.
633
+ if (_termx == x && _termy == y && _cursorVisible) { newat |= _VTREVERSE; } // If this is the cursor location, reverse the color.
634
if (newat != oldat) {
635
buf += closetag;
636
closetag = '';
@@ -584,17 +645,11 @@ var CreateAmtRemoteTerminal = function (divid) {
645
646
c = _tscreen[y][x];
647
switch (c) {
587
- case '&':
588
- buf += '&'; break;
589
- case '<':
590
- buf += '<'; break;
591
- case '>':
592
- buf += '>'; break;
593
- case ' ':
594
- buf += ' '; break;
595
- default:
596
- buf += c;
597
- break;
648
+ case '&': buf += '&'; break;
649
+ case '<': buf += '<'; break;
650
+ case '>': buf += '>'; break;
651
+ case ' ': buf += ' '; break;
652
+ default: buf += c; break;
653
}
654
}
655
if (y != (obj.height - 1)) buf += '<br>';