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/scummsys.h"
24 #include "common/file.h"
25 #include "common/tokenizer.h"
26 #include "common/debug.h"
27 #include "common/rect.h"
28 #include "graphics/fontman.h"
29 #include "graphics/colormasks.h"
30 #include "graphics/surface.h"
31 #include "graphics/font.h"
32 #include "graphics/fonts/ttf.h"
33
34 #include "zvision/text/text.h"
35 #include "zvision/graphics/render_manager.h"
36 #include "zvision/text/truetype_font.h"
37 #include "zvision/scripting/script_manager.h"
38
39 namespace ZVision {
40
TextStyleState()41 TextStyleState::TextStyleState() {
42 _fontname = "Arial";
43 _blue = 255;
44 _green = 255;
45 _red = 255;
46 _bold = false;
47 #if 0
48 _newline = false;
49 _escapement = 0;
50 #endif
51 _italic = false;
52 _justification = TEXT_JUSTIFY_LEFT;
53 _size = 12;
54 #if 0
55 _skipcolor = false;
56 #endif
57 _strikeout = false;
58 _underline = false;
59 _statebox = 0;
60 _sharp = false;
61 }
62
parseStyle(const Common::String & str,int16 len)63 TextChange TextStyleState::parseStyle(const Common::String &str, int16 len) {
64 Common::String buf = Common::String(str.c_str(), len);
65
66 uint retval = TEXT_CHANGE_NONE;
67
68 Common::StringTokenizer tokenizer(buf, " ");
69 Common::String token;
70
71 while (!tokenizer.empty()) {
72 token = tokenizer.nextToken();
73
74 if (token.matchString("font", true)) {
75 token = tokenizer.nextToken();
76 if (token[0] == '"') {
77 Common::String _tmp = Common::String(token.c_str() + 1);
78
79 while (token.lastChar() != '"' && !tokenizer.empty()) {
80 token = tokenizer.nextToken();
81 _tmp += " " + token;
82 }
83
84 if (_tmp.lastChar() == '"')
85 _tmp.deleteLastChar();
86
87 _fontname = _tmp;
88 } else {
89 if (!tokenizer.empty())
90 _fontname = token;
91 }
92 retval |= TEXT_CHANGE_FONT_TYPE;
93
94 } else if (token.matchString("blue", true)) {
95 if (!tokenizer.empty()) {
96 token = tokenizer.nextToken();
97 int32 tmp = atoi(token.c_str());
98 if (_blue != tmp) {
99 _blue = tmp;
100 retval |= TEXT_CHANGE_FONT_STYLE;
101 }
102 }
103 } else if (token.matchString("red", true)) {
104 if (!tokenizer.empty()) {
105 token = tokenizer.nextToken();
106 int32 tmp = atoi(token.c_str());
107 if (_red != tmp) {
108 _red = tmp;
109 retval |= TEXT_CHANGE_FONT_STYLE;
110 }
111 }
112 } else if (token.matchString("green", true)) {
113 if (!tokenizer.empty()) {
114 token = tokenizer.nextToken();
115 int32 tmp = atoi(token.c_str());
116 if (_green != tmp) {
117 _green = tmp;
118 retval |= TEXT_CHANGE_FONT_STYLE;
119 }
120 }
121 } else if (token.matchString("newline", true)) {
122 #if 0
123 if ((retval & TXT_RET_NEWLN) == 0)
124 _newline = 0;
125
126 _newline++;
127 #endif
128 retval |= TEXT_CHANGE_NEWLINE;
129 } else if (token.matchString("point", true)) {
130 if (!tokenizer.empty()) {
131 token = tokenizer.nextToken();
132 int32 tmp = atoi(token.c_str());
133 if (_size != tmp) {
134 _size = tmp;
135 retval |= TEXT_CHANGE_FONT_TYPE;
136 }
137 }
138 } else if (token.matchString("escapement", true)) {
139 if (!tokenizer.empty()) {
140 token = tokenizer.nextToken();
141 #if 0
142 int32 tmp = atoi(token.c_str());
143 _escapement = tmp;
144 #endif
145 }
146 } else if (token.matchString("italic", true)) {
147 if (!tokenizer.empty()) {
148 token = tokenizer.nextToken();
149 if (token.matchString("on", true)) {
150 if (_italic != true) {
151 _italic = true;
152 retval |= TEXT_CHANGE_FONT_STYLE;
153 }
154 } else if (token.matchString("off", true)) {
155 if (_italic != false) {
156 _italic = false;
157 retval |= TEXT_CHANGE_FONT_STYLE;
158 }
159 }
160 }
161 } else if (token.matchString("underline", true)) {
162 if (!tokenizer.empty()) {
163 token = tokenizer.nextToken();
164 if (token.matchString("on", true)) {
165 if (_underline != true) {
166 _underline = true;
167 retval |= TEXT_CHANGE_FONT_STYLE;
168 }
169 } else if (token.matchString("off", true)) {
170 if (_underline != false) {
171 _underline = false;
172 retval |= TEXT_CHANGE_FONT_STYLE;
173 }
174 }
175 }
176 } else if (token.matchString("strikeout", true)) {
177 if (!tokenizer.empty()) {
178 token = tokenizer.nextToken();
179 if (token.matchString("on", true)) {
180 if (_strikeout != true) {
181 _strikeout = true;
182 retval |= TEXT_CHANGE_FONT_STYLE;
183 }
184 } else if (token.matchString("off", true)) {
185 if (_strikeout != false) {
186 _strikeout = false;
187 retval |= TEXT_CHANGE_FONT_STYLE;
188 }
189 }
190 }
191 } else if (token.matchString("bold", true)) {
192 if (!tokenizer.empty()) {
193 token = tokenizer.nextToken();
194 if (token.matchString("on", true)) {
195 if (_bold != true) {
196 _bold = true;
197 retval |= TEXT_CHANGE_FONT_STYLE;
198 }
199 } else if (token.matchString("off", true)) {
200 if (_bold != false) {
201 _bold = false;
202 retval |= TEXT_CHANGE_FONT_STYLE;
203 }
204 }
205 }
206 } else if (token.matchString("skipcolor", true)) {
207 if (!tokenizer.empty()) {
208 token = tokenizer.nextToken();
209 #if 0
210 if (token.matchString("on", true)) {
211 _skipcolor = true;
212 } else if (token.matchString("off", true)) {
213 _skipcolor = false;
214 }
215 #endif
216 }
217 } else if (token.matchString("image", true)) {
218 // Not used
219 } else if (token.matchString("statebox", true)) {
220 if (!tokenizer.empty()) {
221 token = tokenizer.nextToken();
222 _statebox = atoi(token.c_str());
223 retval |= TEXT_CHANGE_HAS_STATE_BOX;
224 }
225 } else if (token.matchString("justify", true)) {
226 if (!tokenizer.empty()) {
227 token = tokenizer.nextToken();
228 if (token.matchString("center", true))
229 _justification = TEXT_JUSTIFY_CENTER;
230 else if (token.matchString("left", true))
231 _justification = TEXT_JUSTIFY_LEFT;
232 else if (token.matchString("right", true))
233 _justification = TEXT_JUSTIFY_RIGHT;
234 }
235 }
236 }
237 return (TextChange)retval;
238 }
239
readAllStyles(const Common::String & txt)240 void TextStyleState::readAllStyles(const Common::String &txt) {
241 int16 startTextPosition = -1;
242 int16 endTextPosition = -1;
243
244 for (uint16 i = 0; i < txt.size(); i++) {
245 if (txt[i] == '<')
246 startTextPosition = i;
247 else if (txt[i] == '>') {
248 endTextPosition = i;
249 if (startTextPosition != -1) {
250 if ((endTextPosition - startTextPosition - 1) > 0) {
251 parseStyle(Common::String(txt.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
252 }
253 }
254 }
255
256 }
257 }
258
updateFontWithTextState(StyledTTFont & font)259 void TextStyleState::updateFontWithTextState(StyledTTFont &font) {
260 uint tempStyle = 0;
261
262 if (_bold) {
263 tempStyle |= StyledTTFont::TTF_STYLE_BOLD;
264 }
265 if (_italic) {
266 tempStyle |= StyledTTFont::TTF_STYLE_ITALIC;
267 }
268 if (_underline) {
269 tempStyle |= StyledTTFont::TTF_STYLE_UNDERLINE;
270 }
271 if (_strikeout) {
272 tempStyle |= StyledTTFont::TTF_STYLE_STRIKETHROUGH;
273 }
274 if (_sharp) {
275 tempStyle |= StyledTTFont::TTF_STYLE_SHARP;
276 }
277
278 font.loadFont(_fontname, _size, tempStyle);
279 }
280
drawTextWithJustification(const Common::String & text,StyledTTFont & font,uint32 color,Graphics::Surface & dest,int lineY,TextJustification justify)281 void TextRenderer::drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification justify) {
282 if (justify == TEXT_JUSTIFY_LEFT)
283 font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignLeft);
284 else if (justify == TEXT_JUSTIFY_CENTER)
285 font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignCenter);
286 else if (justify == TEXT_JUSTIFY_RIGHT)
287 font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignRight);
288 }
289
drawText(const Common::String & text,TextStyleState & state,Graphics::Surface & dest)290 int32 TextRenderer::drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest) {
291 StyledTTFont font(_engine);
292 state.updateFontWithTextState(font);
293
294 uint32 color = _engine->_resourcePixelFormat.RGBToColor(state._red, state._green, state._blue);
295 drawTextWithJustification(text, font, color, dest, 0, state._justification);
296
297 return font.getStringWidth(text);
298 }
299
300 struct TextSurface {
TextSurfaceZVision::TextSurface301 TextSurface(Graphics::Surface *surface, Common::Point surfaceOffset, uint lineNumber)
302 : _surface(surface),
303 _surfaceOffset(surfaceOffset),
304 _lineNumber(lineNumber) {
305 }
306
307 Graphics::Surface *_surface;
308 Common::Point _surfaceOffset;
309 uint _lineNumber;
310 };
311
drawTextWithWordWrapping(const Common::String & text,Graphics::Surface & dest)312 void TextRenderer::drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest) {
313 Common::Array<TextSurface> textSurfaces;
314 Common::Array<uint> lineWidths;
315 Common::Array<TextJustification> lineJustifications;
316
317 // Create the initial text state
318 TextStyleState currentState;
319
320 // Create an empty font and bind it to the state
321 StyledTTFont font(_engine);
322 currentState.updateFontWithTextState(font);
323
324 Common::String currentSentence; // Not a true 'grammatical' sentence. Rather, it's just a collection of words
325 Common::String currentWord;
326 int sentenceWidth = 0;
327 int wordWidth = 0;
328 int lineWidth = 0;
329 int lineHeight = font.getFontHeight();
330
331 uint currentLineNumber = 0u;
332
333 uint numSpaces = 0u;
334 int spaceWidth = 0;
335
336 // The pixel offset to the currentSentence
337 Common::Point sentencePixelOffset;
338
339 uint i = 0u;
340 uint stringlen = text.size();
341
342 while (i < stringlen) {
343 if (text[i] == '<') {
344 // Flush the currentWord to the currentSentence
345 currentSentence += currentWord;
346 sentenceWidth += wordWidth;
347
348 // Reset the word variables
349 currentWord.clear();
350 wordWidth = 0;
351
352 // Parse the style tag
353 uint startTextPosition = i;
354 while (i < stringlen && text[i] != '>') {
355 ++i;
356 }
357 uint endTextPosition = i;
358
359 uint32 textColor = currentState.getTextColor(_engine);
360
361 uint stateChanges = 0u;
362 if ((endTextPosition - startTextPosition - 1) > 0) {
363 stateChanges = currentState.parseStyle(Common::String(text.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
364 }
365
366 if (stateChanges & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) {
367 // Use the last state to render out the current sentence
368 // Styles apply to the text 'after' them
369 if (!currentSentence.empty()) {
370 textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
371
372 lineWidth += sentenceWidth;
373 sentencePixelOffset.x += sentenceWidth;
374
375 // Reset the sentence variables
376 currentSentence.clear();
377 sentenceWidth = 0;
378 }
379
380 // Update the current state with the style information
381 currentState.updateFontWithTextState(font);
382
383 lineHeight = MAX(lineHeight, font.getFontHeight());
384 spaceWidth = font.getCharWidth(' ');
385 }
386 if (stateChanges & TEXT_CHANGE_NEWLINE) {
387 // If the current sentence has content, render it out
388 if (!currentSentence.empty()) {
389 textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
390 }
391
392 // Set line width
393 lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
394
395 currentSentence.clear();
396 sentenceWidth = 0;
397
398 // Update the offsets
399 sentencePixelOffset.x = 0u;
400 sentencePixelOffset.y += lineHeight;
401
402 // Reset the line variables
403 lineHeight = font.getFontHeight();
404 lineWidth = 0;
405 ++currentLineNumber;
406 lineJustifications.push_back(currentState._justification);
407 }
408 if (stateChanges & TEXT_CHANGE_HAS_STATE_BOX) {
409 Common::String temp = Common::String::format("%d", _engine->getScriptManager()->getStateValue(currentState._statebox));
410 wordWidth += font.getStringWidth(temp);
411
412 // If the word causes the line to overflow, render the sentence and start a new line
413 if (lineWidth + sentenceWidth + wordWidth > dest.w) {
414 textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
415
416 // Set line width
417 lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
418
419 currentSentence.clear();
420 sentenceWidth = 0;
421
422 // Update the offsets
423 sentencePixelOffset.x = 0u;
424 sentencePixelOffset.y += lineHeight;
425
426 // Reset the line variables
427 lineHeight = font.getFontHeight();
428 lineWidth = 0;
429 ++currentLineNumber;
430 lineJustifications.push_back(currentState._justification);
431 }
432 }
433 } else {
434 currentWord += text[i];
435 wordWidth += font.getCharWidth(text[i]);
436
437 if (text[i] == ' ') {
438 // When we hit the first space, flush the current word to the sentence
439 if (!currentWord.empty()) {
440 currentSentence += currentWord;
441 sentenceWidth += wordWidth;
442
443 currentWord.clear();
444 wordWidth = 0;
445 }
446
447 // We track the number of spaces so we can disregard their width in lineWidth calculations
448 ++numSpaces;
449 } else {
450 // If the word causes the line to overflow, render the sentence and start a new line
451 if (lineWidth + sentenceWidth + wordWidth > dest.w) {
452 // Only render out content
453 if (!currentSentence.empty()) {
454 textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
455 }
456
457 // Set line width
458 lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
459
460 currentSentence.clear();
461 sentenceWidth = 0;
462
463 // Update the offsets
464 sentencePixelOffset.x = 0u;
465 sentencePixelOffset.y += lineHeight;
466
467 // Reset the line variables
468 lineHeight = font.getFontHeight();
469 lineWidth = 0;
470 ++currentLineNumber;
471 lineJustifications.push_back(currentState._justification);
472 }
473
474 numSpaces = 0u;
475 }
476 }
477
478 i++;
479 }
480
481 // Render out any remaining words/sentences
482 if (!currentWord.empty() || !currentSentence.empty()) {
483 currentSentence += currentWord;
484 sentenceWidth += wordWidth;
485
486 textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
487 }
488
489 lineWidths.push_back(lineWidth + sentenceWidth);
490 lineJustifications.push_back(currentState._justification);
491
492 for (Common::Array<TextSurface>::iterator iter = textSurfaces.begin(); iter != textSurfaces.end(); ++iter) {
493 Common::Rect empty;
494
495 if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_LEFT) {
496 _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0);
497 } else if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_CENTER) {
498 _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, ((dest.w - lineWidths[iter->_lineNumber]) / 2) + iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0);
499 } else if (lineJustifications[iter->_lineNumber] == TEXT_JUSTIFY_RIGHT) {
500 _engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, dest.w - lineWidths[iter->_lineNumber] + iter->_surfaceOffset.x, iter->_surfaceOffset.y, 0);
501 }
502
503 // Release memory
504 iter->_surface->free();
505 delete iter->_surface;
506 }
507 }
508
readWideLine(Common::SeekableReadStream & stream)509 Common::String readWideLine(Common::SeekableReadStream &stream) {
510 Common::String asciiString;
511
512 while (true) {
513 uint32 value = stream.readUint16LE();
514 if (stream.eos())
515 break;
516 // Check for CRLF
517 if (value == 0x0A0D) {
518 // Read in the extra NULL char
519 stream.readByte(); // \0
520 // End of the line. Break
521 break;
522 }
523
524 // Crush each octet pair to a UTF-8 sequence
525 if (value < 0x80) {
526 asciiString += (char)(value & 0x7F);
527 } else if (value >= 0x80 && value < 0x800) {
528 asciiString += (char)(0xC0 | ((value >> 6) & 0x1F));
529 asciiString += (char)(0x80 | (value & 0x3F));
530 } else if (value >= 0x800 && value < 0x10000 && value != 0xCCCC) {
531 asciiString += (char)(0xE0 | ((value >> 12) & 0xF));
532 asciiString += (char)(0x80 | ((value >> 6) & 0x3F));
533 asciiString += (char)(0x80 | (value & 0x3F));
534 } else if (value == 0xCCCC) {
535 // Ignore, this character is used as newline sometimes
536 } else if (value >= 0x10000 && value < 0x200000) {
537 asciiString += (char)(0xF0);
538 asciiString += (char)(0x80 | ((value >> 12) & 0x3F));
539 asciiString += (char)(0x80 | ((value >> 6) & 0x3F));
540 asciiString += (char)(0x80 | (value & 0x3F));
541 }
542 }
543
544 return asciiString;
545 }
546
getUtf8CharSize(char chr)547 int8 getUtf8CharSize(char chr) {
548 if ((chr & 0x80) == 0)
549 return 1;
550 else if ((chr & 0xE0) == 0xC0)
551 return 2;
552 else if ((chr & 0xF0) == 0xE0)
553 return 3;
554 else if ((chr & 0xF8) == 0xF0)
555 return 4;
556 else if ((chr & 0xFC) == 0xF8)
557 return 5;
558 else if ((chr & 0xFE) == 0xFC)
559 return 6;
560
561 return 1;
562 }
563
readUtf8Char(const char * chr)564 uint16 readUtf8Char(const char *chr) {
565 uint16 result = 0;
566 if ((chr[0] & 0x80) == 0)
567 result = chr[0];
568 else if ((chr[0] & 0xE0) == 0xC0)
569 result = ((chr[0] & 0x1F) << 6) | (chr[1] & 0x3F);
570 else if ((chr[0] & 0xF0) == 0xE0)
571 result = ((chr[0] & 0x0F) << 12) | ((chr[1] & 0x3F) << 6) | (chr[2] & 0x3F);
572 else
573 result = chr[0];
574
575 return result;
576 }
577
578 } // End of namespace ZVision
579