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 "common/util.h"
24 #include "common/stack.h"
25 #include "common/unicode-bidi.h"
26 #include "graphics/primitives.h"
27 
28 #include "sci/sci.h"
29 #include "sci/engine/kernel.h"
30 #include "sci/engine/selector.h"
31 #include "sci/engine/state.h"
32 #include "sci/graphics/cache.h"
33 #include "sci/graphics/celobj32.h"
34 #include "sci/graphics/compare.h"
35 #include "sci/graphics/scifont.h"
36 #include "sci/graphics/frameout.h"
37 #include "sci/graphics/screen.h"
38 #include "sci/graphics/text32.h"
39 
40 namespace Sci {
41 
42 int16 GfxText32::_xResolution = 0;
43 int16 GfxText32::_yResolution = 0;
44 
GfxText32(SegManager * segMan,GfxCache * fonts)45 GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) :
46 	_segMan(segMan),
47 	_cache(fonts),
48 	// SSCI did not initialise height, so we intentionally do not do so also
49 	_width(0),
50 	_text(""),
51 	_bitmap(NULL_REG) {
52 		_fontId = kSci32SystemFont;
53 		_font = _cache->getFont(kSci32SystemFont);
54 	}
55 
init()56 void GfxText32::init() {
57 	_xResolution = g_sci->_gfxFrameout->getScriptWidth();
58 	_yResolution = g_sci->_gfxFrameout->getScriptHeight();
59 	// GK1 Korean patched version uses doubled resolution for fonts
60 	if (g_sci->getGameId() == GID_GK1 && g_sci->getLanguage() == Common::KO_KOR) {
61 		_xResolution = _xResolution * 2;
62 		_yResolution = _yResolution * 2;
63 	}
64 }
65 
createFontBitmap(int16 width,int16 height,const Common::Rect & rect,const Common::String & text,const uint8 foreColor,const uint8 backColor,const uint8 skipColor,const GuiResourceId fontId,const TextAlign alignment,const int16 borderColor,const bool dimmed,const bool doScaling,const bool gc)66 reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling, const bool gc) {
67 
68 	_borderColor = borderColor;
69 	_text = text;
70 	_textRect = rect;
71 	_width = width;
72 	_height = height;
73 	_foreColor = foreColor;
74 	_backColor = backColor;
75 	_skipColor = skipColor;
76 	_alignment = alignment;
77 	_dimmed = dimmed;
78 
79 	setFont(fontId);
80 
81 	if (doScaling) {
82 		int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
83 		int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
84 
85 		Ratio scaleX(_xResolution, scriptWidth);
86 		Ratio scaleY(_yResolution, scriptHeight);
87 
88 		_width = (_width * scaleX).toInt();
89 		_height = (_height * scaleY).toInt();
90 		mulinc(_textRect, scaleX, scaleY);
91 	}
92 
93 	// `_textRect` represents where text is drawn inside the bitmap; `clipRect`
94 	// is the entire bitmap
95 	Common::Rect bitmapRect(_width, _height);
96 
97 	if (_textRect.intersects(bitmapRect)) {
98 		_textRect.clip(bitmapRect);
99 	} else {
100 		_textRect = Common::Rect();
101 	}
102 
103 	_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc);
104 
105 	erase(bitmapRect, false);
106 
107 	if (_borderColor > -1) {
108 		drawFrame(bitmapRect, 1, _borderColor, false);
109 	}
110 
111 	drawTextBox();
112 	return _bitmap;
113 }
114 
createFontBitmap(const CelInfo32 & celInfo,const Common::Rect & rect,const Common::String & text,const int16 foreColor,const int16 backColor,const GuiResourceId fontId,const int16 skipColor,const int16 borderColor,const bool dimmed,const bool gc)115 reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed, const bool gc) {
116 	_borderColor = borderColor;
117 	_text = text;
118 	_textRect = rect;
119 	_foreColor = foreColor;
120 	_dimmed = dimmed;
121 
122 	setFont(fontId);
123 
124 	int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
125 	int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
126 
127 	mulinc(_textRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight));
128 
129 	CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo);
130 	_skipColor = view._skipColor;
131 	_width = view._width * _xResolution / view._xResolution;
132 	_height = view._height * _yResolution / view._yResolution;
133 
134 	Common::Rect bitmapRect(_width, _height);
135 	if (_textRect.intersects(bitmapRect)) {
136 		_textRect.clip(bitmapRect);
137 	} else {
138 		_textRect = Common::Rect();
139 	}
140 
141 	SciBitmap &bitmap = *_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc);
142 
143 	// SSCI filled the bitmap pixels with 11 here, which is silly because then
144 	// it just erased the bitmap using the skip color. So we don't fill the
145 	// bitmap redundantly here.
146 
147 	_backColor = _skipColor;
148 	erase(bitmapRect, false);
149 	_backColor = backColor;
150 
151 	view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_xResolution, view._xResolution), Ratio(_yResolution, view._yResolution));
152 
153 	if (_backColor != skipColor && _foreColor != skipColor) {
154 		erase(_textRect, false);
155 	}
156 
157 	if (text.size() > 0) {
158 		if (_foreColor == skipColor) {
159 			error("TODO: Implement transparent text");
160 		} else {
161 			if (borderColor != -1) {
162 				drawFrame(bitmapRect, 1, _borderColor, false);
163 			}
164 
165 			drawTextBox();
166 		}
167 	}
168 
169 	return _bitmap;
170 }
171 
createTitledFontBitmap(int16 width,int16 height,const Common::Rect & textRect,const Common::String & text,const uint8 foreColor,const uint8 backColor,const uint8 skipColor,const GuiResourceId fontId,const TextAlign alignment,const int16 borderColor,const Common::String & title,const uint8 titleForeColor,const uint8 titleBackColor,const GuiResourceId titleFontId,const bool doScaling,const bool gc)172 reg_t GfxText32::createTitledFontBitmap(int16 width, int16 height, const Common::Rect &textRect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const Common::String &title, const uint8 titleForeColor, const uint8 titleBackColor, const GuiResourceId titleFontId, const bool doScaling, const bool gc) {
173 
174 	_borderColor = borderColor;
175 	_width = width;
176 	_height = height;
177 	_skipColor = skipColor;
178 
179 	setFont(titleFontId);
180 
181 	int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
182 	int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
183 	Ratio scaleX(_xResolution, scriptWidth);
184 	Ratio scaleY(_yResolution, scriptHeight);
185 
186 	if (doScaling) {
187 		_width = (_width * scaleX).toInt();
188 		_height = (_height * scaleY).toInt();
189 	}
190 
191 	_text = title;
192 	int16 titleWidth;
193 	int16 titleHeight;
194 	getTextDimensions(0, 10000, titleWidth, titleHeight);
195 	if (getSciVersion() < SCI_VERSION_3) {
196 		GfxFont *titleFont = _cache->getFont(titleFontId);
197 		titleHeight = titleFont->getHeight();
198 	}
199 	titleWidth += 2;
200 	titleHeight += 1;
201 	if (borderColor != -1) {
202 		titleWidth += 2;
203 		titleHeight += 2;
204 	}
205 
206 	// allocate memory for the bitmap
207 	_segMan->allocateBitmap(&_bitmap, _width, _height, _skipColor, 0, 0, _xResolution, _yResolution, 0, false, gc);
208 
209 	// draw background
210 	_backColor = backColor;
211 	_textRect = Common::Rect(0, 0, width, height);
212 	erase(_textRect, false);
213 
214 	// draw title background
215 	_foreColor = titleForeColor;
216 	_backColor = titleBackColor;
217 	_alignment = kTextAlignCenter;
218 	_dimmed = false;
219 	_textRect.setHeight(titleHeight);
220 	erase(_textRect, false);
221 
222 	// draw title border
223 	if (borderColor != -1) {
224 		drawFrame(_textRect, 1, borderColor, false);
225 		_textRect.grow(-2);
226 	}
227 
228 	// draw title text
229 	drawTextBox();
230 
231 	setFont(fontId);
232 	_text = text;
233 	_foreColor = foreColor;
234 	_backColor = backColor;
235 	_alignment = alignment;
236 	_textRect = textRect;
237 	if (doScaling) {
238 		mulinc(_textRect, scaleX, scaleY);
239 	}
240 
241 	// draw text border
242 	Common::Rect textBorderRect(0, titleHeight - 1, _width, _height);
243 	_textRect.clip(textBorderRect);
244 	if (borderColor != -1) {
245 		drawFrame(textBorderRect, 1, borderColor, false);
246 	}
247 
248 	// draw text
249 	GfxFont *font = _cache->getFont(fontId);
250 	if (_textRect.height() >= font->getHeight()) {
251 		drawTextBox();
252 	}
253 
254 	return _bitmap;
255 }
256 
setFont(const GuiResourceId fontId)257 void GfxText32::setFont(const GuiResourceId fontId) {
258 	// In SSCI, this calls FontMgr::BuildFontTable, and then a font table is
259 	// built on the FontMgr directly; instead, because we already have GfxFont
260 	// resources from SCI16 and those resources did not change in SCI32, this
261 	// code just grabs those out of GfxCache
262 	if (fontId != _fontId) {
263 		_fontId = fontId;
264 		_font = _cache->getFont(_fontId);
265 	}
266 }
267 
drawFrame(const Common::Rect & rect,const int16 size,const uint8 color,const bool doScaling)268 void GfxText32::drawFrame(const Common::Rect &rect, const int16 size, const uint8 color, const bool doScaling) {
269 	Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
270 
271 	SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
272 	byte *pixels = bitmap.getPixels() + rect.top * _width + rect.left;
273 
274 	// Not fully disassembled, but appears to be correct in all cases
275 	int16 rectWidth = targetRect.width();
276 	int16 heightRemaining = targetRect.height();
277 	int16 sidesHeight = heightRemaining - size * 2;
278 	int16 centerWidth = rectWidth - size * 2;
279 	int16 stride = _width - rectWidth;
280 
281 	for (int16 y = 0; y < size && y < heightRemaining; ++y) {
282 		memset(pixels, color, rectWidth);
283 		pixels += _width;
284 		--heightRemaining;
285 	}
286 	for (int16 y = 0; y < sidesHeight; ++y) {
287 		for (int16 x = 0; x < size; ++x) {
288 			*pixels++ = color;
289 		}
290 		pixels += centerWidth;
291 		for (int16 x = 0; x < size; ++x) {
292 			*pixels++ = color;
293 		}
294 		pixels += stride;
295 	}
296 	for (int16 y = 0; y < size && y < heightRemaining; ++y) {
297 		memset(pixels, color, rectWidth);
298 		pixels += _width;
299 		--heightRemaining;
300 	}
301 }
302 
drawChar(const uint16 charIndex)303 void GfxText32::drawChar(const uint16 charIndex) {
304 	SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
305 	byte *pixels = bitmap.getPixels();
306 
307 	_font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
308 	_drawPosition.x += _font->getCharWidth(charIndex);
309 }
310 
getScaledFontHeight() const311 int16 GfxText32::getScaledFontHeight() const {
312 	const int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
313 	return (_font->getHeight() * scriptHeight + _yResolution - 1) / _yResolution;
314 }
315 
getCharWidth(const uint16 charIndex,const bool doScaling) const316 uint16 GfxText32::getCharWidth(const uint16 charIndex, const bool doScaling) const {
317 	uint16 width = _font->getCharWidth(charIndex);
318 	if (doScaling) {
319 		width = scaleUpWidth(width);
320 	}
321 	return width;
322 }
323 
drawTextBox()324 void GfxText32::drawTextBox() {
325 	if (_text.size() == 0) {
326 		return;
327 	}
328 
329 	const char *text = _text.c_str();
330 	const char *sourceText = text;
331 	int16 textRectWidth = _textRect.width();
332 	_drawPosition.y = _textRect.top;
333 	uint charIndex = 0;
334 
335 	if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
336 		if (getLongest(&charIndex, textRectWidth) == 0) {
337 			error("DrawTextBox GetLongest=0");
338 		}
339 	}
340 
341 	// Check for Korean text
342 	if (g_sci->getLanguage() == Common::KO_KOR)
343 		SwitchToFont1001OnKorean(text);
344 
345 	charIndex = 0;
346 	uint nextCharIndex = 0;
347 	while (*text != '\0') {
348 		_drawPosition.x = _textRect.left;
349 
350 		uint length = getLongest(&nextCharIndex, textRectWidth);
351 		int16 textWidth = getTextWidth(charIndex, length);
352 
353 		if (!g_sci->isLanguageRTL()) {
354 			if (_alignment == kTextAlignCenter) {
355 				_drawPosition.x += (textRectWidth - textWidth) / 2;
356 			} else if (_alignment == kTextAlignRight) {
357 				_drawPosition.x += textRectWidth - textWidth;
358 			}
359 		} else {
360 			if (_alignment == kTextAlignCenter) {
361 				_drawPosition.x += (textRectWidth - textWidth) / 2;
362 			} else if (_alignment == kTextAlignLeft) {
363 				_drawPosition.x += textRectWidth - textWidth;
364 			}
365 		}
366 
367 		drawText(charIndex, length);
368 		charIndex = nextCharIndex;
369 		text = sourceText + charIndex;
370 		_drawPosition.y += _font->getHeight();
371 	}
372 }
373 
drawTextBox(const Common::String & text)374 void GfxText32::drawTextBox(const Common::String &text) {
375 	_text = text;
376 	drawTextBox();
377 }
378 
drawText(const uint index,uint length)379 void GfxText32::drawText(const uint index, uint length) {
380 	assert(index + length <= _text.size());
381 
382 	// This draw loop implementation is somewhat different than the
383 	// implementation in SSCI, but is accurate. Primarily the changes revolve
384 	// around eliminating some extra temporaries and fixing the logic to match.
385 
386 	Common::String textString;
387 	const char *text;
388 	if (!g_sci->isLanguageRTL()) {
389 		text = _text.c_str() + index;
390 	} else {
391 		const char *textOrig = _text.c_str() + index;
392 		Common::String textLogical = Common::String(textOrig, (uint32)length);
393 		textString = Common::convertBiDiString(textLogical, g_sci->getLanguage());
394 		text = textString.c_str();
395 	}
396 
397 	while (length-- > 0) {
398 		uint16 currentChar = *(const byte *)text++;
399 		if (_font->isDoubleByte(currentChar)) {
400 			currentChar |= *text++ << 8;
401 		}
402 
403 		if (currentChar == '|') {
404 			const char controlChar = *text++;
405 			--length;
406 
407 			if (length == 0) {
408 				return;
409 			}
410 
411 			if (controlChar == 'a' || controlChar == 'c' || controlChar == 'f') {
412 				uint16 value = 0;
413 
414 				while (length > 0) {
415 					const char valueChar = *text;
416 					if (valueChar < '0' || valueChar > '9') {
417 						break;
418 					}
419 
420 					++text;
421 					--length;
422 					value = 10 * value + (valueChar - '0');
423 				}
424 
425 				if (length == 0) {
426 					return;
427 				}
428 
429 				if (controlChar == 'a') {
430 					_alignment = (TextAlign)value;
431 				} else if (controlChar == 'c') {
432 					_foreColor = value;
433 				} else if (controlChar == 'f') {
434 					setFont(value);
435 				}
436 			}
437 
438 			while (length > 0 && *text != '|') {
439 				++text;
440 				--length;
441 			}
442 			if (length > 0) {
443 				++text;
444 				--length;
445 			}
446 		} else {
447 			drawChar(currentChar);
448 		}
449 	}
450 }
451 
invertRect(const reg_t bitmapId,int16 bitmapStride,const Common::Rect & rect,const uint8 foreColor,const uint8 backColor,const bool doScaling)452 void GfxText32::invertRect(const reg_t bitmapId, int16 bitmapStride, const Common::Rect &rect, const uint8 foreColor, const uint8 backColor, const bool doScaling) {
453 	Common::Rect targetRect = rect;
454 	if (doScaling) {
455 		bitmapStride = bitmapStride * _xResolution / g_sci->_gfxFrameout->getScriptWidth();
456 		targetRect = scaleRect(rect);
457 	}
458 
459 	SciBitmap &bitmap = *_segMan->lookupBitmap(bitmapId);
460 
461 	// SSCI is super weird here; it seems to be trying to look at the entire
462 	// size of the bitmap including the header, instead of just the pixel data
463 	// size. We just look at the pixel size. This function generally is an odd
464 	// duck since the stride dimension for a bitmap is built in to the bitmap
465 	// header, so perhaps it was once an unheadered bitmap format and this
466 	// function was never updated to match? Or maybe they exploit the
467 	// configurable stride length somewhere else to do stair stepping inverts...
468 	uint32 invertSize = targetRect.height() * bitmapStride + targetRect.width();
469 	uint32 bitmapSize = bitmap.getDataSize();
470 
471 	if (invertSize >= bitmapSize) {
472 		error("InvertRect too big: %u >= %u", invertSize, bitmapSize);
473 	}
474 
475 	// SSCI just added a hardcoded bitmap header size here
476 	byte *pixel = bitmap.getPixels() + bitmapStride * targetRect.top + targetRect.left;
477 
478 	int16 stride = bitmapStride - targetRect.width();
479 	int16 targetHeight = targetRect.height();
480 	int16 targetWidth = targetRect.width();
481 
482 	for (int16 y = 0; y < targetHeight; ++y) {
483 		for (int16 x = 0; x < targetWidth; ++x) {
484 			if (*pixel == foreColor) {
485 				*pixel = backColor;
486 			} else if (*pixel == backColor) {
487 				*pixel = foreColor;
488 			}
489 
490 			++pixel;
491 		}
492 
493 		pixel += stride;
494 	}
495 }
496 
getLongest(uint * charIndex,const int16 width)497 uint GfxText32::getLongest(uint *charIndex, const int16 width) {
498 	assert(width > 0);
499 
500 	uint testLength = 0;
501 	uint length = 0;
502 
503 	const uint initialCharIndex = *charIndex;
504 
505 	// The index of the next word after the last word break
506 	uint lastWordBreakIndex = *charIndex;
507 
508 	const char *text = _text.c_str() + *charIndex;
509 
510 	uint16 currentChar = 0;
511 	while ((currentChar = *(const byte *)text++) != '\0') {
512 		if (_font->isDoubleByte(currentChar)) {
513 			currentChar |= (*text++) << 8;
514 		}
515 		// In SSCI, the font, color, and alignment were reset here to their
516 		// initial values; this does not seem to be necessary and really
517 		// complicates the font system, so we do not do it
518 
519 		// The text to render contains a line break; stop at the line break
520 		if (currentChar == '\r' || currentChar == '\n') {
521 			// Skip the rest of the line break if it is a Windows-style \r\n (or
522 			// non-standard \n\r)
523 
524 			// In SSCI, the `text` pointer had not been advanced yet here, so
525 			// the indexes used to access characters were one higher there
526 			if (
527 				(currentChar == '\r' && text[0] == '\n') ||
528 				(currentChar == '\n' && text[0] == '\r' && text[1] != '\n')
529 			) {
530 				++*charIndex;
531 			}
532 
533 			// We are at the end of a line but the last word in the line made
534 			// it too wide to fit in the text area; return up to the previous
535 			// word
536 			if (length && getTextWidth(initialCharIndex, testLength) > width) {
537 				*charIndex = lastWordBreakIndex;
538 				return length;
539 			}
540 
541 			// Skip the line break and return all text seen up to now
542 			// In SSCI, the font, color, and alignment were reset, then
543 			// getTextWidth was called to use its side-effects to set font,
544 			// color, and alignment according to the text from
545 			// `initialCharIndex` to `testLength`. This is complicated and
546 			// apparently not necessary for correct operation, so we do not do
547 			// it
548 
549 			++*charIndex;
550 			return testLength;
551 		} else if (currentChar == ' ') {
552 			// The last word in the line made it too wide to fit in the text
553 			// area; return up to the previous word, then collapse the
554 			// whitespace between that word and its next sibling word into the
555 			// line break
556 			if (getTextWidth(initialCharIndex, testLength) > width) {
557 				*charIndex = lastWordBreakIndex;
558 				const char *nextChar = _text.c_str() + lastWordBreakIndex;
559 				while (*nextChar++ == ' ') {
560 					++*charIndex;
561 				}
562 
563 				// In SSCI, the font, color, and alignment were set here to the
564 				// values that were seen at the last space character, but this
565 				// is complicated and unnecessary so we do not do it
566 
567 				return length;
568 			}
569 
570 			// In SSCI, the values of `_fontId`, `_foreColor`, and `_alignment`
571 			// were stored for use in the return path mentioned just above here,
572 			// but we do not need to do this because we do not cause
573 			// side-effects when calculating text dimensions
574 
575 			// We found a word break that was within the text area, memorise it
576 			// and continue processing. +1 on the character index because it has
577 			// not been incremented yet so currently points to the word break
578 			// and not the word after the break
579 			length = testLength;
580 			lastWordBreakIndex = *charIndex + 1;
581 		}
582 
583 		// In the middle of a line, keep processing
584 		++*charIndex;
585 		++testLength;
586 		if (_font->isDoubleByte(currentChar)) {
587 			++*charIndex;
588 		}
589 
590 		// In SSCI, the font, color, and alignment were reset here to their
591 		// initial values, but we do not need to do this because we do not cause
592 		// side-effects when calculating text dimensions
593 
594 		// The text to render contained no word breaks yet but is already too
595 		// wide for the text area; just split the word in half at the point
596 		// where it overflows
597 		if (length == 0 && getTextWidth(initialCharIndex, testLength) > width) {
598 			*charIndex = --testLength + lastWordBreakIndex;
599 			return testLength;
600 		}
601 	}
602 
603 	// The complete text to render was a single word, or was narrower than
604 	// the text area, so return the entire line
605 	if (length == 0 || getTextWidth(initialCharIndex, testLength) <= width) {
606 		// In SSCI, the font, color, and alignment were reset, then getTextWidth
607 		// was called to use its side-effects to set font, color, and alignment
608 		// according to the text from `initialCharIndex` to `testLength`. This
609 		// is not necessary because we do not cause side-effects when
610 		// calculating text dimensions
611 		return testLength;
612 	}
613 
614 	// The last word in the line made it wider than the text area, so return
615 	// up to the penultimate word
616 	*charIndex = lastWordBreakIndex;
617 	return length;
618 }
619 
getTextWidth(const uint index,uint length) const620 int16 GfxText32::getTextWidth(const uint index, uint length) const {
621 	int16 width;
622 	int16 height;
623 	getTextDimensions(index, length, width, height);
624 	return width;
625 }
626 
getTextDimensions(const uint index,uint length,int16 & width,int16 & height) const627 void GfxText32::getTextDimensions(const uint index, uint length, int16 &width, int16& height) const {
628 	width = 0;
629 	height = 0;
630 
631 	const char *text = _text.c_str() + index;
632 
633 	GfxFont *font = _font;
634 
635 	uint16 currentChar = *(const byte *)text++;
636 	while (length > 0 && currentChar != '\0') {
637 		if (_font->isDoubleByte(currentChar)) {
638 			currentChar |= (*text++) << 8;
639 		}
640 		// Control codes are in the format `|<code><value>|`
641 		if (currentChar == '|') {
642 			// SSCI changed the global state of the FontMgr here upon
643 			// encountering any color, alignment, or font control code. To avoid
644 			// requiring all callers to manually restore these values on every
645 			// call, we ignore control codes other than font change (since
646 			// alignment and color do not change the width of characters), and
647 			// simply update the font pointer on stack instead of the member
648 			// property font to avoid these unnecessary side-effects.
649 			currentChar = *text++;
650 			--length;
651 
652 			if (length > 0 && currentChar == 'f') {
653 				GuiResourceId fontId = 0;
654 				while (length > 0 && *text >= '0' && *text <= '9') {
655 					currentChar = *text++;
656 					--length;
657 
658 					fontId = fontId * 10 + currentChar - '0';
659 				}
660 
661 				if (length > 0) {
662 					font = _cache->getFont(fontId);
663 				}
664 			}
665 
666 			// Forward through any more unknown control character data
667 			while (length > 0 && *text != '|') {
668 				++text;
669 				--length;
670 			}
671 			if (length > 0) {
672 				++text;
673 				--length;
674 			}
675 		} else {
676 			width += font->getCharWidth((unsigned char)currentChar);
677 			byte charHeight = font->getCharHeight((unsigned char)currentChar);
678 			if (height < charHeight) {
679 				height = charHeight;
680 			}
681 		}
682 
683 		if (length > 0) {
684 			currentChar = *text++;
685 			--length;
686 		}
687 	}
688 }
689 
getTextWidth(const Common::String & text,const uint index,const uint length)690 int16 GfxText32::getTextWidth(const Common::String &text, const uint index, const uint length) {
691 	_text = text;
692 	return scaleUpWidth(getTextWidth(index, length));
693 }
694 
getTextSize(const Common::String & text,int16 maxWidth,bool doScaling)695 Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, bool doScaling) {
696 	// Like most of the text rendering code, this function was pretty weird in
697 	// SSCI. The initial result rectangle was actually a 1x1 rectangle
698 	// (0, 0, 0, 0), which was then "fixed" after the main text size loop
699 	// finished running by subtracting 1 from the right and bottom edges. Like
700 	// other functions in SCI32, this has been converted to use exclusive rects
701 	// with inclusive rounding.
702 
703 	Common::Rect result;
704 
705 	int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
706 	int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
707 
708 	maxWidth = maxWidth * _xResolution / scriptWidth;
709 
710 	_text = text;
711 
712 	if (maxWidth >= 0) {
713 		if (maxWidth == 0) {
714 			maxWidth = _xResolution * 3 / 5;
715 		}
716 
717 		result.right = maxWidth;
718 
719 		int16 textWidth = 0;
720 		if (_text.size() > 0) {
721 			const char *rawText = _text.c_str();
722 			const char *sourceText = rawText;
723 
724 			// Check for Korean text
725 			if (g_sci->getLanguage() == Common::KO_KOR)
726 				SwitchToFont1001OnKorean(rawText);
727 
728 			uint charIndex = 0;
729 			uint nextCharIndex = 0;
730 			while (*rawText != '\0') {
731 				uint length = getLongest(&nextCharIndex, result.width());
732 				textWidth = MAX(textWidth, getTextWidth(charIndex, length));
733 				charIndex = nextCharIndex;
734 				rawText = sourceText + charIndex;
735 				// TODO: Due to getLongest and getTextWidth not having side
736 				// effects, it is possible that the currently loaded font's
737 				// height is wrong for this line if it was changed inline
738 				result.bottom += _font->getHeight();
739 			}
740 		}
741 
742 		if (textWidth < maxWidth) {
743 			result.right = textWidth;
744 		}
745 	} else {
746 		result.right = getTextWidth(0, 10000);
747 
748 		if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) {
749 			result.bottom = 0;
750 		} else {
751 			// In SSCI, the bottom was not decremented by 1, which means that
752 			// the rect was actually a pixel taller than the height of the font.
753 			// This was not the case in the other branch, which decremented the
754 			// bottom by 1 at the end of the loop. For accuracy, we do what SSCI
755 			// did, even though this means the result is a pixel off
756 			result.bottom = _font->getHeight() + 1;
757 		}
758 	}
759 
760 	if (doScaling) {
761 		// SSCI also scaled top/left but these are always zero so there is no
762 		// reason to do that
763 		result.right = ((result.right - 1) * scriptWidth + _xResolution - 1) / _xResolution + 1;
764 		result.bottom = ((result.bottom - 1) * scriptHeight + _yResolution - 1) / _yResolution + 1;
765 	}
766 
767 	return result;
768 }
769 
erase(const Common::Rect & rect,const bool doScaling)770 void GfxText32::erase(const Common::Rect &rect, const bool doScaling) {
771 	Common::Rect targetRect = doScaling ? scaleRect(rect) : rect;
772 
773 	SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
774 	bitmap.getBuffer().fillRect(targetRect, _backColor);
775 }
776 
getStringWidth(const Common::String & text)777 int16 GfxText32::getStringWidth(const Common::String &text) {
778 	return getTextWidth(text, 0, 10000);
779 }
780 
getTextCount(const Common::String & text,const uint index,const Common::Rect & textRect,const bool doScaling)781 int16 GfxText32::getTextCount(const Common::String &text, const uint index, const Common::Rect &textRect, const bool doScaling) {
782 	const int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
783 	const int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
784 
785 	Common::Rect scaledRect(textRect);
786 	if (doScaling) {
787 		mulinc(scaledRect, Ratio(_xResolution, scriptWidth), Ratio(_yResolution, scriptHeight));
788 	}
789 
790 	Common::String oldText = _text;
791 	_text = text;
792 
793 	uint charIndex = index;
794 	int16 maxWidth = scaledRect.width();
795 	int16 lineCount = (scaledRect.height() - 2) / _font->getHeight();
796 	while (lineCount--) {
797 		getLongest(&charIndex, maxWidth);
798 	}
799 
800 	_text = oldText;
801 	return charIndex - index;
802 }
803 
getTextCount(const Common::String & text,const uint index,const GuiResourceId fontId,const Common::Rect & textRect,const bool doScaling)804 int16 GfxText32::getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling) {
805 	setFont(fontId);
806 	return getTextCount(text, index, textRect, doScaling);
807 }
808 
scrollLine(const Common::String & lineText,int numLines,uint8 color,TextAlign align,GuiResourceId fontId,ScrollDirection dir)809 void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) {
810 	SciBitmap &bmr = *_segMan->lookupBitmap(_bitmap);
811 	byte *pixels = bmr.getPixels();
812 
813 	int h = _font->getHeight();
814 
815 	if (dir == kScrollUp) {
816 		// Scroll existing text down
817 		for (int i = 0; i < (numLines - 1) * h; ++i) {
818 			int y = _textRect.top + numLines * h - i - 1;
819 			memcpy(pixels + y * _width + _textRect.left,
820 			       pixels + (y - h) * _width + _textRect.left,
821 			       _textRect.width());
822 		}
823 	} else {
824 		// Scroll existing text up
825 		for (int i = 0; i < (numLines - 1) * h; ++i) {
826 			int y = _textRect.top + i;
827 			memcpy(pixels + y * _width + _textRect.left,
828 			       pixels + (y + h) * _width + _textRect.left,
829 			       _textRect.width());
830 		}
831 	}
832 
833 	Common::Rect lineRect = _textRect;
834 
835 	if (dir == kScrollUp) {
836 		lineRect.bottom = lineRect.top + h;
837 	} else {
838 		// It is unclear to me what the purpose of this bottom++ is.
839 		// It does not seem to be the usual inc/exc issue.
840 		lineRect.top += (numLines - 1) * h;
841 		lineRect.bottom++;
842 	}
843 
844 	erase(lineRect, false);
845 
846 	_drawPosition.x = _textRect.left;
847 	_drawPosition.y = _textRect.top;
848 	if (dir == kScrollDown) {
849 		_drawPosition.y += (numLines - 1) * h;
850 	}
851 
852 	_foreColor = color;
853 	_alignment = align;
854 
855 	// As with other font functions, SSCI saved _foreColor here so it could be
856 	// restored after the getTextWidth call, but this call is side-effect-free
857 	// in our implementation so this is not necessary
858 
859 	setFont(fontId);
860 
861 	_text = lineText;
862 	int16 textWidth = getTextWidth(0, lineText.size());
863 
864 	if (_alignment == kTextAlignCenter) {
865 		_drawPosition.x += (_textRect.width() - textWidth) / 2;
866 	} else if (_alignment == kTextAlignRight) {
867 		_drawPosition.x += _textRect.width() - textWidth;
868 	}
869 
870 	// _foreColor and font were restored here in SSCI due to side-effects of
871 	// getTextWidth which do not exist in our implementation
872 
873 	drawText(0, lineText.size());
874 }
875 
876 // Check for Korean strings, and use font 1001 to render them
SwitchToFont1001OnKorean(const char * text)877 bool GfxText32::SwitchToFont1001OnKorean(const char *text) {
878 	const byte *ptr = (const byte *)text;
879 	// Check if the text contains at least one Korean character
880 	while (*ptr) {
881 		byte ch = *ptr++;
882 		if (ch >= 0xB0 && ch <= 0xC8) {
883 			ch = *ptr++;
884 			if (!ch)
885 				return false;
886 
887 			if (ch >= 0xA1 && ch <= 0xFE) {
888 				setFont(1001);
889 				return true;
890 			}
891 		}
892 	}
893 	return false;
894 }
895 
896 } // End of namespace Sci
897