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