1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "glk/window_text_grid.h"
24 #include "glk/conf.h"
25 #include "glk/glk.h"
26 #include "glk/selection.h"
27 #include "glk/screen.h"
28 
29 namespace Glk {
30 
TextGridWindow(Windows * windows,uint rock)31 TextGridWindow::TextGridWindow(Windows *windows, uint rock) : TextWindow(windows, rock),
32 		_font(g_conf->_monoInfo) {
33 	_type = wintype_TextGrid;
34 	_width = _height = 0;
35 	_curX = _curY = 0;
36 	_inBuf = nullptr;
37 	_inOrgX = _inOrgY = 0;
38 	_inMax = 0;
39 	_inCurs = _inLen = 0;
40 	_inArrayRock.num = 0;
41 	_lineTerminators = nullptr;
42 
43 	Common::copy(&g_conf->_gStyles[0], &g_conf->_gStyles[style_NUMSTYLES], _styles);
44 
45 	if (g_conf->_speak)
46 		gli_initialize_tts();
47 }
48 
~TextGridWindow()49 TextGridWindow::~TextGridWindow() {
50 	if (g_conf->_speak)
51 		gli_free_tts();
52 
53 	if (_inBuf) {
54 		if (g_vm->gli_unregister_arr)
55 			(*g_vm->gli_unregister_arr)(_inBuf, _inMax, "&+#!Cn", _inArrayRock);
56 		_inBuf = nullptr;
57 	}
58 
59 	delete[] _lineTerminators;
60 }
61 
rearrange(const Rect & box)62 void TextGridWindow::rearrange(const Rect &box) {
63 	Window::rearrange(box);
64 	int newwid, newhgt;
65 
66 	newwid = MAX(box.width() / _font._cellW, 0);
67 	newhgt = MAX(box.height() / _font._cellH, 0);
68 
69 	if (newwid == _width && newhgt == _height)
70 		return;
71 
72 	_lines.resize(newhgt);
73 	for (int y = 0; y < newhgt; ++y) {
74 		_lines[y].resize(newwid);
75 		touch(y);
76 	}
77 
78 	_attr.clear();
79 	_width = newwid;
80 	_height = newhgt;
81 }
82 
touch(int line)83 void TextGridWindow::touch(int line) {
84 	int y = _bbox.top + line * _font._leading;
85 	_lines[line].dirty = true;
86 	_windows->repaint(Rect(_bbox.left, y, _bbox.right, y + _font._leading));
87 }
88 
getSplit(uint size,bool vertical) const89 uint TextGridWindow::getSplit(uint size, bool vertical) const {
90 	return vertical ? size * _font._cellW : size * _font._cellH;
91 }
92 
putCharUni(uint32 ch)93 void TextGridWindow::putCharUni(uint32 ch) {
94 	TextGridRow *ln;
95 
96 	// This may not be the best way to do this, but some games use user styles to
97 	// display some gliphs from ASCII characters. Those should not be spoken as
98 	// they make no sense.
99 	if (_attr.style < style_User1)
100 		gli_tts_speak(&ch, 1);
101 
102 	// Canonicalize the cursor position. That is, the cursor may have been
103 	// left outside the window area; wrap it if necessary.
104 	if (_curX < 0) {
105 		_curX = 0;
106 	} else if (_curX >= _width) {
107 		_curX = 0;
108 		_curY++;
109 	}
110 	if (_curY < 0)
111 		_curY = 0;
112 	else if (_curY >= _height)
113 		return; // outside the window
114 
115 	if (ch == '\n') {
116 		// a newline just moves the cursor.
117 		_curY++;
118 		_curX = 0;
119 		return;
120 	}
121 
122 	touch(_curY);
123 
124 	ln = &(_lines[_curY]);
125 	ln->_chars[_curX] = ch;
126 	ln->_attrs[_curX] = _attr;
127 
128 	_curX++;
129 	// We can leave the cursor outside the window, since it will be
130 	// canonicalized next time a character is printed.
131 }
132 
unputCharUni(uint32 ch)133 bool TextGridWindow::unputCharUni(uint32 ch) {
134 	TextGridRow *ln;
135 	int oldx = _curX, oldy = _curY;
136 
137 	// Move the cursor back.
138 	if (_curX >= _width)
139 		_curX = _width - 1;
140 	else
141 		_curX--;
142 
143 	// Canonicalize the cursor position. That is, the cursor may have been
144 	// left outside the window area; wrap it if necessary.
145 	if (_curX < 0) {
146 		_curX = _width - 1;
147 		_curY--;
148 	}
149 	if (_curY < 0)
150 		_curY = 0;
151 	else if (_curY >= _height)
152 		return false; // outside the window
153 
154 	if (ch == '\n') {
155 		// a newline just moves the cursor.
156 		if (_curX == _width - 1)
157 			return 1; // deleted a newline
158 		_curX = oldx;
159 		_curY = oldy;
160 		return 0;    // it wasn't there
161 	}
162 
163 	ln = &(_lines[_curY]);
164 	if (ln->_chars[_curX] == ch) {
165 		ln->_chars[_curX] = ' ';
166 		ln->_attrs[_curX].clear();
167 		touch(_curY);
168 		return true; // deleted the char
169 	} else {
170 		_curX = oldx;
171 		_curY = oldy;
172 		return false; // it wasn't there
173 	}
174 }
175 
moveCursor(const Point & pos)176 void TextGridWindow::moveCursor(const Point &pos) {
177 	// If the values are negative, they're really huge positive numbers --
178 	// remember that they were cast from uint. So set them huge and
179 	// let canonicalization take its course.
180 	if (_curY >= 0 && _curY < _height && _lines[_curY].dirty) {
181 		const uint32 NEWLINE = '\n';
182 		gli_tts_speak((const uint32 *)&NEWLINE, 1);
183 	}
184 
185 	_curX = (pos.x < 0) ? 32767 : pos.x;
186 	_curY = (pos.y < 0) ? 32767 : pos.y;
187 }
188 
clear()189 void TextGridWindow::clear() {
190 	_attr.fgset = Windows::_overrideFgSet;
191 	_attr.bgset = Windows::_overrideBgSet;
192 	_attr.fgcolor = Windows::_overrideFgSet ? Windows::_overrideFgVal : 0;
193 	_attr.bgcolor = Windows::_overrideBgSet ? Windows::_overrideBgVal : 0;
194 	_attr.reverse = false;
195 
196 	for (int k = 0; k < _height; k++) {
197 		TextGridRow &ln = _lines[k];
198 		touch(k);
199 		for (uint j = 0; j < ln._attrs.size(); ++j) {
200 			ln._chars[j] = ' ';
201 			ln._attrs[j].clear();
202 		}
203 	}
204 
205 	_curX = 0;
206 	_curY = 0;
207 }
208 
click(const Point & newPos)209 void TextGridWindow::click(const Point &newPos) {
210 	int x = newPos.x - _bbox.left;
211 	int y = newPos.y - _bbox.top;
212 
213 	if (_lineRequest || _charRequest || _lineRequestUni || _charRequestUni
214 			|| _moreRequest || _scrollRequest)
215 		_windows->setFocus(this);
216 
217 	if (_mouseRequest) {
218 		g_vm->_events->store(evtype_MouseInput, this, x / _font._cellW, y / _font._leading);
219 		_mouseRequest = false;
220 		if (g_conf->_safeClicks)
221 			g_vm->_events->_forceClick = true;
222 	}
223 
224 	if (_hyperRequest) {
225 		uint linkval = g_vm->_selection->getHyperlink(newPos);
226 		if (linkval) {
227 			g_vm->_events->store(evtype_Hyperlink, this, linkval, 0);
228 			_hyperRequest = false;
229 			if (g_conf->_safeClicks)
230 				g_vm->_events->_forceClick = true;
231 		}
232 	}
233 }
234 
requestLineEvent(char * buf,uint maxlen,uint initlen)235 void TextGridWindow::requestLineEvent(char *buf, uint maxlen, uint initlen) {
236 	if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
237 		warning("request_line_event: window already has keyboard request");
238 		return;
239 	}
240 
241 	_lineRequest = true;
242 	gli_tts_flush();
243 
244 	if ((int)maxlen > (_width - _curX))
245 		maxlen = (_width - _curX);
246 
247 	_inBuf = buf;
248 	_inMax = maxlen;
249 	_inLen = 0;
250 	_inCurs = 0;
251 	_inOrgX = _curX;
252 	_inOrgY = _curY;
253 	_origAttr = _attr;
254 	_attr.set(style_Input);
255 
256 	if (initlen > maxlen)
257 		initlen = maxlen;
258 
259 	if (initlen) {
260 		TextGridRow *ln = &_lines[_inOrgY];
261 
262 		for (uint ix = 0; ix < initlen; ix++) {
263 			ln->_attrs[_inOrgX + ix].set(style_Input);
264 			ln->_chars[_inOrgX + ix] = buf[ix];
265 		}
266 
267 		_inCurs += initlen;
268 		_inLen += initlen;
269 		_curX = _inOrgX + _inCurs;
270 		_curY = _inOrgY;
271 
272 		touch(_inOrgY);
273 	}
274 
275 	if (_lineTerminatorsBase && _termCt) {
276 		_lineTerminators = new uint32[_termCt + 1];
277 
278 		if (_lineTerminators) {
279 			memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint32));
280 			_lineTerminators[_termCt] = 0;
281 		}
282 	}
283 
284 	if (g_vm->gli_register_arr)
285 		_inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Cn");
286 
287 	// Switch focus to the new window
288 	_windows->inputGuessFocus();
289 }
290 
requestLineEventUni(uint32 * buf,uint maxlen,uint initlen)291 void TextGridWindow::requestLineEventUni(uint32 *buf, uint maxlen, uint initlen) {
292 	if (_charRequest || _lineRequest || _charRequestUni || _lineRequestUni) {
293 		warning("requestLineEventUni: window already has keyboard request");
294 		return;
295 	}
296 
297 	_lineRequestUni = true;
298 	gli_tts_flush();
299 
300 	if ((int)maxlen > (_width - _curX))
301 		maxlen = (_width - _curX);
302 
303 	_inBuf = buf;
304 	_inMax = maxlen;
305 	_inLen = 0;
306 	_inCurs = 0;
307 	_inOrgX = _curX;
308 	_inOrgY = _curY;
309 	_origAttr = _attr;
310 	_attr.set(style_Input);
311 
312 	if (initlen > maxlen)
313 		initlen = maxlen;
314 
315 	if (initlen) {
316 		TextGridRow *ln = &(_lines[_inOrgY]);
317 
318 		for (uint ix = 0; ix < initlen; ix++) {
319 			ln->_attrs[_inOrgX + ix].set(style_Input);
320 			ln->_chars[_inOrgX + ix] = buf[ix];
321 		}
322 
323 		_inCurs += initlen;
324 		_inLen += initlen;
325 		_curX = _inOrgX + _inCurs;
326 		_curY = _inOrgY;
327 
328 		touch(_inOrgY);
329 	}
330 
331 	if (_lineTerminatorsBase && _termCt) {
332 		_lineTerminators = new uint32[_termCt + 1];
333 
334 		if (_lineTerminators) {
335 			memcpy(_lineTerminators, _lineTerminatorsBase, _termCt * sizeof(uint));
336 			_lineTerminators[_termCt] = 0;
337 		}
338 	}
339 
340 	if (g_vm->gli_register_arr)
341 		_inArrayRock = (*g_vm->gli_register_arr)(buf, maxlen, "&+#!Iu");
342 
343 	// Switch focus to the new window
344 	_windows->inputGuessFocus();
345 }
346 
requestCharEvent()347 void TextGridWindow::requestCharEvent() {
348 	_charRequest = true;
349 
350 	// Switch focus to the new window
351 	_windows->inputGuessFocus();
352 }
353 
requestCharEventUni()354 void TextGridWindow::requestCharEventUni() {
355 	_charRequestUni = true;
356 
357 	// Switch focus to the new window
358 	_windows->inputGuessFocus();
359 }
360 
cancelLineEvent(Event * ev)361 void TextGridWindow::cancelLineEvent(Event *ev) {
362 	int ix;
363 	void *inbuf;
364 	int inmax;
365 	bool unicode = _lineRequestUni;
366 	gidispatch_rock_t inarrayrock;
367 	TextGridRow *ln = &_lines[_inOrgY];
368 	Event dummyEv;
369 
370 	if (!ev)
371 		ev = &dummyEv;
372 
373 	ev->clear();
374 
375 	if (!_lineRequest && !_lineRequestUni)
376 		return;
377 
378 
379 	inbuf = _inBuf;
380 	inmax = _inMax;
381 	inarrayrock = _inArrayRock;
382 
383 	if (!unicode) {
384 		for (ix = 0; ix < _inLen; ix++) {
385 			uint32 ch = ln->_chars[_inOrgX + ix];
386 			if (ch > 0xff)
387 				ch = '?';
388 			((char *)inbuf)[ix] = (char)ch;
389 		}
390 		if (_echoStream)
391 			_echoStream->echoLine((char *)_inBuf, _inLen);
392 	} else {
393 		for (ix = 0; ix < _inLen; ix++)
394 			((uint *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
395 		if (_echoStream)
396 			_echoStream->echoLineUni((uint32 *)inbuf, _inLen);
397 	}
398 
399 	_curY = _inOrgY + 1;
400 	_curX = 0;
401 	_attr = _origAttr;
402 
403 	ev->type = evtype_LineInput;
404 	ev->window = this;
405 	ev->val1 = _inLen;
406 	ev->val2 = 0;
407 
408 	_lineRequest = false;
409 	_lineRequestUni = false;
410 
411 	if (_lineTerminators) {
412 		delete[] _lineTerminators;
413 		_lineTerminators = nullptr;
414 	}
415 
416 	_inBuf = nullptr;
417 	_inMax = 0;
418 	_inOrgX = 0;
419 	_inOrgY = 0;
420 
421 	if (g_vm->gli_unregister_arr)
422 		(*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
423 }
424 
acceptReadChar(uint arg)425 void TextGridWindow::acceptReadChar(uint arg) {
426 	uint key;
427 
428 	switch (arg) {
429 	case keycode_Erase:
430 		key = keycode_Delete;
431 		break;
432 	case keycode_MouseWheelUp:
433 	case keycode_MouseWheelDown:
434 		return;
435 	default:
436 		key = arg;
437 	}
438 
439 	gli_tts_purge();
440 
441 	if (key > 0xff && key < (0xffffffff - keycode_MAXVAL + 1)) {
442 		if (!(_charRequestUni) || key > 0x10ffff)
443 			key = keycode_Unknown;
444 	}
445 
446 	_charRequest = false;
447 	_charRequestUni = false;
448 	g_vm->_events->store(evtype_CharInput, this, key, 0);
449 }
450 
acceptLine(uint32 keycode)451 void TextGridWindow::acceptLine(uint32 keycode) {
452 	int ix;
453 	void *inbuf;
454 	int inmax;
455 	gidispatch_rock_t inarrayrock;
456 	TextGridRow *ln = &(_lines[_inOrgY]);
457 	bool unicode = _lineRequestUni;
458 
459 	if (!_inBuf)
460 		return;
461 
462 	inbuf = _inBuf;
463 	inmax = _inMax;
464 	inarrayrock = _inArrayRock;
465 
466 	gli_tts_purge();
467 
468 	if (!unicode) {
469 		for (ix = 0; ix < _inLen; ix++)
470 			((char *)inbuf)[ix] = (char)ln->_chars[_inOrgX + ix];
471 		if (_echoStream)
472 			_echoStream->echoLine((char *)inbuf, _inLen);
473 		if (g_conf->_speakInput) {
474 			const char NEWLINE = '\n';
475 			gli_tts_speak((const char *)inbuf, _inLen);
476 			gli_tts_speak((const char *)&NEWLINE, 1);
477 		}
478 	} else {
479 		for (ix = 0; ix < _inLen; ix++)
480 			((uint *)inbuf)[ix] = ln->_chars[_inOrgX + ix];
481 		if (_echoStream)
482 			_echoStream->echoLineUni((const uint32 *)inbuf, _inLen);
483 		if (g_conf->_speakInput) {
484 			const uint32 NEWLINE = '\n';
485 			gli_tts_speak((const uint32 *)inbuf, _inLen);
486 			gli_tts_speak((const uint32 *)&NEWLINE, 1);
487 		}
488 	}
489 
490 	_curY = _inOrgY + 1;
491 	_curX = 0;
492 	_attr = _origAttr;
493 
494 	if (_lineTerminators) {
495 		uint val2 = keycode;
496 		if (val2 == keycode_Return)
497 			val2 = 0;
498 		g_vm->_events->store(evtype_LineInput, this, _inLen, val2);
499 		delete[] _lineTerminators;
500 		_lineTerminators = nullptr;
501 	} else {
502 		g_vm->_events->store(evtype_LineInput, this, _inLen, 0);
503 	}
504 	_lineRequest = false;
505 	_lineRequestUni = false;
506 	_inBuf = nullptr;
507 	_inMax = 0;
508 	_inOrgX = 0;
509 	_inOrgY = 0;
510 
511 	if (g_vm->gli_unregister_arr)
512 		(*g_vm->gli_unregister_arr)(inbuf, inmax, unicode ? "&+#!Iu" : "&+#!Cn", inarrayrock);
513 }
514 
acceptReadLine(uint32 arg)515 void TextGridWindow::acceptReadLine(uint32 arg) {
516 	int ix;
517 	TextGridRow *ln = &(_lines[_inOrgY]);
518 
519 	if (!_inBuf)
520 		return;
521 
522 	if (_lineTerminators && checkTerminators(arg)) {
523 		const uint32 *cx;
524 		for (cx = _lineTerminators; *cx; cx++) {
525 			if (*cx == arg) {
526 				acceptLine(arg);
527 				return;
528 			}
529 		}
530 	}
531 
532 	switch (arg) {
533 
534 	// Delete keys, during line input.
535 	case keycode_Delete:
536 		if (_inLen <= 0)
537 			return;
538 		if (_inCurs <= 0)
539 			return;
540 		for (ix = _inCurs; ix < _inLen; ix++)
541 			ln->_chars[_inOrgX + ix - 1] = ln->_chars[_inOrgX + ix];
542 		ln->_chars[_inOrgX + _inLen - 1] = ' ';
543 		_inCurs--;
544 		_inLen--;
545 		break;
546 
547 	case keycode_Erase:
548 		if (_inLen <= 0)
549 			return;
550 		if (_inCurs >= _inLen)
551 			return;
552 		for (ix = _inCurs; ix < _inLen - 1; ix++)
553 			ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix + 1];
554 		ln->_chars[_inOrgX + _inLen - 1] = ' ';
555 		_inLen--;
556 		break;
557 
558 	case keycode_Escape:
559 		if (_inLen <= 0)
560 			return;
561 		for (ix = 0; ix < _inLen; ix++)
562 			ln->_chars[_inOrgX + ix] = ' ';
563 		_inLen = 0;
564 		_inCurs = 0;
565 		break;
566 
567 	// Cursor movement keys, during line input.
568 	case keycode_Left:
569 		if (_inCurs <= 0)
570 			return;
571 		_inCurs--;
572 		break;
573 
574 	case keycode_Right:
575 		if (_inCurs >= _inLen)
576 			return;
577 		_inCurs++;
578 		break;
579 
580 	case keycode_Home:
581 		if (_inCurs <= 0)
582 			return;
583 		_inCurs = 0;
584 		break;
585 
586 	case keycode_End:
587 		if (_inCurs >= _inLen)
588 			return;
589 		_inCurs = _inLen;
590 		break;
591 
592 	case keycode_Return:
593 		acceptLine(arg);
594 		break;
595 
596 	default:
597 		if (_inLen >= _inMax)
598 			return;
599 
600 		if (arg < 32 || arg > 0xff)
601 			return;
602 
603 		if (_font._caps && (arg > 0x60 && arg < 0x7b))
604 			arg -= 0x20;
605 
606 		for (ix = _inLen; ix > _inCurs; ix--)
607 			ln->_chars[_inOrgX + ix] = ln->_chars[_inOrgX + ix - 1];
608 		ln->_attrs[_inOrgX + _inLen].set(style_Input);
609 		ln->_chars[_inOrgX + _inCurs] = arg;
610 
611 		_inCurs++;
612 		_inLen++;
613 	}
614 
615 	_curX = _inOrgX + _inCurs;
616 	_curY = _inOrgY;
617 
618 	touch(_inOrgY);
619 }
620 
redraw()621 void TextGridWindow::redraw() {
622 	TextGridRow *ln;
623 	int x0, y0;
624 	int x, y, w;
625 	int i, a, b, k, o;
626 	uint link;
627 	int font;
628 	uint fgcolor, bgcolor;
629 	Screen &screen = *g_vm->_screen;
630 
631 	gli_tts_flush();
632 
633 	Window::redraw();
634 
635 	x0 = _bbox.left;
636 	y0 = _bbox.top;
637 
638 	for (i = 0; i < _height; i++) {
639 		ln = &_lines[i];
640 		if (ln->dirty || Windows::_forceRedraw) {
641 			ln->dirty = false;
642 
643 			x = x0;
644 			y = y0 + i * _font._leading;
645 
646 			// clear any stored hyperlink coordinates
647 			g_vm->_selection->putHyperlink(0, x0, y, x0 + _font._cellW * _width, y + _font._leading);
648 
649 			a = 0;
650 			for (b = 0; b < _width; b++) {
651 				if (ln->_attrs[a] != ln->_attrs[b]) {
652 					link = ln->_attrs[a].hyper;
653 					font = ln->_attrs[a].attrFont(_styles);
654 					fgcolor = link ? _font._linkColor : ln->_attrs[a].attrFg(_styles);
655 					bgcolor = ln->_attrs[a].attrBg(_styles);
656 					w = (b - a) * _font._cellW;
657 					screen.fillRect(Rect::fromXYWH(x, y, w, _font._leading), bgcolor);
658 					o = x;
659 
660 					for (k = a, o = x; k < b; k++, o += _font._cellW) {
661 						screen.drawStringUni(Point(o * GLI_SUBPIX, y + _font._baseLine), font,
662 											 fgcolor, Common::U32String(&ln->_chars[k], 1), -1);
663 					}
664 					if (link) {
665 						screen.fillRect(Rect::fromXYWH(x, y + _font._baseLine + 1, w,
666 													   _font._linkStyle), _font._linkColor);
667 						g_vm->_selection->putHyperlink(link, x, y, x + w, y + _font._leading);
668 					}
669 
670 					x += w;
671 					a = b;
672 				}
673 			}
674 			link = ln->_attrs[a].hyper;
675 			font = ln->_attrs[a].attrFont(_styles);
676 			fgcolor = link ? _font._linkColor : ln->_attrs[a].attrFg(_styles);
677 			bgcolor = ln->_attrs[a].attrBg(_styles);
678 			w = (b - a) * _font._cellW;
679 			w += _bbox.right - (x + w);
680 			screen.fillRect(Rect::fromXYWH(x, y, w, _font._leading), bgcolor);
681 
682 			// Draw the caret if necessary
683 			if (_windows->getFocusWindow() == this && i == _curY &&
684 					(_lineRequest || _lineRequestUni || _charRequest || _charRequestUni)) {
685 				_font.drawCaret(Point((x0 + _curX * _font._cellW) * GLI_SUBPIX, y + _font._baseLine));
686 			}
687 
688 			// Write out the text
689 			for (k = a, o = x; k < b; k++, o += _font._cellW) {
690 				screen.drawStringUni(Point(o * GLI_SUBPIX, y + _font._baseLine), font,
691 									 fgcolor, Common::U32String(&ln->_chars[k], 1));
692 			}
693 			if (link) {
694 				screen.fillRect(Rect::fromXYWH(x, y + _font._baseLine + 1, w, _font._linkStyle), _font._linkColor);
695 				g_vm->_selection->putHyperlink(link, x, y, x + w, y + _font._leading);
696 			}
697 		}
698 	}
699 }
700 
getSize(uint * width,uint * height) const701 void TextGridWindow::getSize(uint *width, uint *height) const {
702 	if (width)
703 		*width = _bbox.width() / _font._cellW;
704 	if (height)
705 		*height = _bbox.height() / _font._cellH;
706 }
707 
708 /*--------------------------------------------------------------------------*/
709 
resize(size_t newSize)710 void TextGridWindow::TextGridRow::resize(size_t newSize) {
711 	size_t oldSize = _chars.size();
712 	if (newSize != oldSize) {
713 		_chars.resize(newSize);
714 		_attrs.resize(newSize);
715 
716 		if (newSize > oldSize)
717 			Common::fill(&_chars[0] + oldSize, &_chars[0] + newSize, ' ');
718 	}
719 }
720 
721 } // End of namespace Glk
722