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/macresman.h"
24 
25 #include "scumm/charset.h"
26 #include "scumm/file.h"
27 #include "scumm/scumm.h"
28 #include "scumm/nut_renderer.h"
29 #include "scumm/util.h"
30 #include "scumm/he/intern_he.h"
31 #include "scumm/he/wiz_he.h"
32 
33 namespace Scumm {
34 
35 /*
36 TODO:
37 Right now our charset renderers directly access _textSurface, as well as the
38 virtual screens of ScummEngine. Ideally, this would not be the case. Instead,
39 ScummVM would simply pass the appropriate Surface to the resp. methods.
40 Of course it is not quite as simple, various flags and offsets have to
41 be taken into account for that.
42 
43 The advantage will be cleaner coder (easier to debug, in particular), and a
44 better separation of the various modules.
45 */
46 
isScummvmKorTarget()47 bool ScummEngine::isScummvmKorTarget() {
48 	if (_language == Common::KO_KOR && (_game.version < 7 || _game.id == GID_FT)) {
49 		return true;
50 	}
51 	return false;
52 }
53 
loadCJKFont()54 void ScummEngine::loadCJKFont() {
55 	_useCJKMode = false;
56 	_textSurfaceMultiplier = 1;
57 	_newLineCharacter = 0;
58 
59 	_useMultiFont = 0;	// Korean Multi-Font
60 
61 	// Special case for Korean
62 	if (isScummvmKorTarget()) {
63 		loadKorFont();
64 
65 		return;
66 	}
67 
68 	ScummFile fp;
69 
70 	if (_game.version <= 5 && _game.platform == Common::kPlatformFMTowns && _language == Common::JA_JPN) { // FM-TOWNS v3 / v5 Kanji
71 #if defined(DISABLE_TOWNS_DUAL_LAYER_MODE) || !defined(USE_RGB_COLOR)
72 		GUIErrorMessage("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build");
73 		error("FM-Towns Kanji font drawing requires dual graphics layer support which is disabled in this build");
74 #else
75 		// use FM-TOWNS font rom, since game files don't have kanji font resources
76 		_cjkFont = Graphics::FontSJIS::createFont(_game.platform);
77 		if (!_cjkFont)
78 			error("SCUMM::Font: Could not open file 'FMT_FNT.ROM'");
79 		_textSurfaceMultiplier = 2;
80 		_useCJKMode = true;
81 #endif
82 	} else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine && _language == Common::JA_JPN) {
83 #ifdef USE_RGB_COLOR
84 		// use PC-Engine System Card, since game files don't have kanji font resources
85 		_cjkFont = Graphics::FontSJIS::createFont(_game.platform);
86 		if (!_cjkFont)
87 			error("SCUMM::Font: Could not open file 'pce.cdbios'");
88 
89 		_cjkFont->setDrawingMode(Graphics::FontSJIS::kShadowRightMode);
90 		_2byteWidth = _2byteHeight = 12;
91 		_useCJKMode = true;
92 #endif
93 	} else if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) {
94 		int numChar = 1413;
95 		_2byteWidth = 16;
96 		_2byteHeight = 16;
97 		_useCJKMode = true;
98 		_newLineCharacter = 0x5F;
99 		// charset resources are not inited yet, load charset later
100 		_2byteFontPtr = new byte[_2byteWidth * _2byteHeight * numChar / 8];
101 		// set byte 0 to 0xFF (0x00 when loaded) to indicate that the font was not loaded
102 		_2byteFontPtr[0] = 0xFF;
103 	} else if (_language == Common::KO_KOR ||
104 			   (_game.version >= 7 && (_language == Common::JA_JPN || _language == Common::ZH_TWN)) ||
105 			   (_game.version >= 3 && _language == Common::ZH_CNA)) {
106 		int numChar = 0;
107 		const char *fontFile = NULL;
108 
109 		switch (_language) {
110 		case Common::KO_KOR:
111 			fontFile = "korean.fnt";
112 			numChar = 2350;
113 			break;
114 		case Common::JA_JPN:
115 			fontFile = (_game.id == GID_DIG) ? "kanji16.fnt" : "japanese.fnt";
116 			numChar = 8192;
117 			break;
118 		case Common::ZH_TWN:
119 			// Both The DIG and COMI use same font
120 			fontFile = "chinese.fnt";
121 			numChar = 13630;
122 			break;
123 		case Common::ZH_CNA:
124 			if (_game.id == GID_FT || _game.id == GID_LOOM || _game.id == GID_INDY3 ||
125 				_game.id == GID_INDY4 || _game.id == GID_MONKEY || _game.id == GID_MONKEY2 ||
126 				_game.id == GID_TENTACLE) {
127 				fontFile = "chinese_gb16x12.fnt";
128 				numChar = 8178;
129 			}
130 			break;
131 		default:
132 			break;
133 		}
134 		if (fontFile && openFile(fp, fontFile)) {
135 			debug(2, "Loading CJK Font");
136 			_useCJKMode = true;
137 			_textSurfaceMultiplier = 1; // No multiplication here
138 
139 			switch (_language) {
140 			case Common::KO_KOR:
141 				fp.seek(2, SEEK_CUR);
142 				_2byteWidth = fp.readByte();
143 				_2byteHeight = fp.readByte();
144 				_newLineCharacter = (_game.id == GID_CMI) ? 0xff : 0xfe;
145 				break;
146 			case Common::JA_JPN:
147 				_2byteWidth = 16;
148 				_2byteHeight = 16;
149 				_newLineCharacter = 0xfe;
150 				break;
151 			case Common::ZH_TWN:
152 				_2byteWidth = 16;
153 				_2byteHeight = 15;
154 				_newLineCharacter = 0x21;
155 				break;
156 			case Common::ZH_CNA:
157 				_2byteWidth = 12;
158 				_2byteHeight = 12;
159 				_newLineCharacter = 0x21;
160 				break;
161 			default:
162 				break;
163 			}
164 
165 			_2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar];
166 			fp.read(_2byteFontPtr, ((_2byteWidth + 7) / 8) * _2byteHeight * numChar);
167 			fp.close();
168 		} else {
169 			if (fontFile)
170 				error("SCUMM::Font: Could not open %s",fontFile);
171 			else
172 				error("SCUMM::Font: Could not load any font");
173 		}
174 	}
175 }
176 
loadKorFont()177 void ScummEngine::loadKorFont() {
178 	Common::File fp;
179 	int numChar = 2350;
180 	_useCJKMode = true;
181 
182 	if (_game.version < 7 || _game.id == GID_FT)
183 		_useMultiFont = 1;
184 
185 	if (_useMultiFont) {
186 		debug("Loading Korean Multi Font System");
187 		_numLoadedFont = 0;
188 		_2byteFontPtr = NULL;
189 		_2byteWidth = 0;
190 		_2byteHeight = 0;
191 		for (int i = 0; i < 20; i++) {
192 			char fontFile[256];
193 			snprintf(fontFile, sizeof(fontFile), "korean%02d.fnt", i);
194 			_2byteMultiFontPtr[i] = NULL;
195 			if (fp.open(fontFile)) {
196 				_numLoadedFont++;
197 				fp.readByte();
198 				_2byteMultiShadow[i] = fp.readByte();
199 				_2byteMultiWidth[i] = fp.readByte();
200 				_2byteMultiHeight[i] = fp.readByte();
201 
202 				int fontSize = ((_2byteMultiWidth[i] + 7) / 8) * _2byteMultiHeight[i] * numChar;
203 				_2byteMultiFontPtr[i] = new byte[fontSize];
204 				warning("#%d, size %d, height =%d", i, fontSize, _2byteMultiHeight[i]);
205 				fp.read(_2byteMultiFontPtr[i], fontSize);
206 				fp.close();
207 				if (_2byteFontPtr == NULL) {	// for non-initialized Smushplayer drawChar
208 					_2byteFontPtr = _2byteMultiFontPtr[i];
209 					_2byteWidth = _2byteMultiWidth[i];
210 					_2byteHeight = _2byteMultiHeight[i];
211 					_2byteShadow = _2byteMultiShadow[i];
212 				}
213 			}
214 		}
215 		if (_numLoadedFont == 0) {
216 			warning("Cannot load any font for multi font");
217 			_useMultiFont = 0;
218 		} else {
219 			debug("%d fonts are loaded", _numLoadedFont);
220 		}
221 	}
222 
223 	if (!_useMultiFont) {
224 		debug("Loading Korean Single Font System");
225 		if (fp.open("korean.fnt")) {
226 			fp.seek(2, SEEK_CUR);
227 			_2byteWidth = fp.readByte();
228 			_2byteHeight = fp.readByte();
229 			_2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar];
230 			fp.read(_2byteFontPtr, ((_2byteWidth + 7) / 8) * _2byteHeight * numChar);
231 			fp.close();
232 		} else {
233 			error("Couldn't load any font: %s", fp.getName());
234 		}
235 	}
236 	return;
237 }
238 
get2byteCharPtr(int idx)239 byte *ScummEngine::get2byteCharPtr(int idx) {
240 	if (_game.platform == Common::kPlatformFMTowns || _game.platform == Common::kPlatformPCEngine)
241 		return 0;
242 
243 	switch (_language) {
244 	case Common::KO_KOR:
245 		idx = ((idx % 256) - 0xb0) * 94 + (idx / 256) - 0xa1;
246 		break;
247 	case Common::JA_JPN:
248 		if (_game.id == GID_MONKEY && _game.platform == Common::kPlatformSegaCD && _language == Common::JA_JPN) {
249 			// init pointer to charset resource
250 			if (_2byteFontPtr[0] == 0xFF) {
251 				int charsetId = 5;
252 				int numChar = 1413;
253 				byte *charsetPtr = getResourceAddress(rtCharset, charsetId);
254 				if (charsetPtr == 0)
255 					error("ScummEngine::get2byteCharPtr: charset %d not found", charsetId);
256 				memcpy(_2byteFontPtr, charsetPtr + 46, _2byteWidth * _2byteHeight * numChar / 8);
257 			}
258 
259 			idx = (SWAP_CONSTANT_16(idx) & 0x7fff) - 1;
260 		} else {
261 			idx = Graphics::FontTowns::getCharFMTChunk(idx);
262 		}
263 
264 		break;
265 	case Common::ZH_TWN:
266 		{
267 			int base = 0;
268 			byte low = idx % 256;
269 			int high = 0;
270 
271 			if (low >= 0x20 && low <= 0x7e) {
272 				base = (3 * low + 81012) * 5;
273 			} else {
274 				if (low >= 0xa1 && low <= 0xa3) {
275 					base = 392820;
276 					low += 0x5f;
277 				} else if (low >= 0xa4 && low <= 0xc6) {
278 					base = 0;
279 					low += 0x5c;
280 				} else if (low >= 0xc9 && low <= 0xf9) {
281 					base = 162030;
282 					low += 0x37;
283 				} else {
284 					base = 392820;
285 					low = 0xff;
286 				}
287 
288 				if (low != 0xff) {
289 					high = idx / 256;
290 					if (high >= 0x40 && high <= 0x7e) {
291 						high -= 0x40;
292 					} else {
293 						high -= 0x62;
294 					}
295 
296 					base += (low * 0x9d + high) * 30;
297 				}
298 			}
299 
300 			return _2byteFontPtr + base;
301 		}
302 	case Common::ZH_CNA:
303 		idx = ((idx % 256) - 0xa1)* 94  + ((idx / 256) - 0xa1);
304 		break;
305 	default:
306 		idx = 0;
307 	}
308 	return	_2byteFontPtr + ((_2byteWidth + 7) / 8) * _2byteHeight * idx;
309 }
310 
311 
312 #pragma mark -
313 
314 
CharsetRenderer(ScummEngine * vm)315 CharsetRenderer::CharsetRenderer(ScummEngine *vm) {
316 	_top = 0;
317 	_left = 0;
318 	_startLeft = 0;
319 	_right = 0;
320 
321 	_color = 0;
322 
323 	_center = false;
324 	_hasMask = false;
325 	_textScreenID = kMainVirtScreen;
326 	_blitAlso = false;
327 	_firstChar = false;
328 	_disableOffsX = false;
329 
330 	_vm = vm;
331 	_curId = -1;
332 }
333 
~CharsetRenderer()334 CharsetRenderer::~CharsetRenderer() {
335 }
336 
CharsetRendererCommon(ScummEngine * vm)337 CharsetRendererCommon::CharsetRendererCommon(ScummEngine *vm)
338 	: CharsetRenderer(vm), _bytesPerPixel(0), _fontHeight(0), _numChars(0) {
339 	_enableShadow = false;
340 	_shadowColor = 0;
341 }
342 
setCurID(int32 id)343 void CharsetRendererCommon::setCurID(int32 id) {
344 	if (id == -1)
345 		return;
346 
347 	assertRange(0, id, _vm->_numCharsets - 1, "charset");
348 
349 	_curId = id;
350 
351 	_fontPtr = _vm->getResourceAddress(rtCharset, id);
352 	if (_fontPtr == 0)
353 		error("CharsetRendererCommon::setCurID: charset %d not found", id);
354 
355 	if (_vm->_game.version == 4)
356 		_fontPtr += 17;
357 	else
358 		_fontPtr += 29;
359 
360 	_bytesPerPixel = _fontPtr[0];
361 	_fontHeight = _fontPtr[1];
362 	_numChars = READ_LE_UINT16(_fontPtr + 2);
363 
364 	if (_vm->_useMultiFont) {
365 		if (id == 6)    // HACK: Fix monkey1cd/monkey2/dott font error
366 			id = 0;
367 
368 		if (_vm->_2byteMultiFontPtr[id]) {
369 			_vm->_2byteFontPtr = _vm->_2byteMultiFontPtr[id];
370 			_vm->_2byteWidth = _vm->_2byteMultiWidth[id];
371 			_vm->_2byteHeight = _vm->_2byteMultiHeight[id];
372 			_vm->_2byteShadow = _vm->_2byteMultiShadow[id];
373 		} else {
374 			// Get nearest font set (by height)
375 			debug(7, "Cannot find matching font set for charset #%d, use nearest font set", id);
376 			int dstHeight = _fontHeight;
377 			int nearest = 0;
378 			for (int i = 0; i < _vm->_numLoadedFont; i++) {
379 				if (ABS(_vm->_2byteMultiHeight[i] - dstHeight) <= ABS(_vm->_2byteMultiHeight[nearest] - dstHeight)) {
380 					nearest = i;
381 				}
382 			}
383 			debug(7, "Found #%d", nearest);
384 			_vm->_2byteFontPtr = _vm->_2byteMultiFontPtr[nearest];
385 			_vm->_2byteWidth = _vm->_2byteMultiWidth[nearest];
386 			_vm->_2byteHeight = _vm->_2byteMultiHeight[nearest];
387 			_vm->_2byteShadow = _vm->_2byteMultiShadow[nearest];
388 		}
389 	}
390 }
391 
setCurID(int32 id)392 void CharsetRendererV3::setCurID(int32 id) {
393 	if (id == -1)
394 		return;
395 
396 	assertRange(0, id, _vm->_numCharsets - 1, "charset");
397 
398 	_curId = id;
399 
400 	_fontPtr = _vm->getResourceAddress(rtCharset, id);
401 	if (_fontPtr == 0)
402 		error("CharsetRendererCommon::setCurID: charset %d not found", id);
403 
404 	_bytesPerPixel = 1;
405 	_numChars = _fontPtr[4];
406 	_fontHeight = _fontPtr[5];
407 
408 	_fontPtr += 6;
409 	_widthTable = _fontPtr;
410 	_fontPtr += _numChars;
411 
412 	if (_vm->_useMultiFont) {
413 		if (_vm->_2byteMultiFontPtr[id]) {
414 			_vm->_2byteFontPtr = _vm->_2byteMultiFontPtr[id];
415 			_vm->_2byteWidth = _vm->_2byteMultiWidth[id];
416 			_vm->_2byteHeight = _vm->_2byteMultiHeight[id];
417 			_vm->_2byteShadow = _vm->_2byteMultiShadow[id];
418 		} else {
419 			// Get nearest font set (by height)
420 			debug(7, "Cannot find matching font set for charset #%d, use nearest font set", id);
421 			int dstHeight = _fontHeight;
422 			int nearest = 0;
423 			for (int i = 0; i < _vm->_numLoadedFont; i++) {
424 				if (ABS(_vm->_2byteMultiHeight[i] - dstHeight) <= ABS(_vm->_2byteMultiHeight[nearest] - dstHeight)) {
425 					nearest = i;
426 				}
427 			}
428 			debug(7, "Found #%d", nearest);
429 			_vm->_2byteFontPtr = _vm->_2byteMultiFontPtr[nearest];
430 			_vm->_2byteWidth = _vm->_2byteMultiWidth[nearest];
431 			_vm->_2byteHeight = _vm->_2byteMultiHeight[nearest];
432 			_vm->_2byteShadow = _vm->_2byteMultiShadow[nearest];
433 		}
434 	}
435 }
436 
getFontHeight()437 int CharsetRendererCommon::getFontHeight() {
438 	if (_vm->_useCJKMode)
439 		return MAX(_vm->_2byteHeight + 1, _fontHeight);
440 	else
441 		return _fontHeight;
442 }
443 
444 // do spacing for variable width old-style font
getCharWidth(uint16 chr)445 int CharsetRendererClassic::getCharWidth(uint16 chr) {
446 	int spacing = 0;
447 
448 	if (_vm->_useCJKMode && chr >= 0x80)
449 		return _vm->_2byteWidth / 2;
450 
451 	int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4);
452 	if (offs)
453 		spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2];
454 
455 	return spacing;
456 }
457 
getStringWidth(int arg,const byte * text,uint strLenMax)458 int CharsetRenderer::getStringWidth(int arg, const byte *text, uint strLenMax) {
459 	int pos = 0;
460 	int width = 1;
461 	int chr;
462 	int oldID = getCurID();
463 	int code = (_vm->_game.heversion >= 80) ? 127 : 64;
464 
465 	while ((chr = text[pos++]) != 0) {
466 		if (_vm->_game.version == 7 && chr == _vm->_newLineCharacter)
467 			continue;
468 		else if (chr == '\n' || chr == '\r' || chr == _vm->_newLineCharacter)
469 			break;
470 
471 		if (_vm->_game.heversion >= 72) {
472 			if (chr == code) {
473 				chr = text[pos++];
474 				if (chr == 84 || chr == 116) {  // Strings of speech offset/size
475 					while (chr != code)
476 						chr = text[pos++];
477 					continue;
478 				}
479 				if (chr == 119) // 'Wait'
480 					break;
481 				if (chr == 104|| chr == 110) // 'Newline'
482 					break;
483 			}
484 		} else {
485 			if (chr == '@')
486 				continue;
487 			if (chr == 255 || (_vm->_game.version <= 6 && chr == 254)) {
488 				chr = text[pos++];
489 				if (chr == 3)	// 'WAIT'
490 					break;
491 				if (chr == 8) { // 'Verb on next line'
492 					if (arg == 1)
493 						break;
494 					while (text[pos++] == ' ') {}
495 					continue;
496 				}
497 				if (chr == 10 || chr == 21 || chr == 12 || chr == 13) {
498 					pos += 2;
499 					continue;
500 				}
501 				if (chr == 9 || chr == 1 || chr == 2) // 'Newline'
502 					break;
503 				if (chr == 14) {
504 					int set = text[pos] | (text[pos + 1] << 8);
505 					pos += 2;
506 					setCurID(set);
507 					continue;
508 				}
509 			}
510 		}
511 
512 		if (_vm->_useCJKMode) {
513 			if (_vm->_game.platform == Common::kPlatformFMTowns) {
514 				if (checkSJISCode(chr))
515 					// This strange character conversion is the exact way the original does it here.
516 					// This is the only way to get an accurate text formatting in the MI1 intro.
517 					chr = (int8)text[pos++] | (chr << 8);
518 			} else if (chr & 0x80) {
519 				pos++;
520 				width += _vm->_2byteWidth;
521 				// Original keeps glyph width and character dimensions separately
522 				if (_vm->_language == Common::KO_KOR || _vm->_language == Common::ZH_TWN) {
523 					width++;
524 				}
525 				continue;
526 			}
527 		}
528 		width += getCharWidth(chr);
529 	}
530 
531 	setCurID(oldID);
532 
533 	return width;
534 }
535 
addLinebreaks(int a,byte * str,int pos,int maxwidth)536 void CharsetRenderer::addLinebreaks(int a, byte *str, int pos, int maxwidth) {
537 	int lastKoreanLineBreak = -1;
538 	int origPos = pos;
539 	int lastspace = -1;
540 	int curw = 1;
541 	int chr;
542 	int oldID = getCurID();
543 	int code = (_vm->_game.heversion >= 80) ? 127 : 64;
544 
545 	int strLength = _vm->resStrLen(str);
546 
547 	while ((chr = str[pos++]) != 0) {
548 		if (_vm->_game.heversion >= 72) {
549 			if (chr == code) {
550 				chr = str[pos++];
551 				if (chr == 84 || chr == 116) {  // Strings of speech offset/size
552 					while (chr != code)
553 						chr = str[pos++];
554 					continue;
555 				}
556 				if (chr == 119) // 'Wait'
557 					break;
558 				if (chr == 110) { // 'Newline'
559 					curw = 1;
560 					continue;
561 				}
562 				if (chr == 104) // 'Don't terminate with \n'
563 					break;
564 			}
565 		} else {
566 			if (chr == '@')
567 				continue;
568 			if (chr == 255 || (_vm->_game.version <= 6 && chr == 254)) {
569 				chr = str[pos++];
570 				if (chr == 3) // 'Wait'
571 					break;
572 				if (chr == 8) { // 'Verb on next line'
573 					if (a == 1) {
574 						curw = 1;
575 					} else {
576 						while (str[pos] == ' ')
577 							str[pos++] = '@';
578 					}
579 					continue;
580 				}
581 				if (chr == 10 || chr == 21 || chr == 12 || chr == 13) {
582 					pos += 2;
583 					continue;
584 				}
585 				if (chr == 1) { // 'Newline'
586 					curw = 1;
587 					continue;
588 				}
589 				if (chr == 2) // 'Don't terminate with \n'
590 					break;
591 				if (chr == 14) {
592 					int set = str[pos] | (str[pos + 1] << 8);
593 					pos += 2;
594 					setCurID(set);
595 					continue;
596 				}
597 			}
598 		}
599 		if (chr == ' ')
600 			lastspace = pos - 1;
601 
602 		if (chr == _vm->_newLineCharacter)
603 			lastspace = pos - 1;
604 
605 		if (_vm->_useCJKMode) {
606 			if (_vm->_game.platform == Common::kPlatformFMTowns) {
607 				if (checkSJISCode(chr))
608 					// This strange character conversion is the exact way the original does it here.
609 					// This is the only way to get an accurate text formatting in the MI1 intro.
610 					chr = (int8)str[pos++] | (chr << 8);
611 				curw += getCharWidth(chr);
612 			} else if (chr & 0x80) {
613 				pos++;
614 				curw += _vm->_2byteWidth;
615 				// Original keeps glyph width and character dimensions separately
616 				if (_vm->_language == Common::KO_KOR || _vm->_language == Common::ZH_TWN) {
617 					curw++;
618 				}
619 			} else if (chr != _vm->_newLineCharacter) {
620 				curw += getCharWidth(chr);
621 			}
622 
623 			if (_vm->isScummvmKorTarget() && !_center) {
624 				// Break Korean words at any character
625 				// Used in Korean fan translated games
626 				if (chr & 0x80) {
627 					if (checkKSCode(chr, str[pos - 1])
628 					    && !(pos - 4 >= origPos && str[pos - 3] == '`' && str[pos - 4] == ' ')  // prevents hanging quotation mark at the end of line
629 					    && !(pos - 4 >= origPos && str[pos - 3] == '\'' && str[pos - 4] == ' ') // prevents hanging single quotation mark at the end of line
630 					    && !(pos - 3 >= origPos && str[pos - 3] == '('))  // prevents hanging parenthesis at the end of line
631 						lastKoreanLineBreak = pos - 2;
632 				} else {
633 					if (chr == '(' && pos - 3 >= origPos && checkKSCode(str[pos - 3], str[pos - 2]))
634 						lastKoreanLineBreak = pos - 1;
635 				}
636 			}
637 		} else {
638 			curw += getCharWidth(chr);
639 		}
640 		if (lastspace == -1) {
641 			if (!_vm->isScummvmKorTarget() || lastKoreanLineBreak == -1) {
642 				continue;
643 			}
644 		}
645 		if (curw > maxwidth) {
646 			if (!_vm->isScummvmKorTarget()) {
647 				str[lastspace] = 0xD;
648 				curw = 1;
649 				pos = lastspace + 1;
650 				lastspace = -1;
651 			} else {
652 				// Handle Korean line break mode (break Korean words at any character)
653 				// Used in Korean fan translated games
654 				if (lastspace >= lastKoreanLineBreak) {
655 					str[lastspace] = 0xD;
656 					curw = 1;
657 					pos = lastspace + 1;
658 					lastspace = -1;
659 					lastKoreanLineBreak = -1;
660 				} else {
661 					byte *breakPtr = str + lastKoreanLineBreak;
662 					memmove(breakPtr + 1, breakPtr, strLength - lastKoreanLineBreak + 1);
663 					str[lastKoreanLineBreak] = 0xD;
664 					curw = 1;
665 					pos = lastKoreanLineBreak + 1;
666 					lastspace = -1;
667 					lastKoreanLineBreak = -1;
668 				}
669 			}
670 		}
671 	}
672 
673 	setCurID(oldID);
674 }
675 
getCharWidth(uint16 chr)676 int CharsetRendererV3::getCharWidth(uint16 chr) {
677 	int spacing = 0;
678 
679 	if (_vm->_useCJKMode && (chr & 0x80))
680 		spacing = _vm->_2byteWidth / 2;
681 
682 	if (!spacing)
683 		spacing = *(_widthTable + chr);
684 
685 	return spacing;
686 }
687 
enableShadow(bool enable)688 void CharsetRendererPC::enableShadow(bool enable) {
689 	_shadowColor = 0;
690 	_enableShadow = enable;
691 
692 	if (_vm->_game.version >= 7 && _vm->_useCJKMode)
693 		_shadowType = kHorizontalShadowType;
694 	else
695 		_shadowType = kNormalShadowType;
696 }
697 
drawBits1(Graphics::Surface & dest,int x,int y,const byte * src,int drawTop,int width,int height)698 void CharsetRendererPC::drawBits1(Graphics::Surface &dest, int x, int y, const byte *src, int drawTop, int width, int height) {
699 	if (_vm->_useCJKMode && _vm->isScummvmKorTarget()) {
700 		drawBits1Kor(dest, x, y, src, drawTop, width, height);
701 		return;
702 	}
703 
704 	byte *dst = (byte *)dest.getBasePtr(x, y);
705 	byte bits = 0;
706 	uint8 col = _color;
707 	int pitch = dest.pitch - width * dest.format.bytesPerPixel;
708 	byte *dst2 = dst + dest.pitch;
709 
710 	for (y = 0; y < height && y + drawTop < dest.h; y++) {
711 		for (x = 0; x < width; x++) {
712 			if ((x % 8) == 0)
713 				bits = *src++;
714 			if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) {
715 				if (_enableShadow) {
716 					if (_shadowType == kNormalShadowType)
717 						dst[1] = dst2[0] = dst2[1] = _shadowColor;
718 					else if (_shadowType == kHorizontalShadowType)
719 						dst[1] = _shadowColor;
720 				}
721 				dst[0] = col;
722 			}
723 			dst += dest.format.bytesPerPixel;
724 			dst2 += dest.format.bytesPerPixel;
725 		}
726 
727 		dst += pitch;
728 		dst2 += pitch;
729 	}
730 }
731 
drawBits1Kor(Graphics::Surface & dest,int x1,int y1,const byte * src,int drawTop,int width,int height)732 void CharsetRendererPC::drawBits1Kor(Graphics::Surface &dest, int x1, int y1, const byte *src, int drawTop, int width, int height) {
733 	byte *dst = (byte *)dest.getBasePtr(x1, y1);
734 
735 	int y, x;
736 	byte bits = 0;
737 
738 	// HACK: Since Korean fonts don't have shadow/stroke information,
739 	//	   we use NUT-Renderer-like shadow drawing method.
740 
741 	int offsetX[14] = {-2, -2, -2, -1, 0, -1, 0, 1, -1, 1, -1, 0, 1, 0};
742 	int offsetY[14] = {0, 1, 2, 2, 2, -1, -1, -1, 0, 0, 1, 1, 1, 0};
743 	int cTable[14] = {_shadowColor, _shadowColor, _shadowColor,
744 						_shadowColor, _shadowColor, _shadowColor, _shadowColor,
745 						_shadowColor, _shadowColor, _shadowColor, _shadowColor,
746 						_shadowColor, _shadowColor, _color};
747 	int i = 0;
748 
749 	switch (_vm->_2byteShadow) {
750 	case 1: // No shadow
751 		i = 13;
752 		break;
753 	case 2: // SE direction shadow
754 		i = 12;
755 		break;
756 	case 3: // Stroke & SW direction shadow ("Monkey2", "Indy4")
757 		i = 0;
758 		break;
759 	default: // Stroke
760 		i = 5;
761 	}
762 
763 	const byte *origSrc = src;
764 	byte *origDst = dst;
765 
766 	for (; i < 14; i++) {
767 		src = origSrc;
768 		dst = origDst;
769 
770 		for (y = 0; y < height && y + drawTop + offsetY[i] < dest.h; y++) {
771 			for (x = 0; x < width && x + x1 + offsetX[i] < dest.w; x++) {
772 				if ((x % 8) == 0)
773 					bits = *src++;
774 				if ((bits & revBitMask(x % 8)) && y + drawTop + offsetY[i] >= 0 && x + x1 + offsetX[i] >= 0) {
775 					*(dst + (dest.pitch * offsetY[i]) + offsetX[i]) = cTable[i];
776 				}
777 				dst++;
778 			}
779 
780 			dst += dest.pitch - width;
781 		}
782 	}
783 }
784 
getDrawWidthIntern(uint16 chr)785 int CharsetRendererV3::getDrawWidthIntern(uint16 chr) {
786 	return getCharWidth(chr);
787 }
788 
getDrawHeightIntern(uint16)789 int CharsetRendererV3::getDrawHeightIntern(uint16) {
790 	return 8;
791 }
792 
setColor(byte color)793 void CharsetRendererV3::setColor(byte color) {
794 	bool useShadow = false;
795 	_color = color;
796 
797 	// FM-TOWNS version of Loom uses old color method as well
798 	if ((_vm->_game.version >= 2) && ((_vm->_game.features & GF_16COLOR) || (_vm->_game.id == GID_LOOM && _vm->_game.version == 3))) {
799 		useShadow = ((_color & 0xF0) != 0);
800 		_color &= 0x0f;
801 	} else if (_vm->_game.features & GF_OLD256) {
802 		useShadow = ((_color & 0x80) != 0);
803 		_color &= 0x7f;
804 	} else
805 		useShadow = false;
806 
807 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
808 	if (_vm->_game.platform == Common::kPlatformFMTowns) {
809 		_color = (_color & 0x0f) | ((_color & 0x0f) << 4);
810 		if (_color == 0)
811 			_color = 0x88;
812 	}
813 #endif
814 
815 	enableShadow(useShadow);
816 
817 	translateColor();
818 }
819 
820 #ifdef USE_RGB_COLOR
setColor(byte color)821 void CharsetRendererPCE::setColor(byte color) {
822 	_vm->setPCETextPalette(color);
823 	_color = 15;
824 
825 	enableShadow(true);
826 }
827 #endif
828 
printChar(int chr,bool ignoreCharsetMask)829 void CharsetRendererV3::printChar(int chr, bool ignoreCharsetMask) {
830 	// WORKAROUND for bug #2703: Indy3 Mac does not show black
831 	// characters (such as in the grail diary) if ignoreCharsetMask
832 	// is true. See also bug #8759.
833 	if (_vm->_game.id == GID_INDY3 && _vm->_game.platform == Common::kPlatformMacintosh && _color == 0)
834 		ignoreCharsetMask = false;
835 
836 	// Indy3 / Zak256 / Loom
837 	int width, height, origWidth = 0, origHeight;
838 	VirtScreen *vs;
839 	const byte *charPtr;
840 	int is2byte = (chr >= 256 && _vm->_useCJKMode) ? 1 : 0;
841 
842 	assertRange(0, _curId, _vm->_numCharsets - 1, "charset");
843 
844 	if ((vs = _vm->findVirtScreen(_top)) == NULL) {
845 		warning("findVirtScreen(%d) failed, therefore printChar cannot print '%c'", _top, chr);
846 		return;
847 	}
848 
849 	if (chr == '@')
850 		return;
851 
852 	if (_vm->isScummvmKorTarget()) {
853 		if (is2byte) {
854 			charPtr = _vm->get2byteCharPtr(chr);
855 			width = _vm->_2byteWidth;
856 			height = _vm->_2byteHeight;
857 		} else {
858 			charPtr = _fontPtr + chr * 8;
859 			width = getDrawWidthIntern(chr);
860 			height = getDrawHeightIntern(chr);
861 		}
862 	} else {
863 		charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8;
864 		width = getDrawWidthIntern(chr);
865 		height = getDrawHeightIntern(chr);
866 	}
867 	setDrawCharIntern(chr);
868 
869 	origWidth = width;
870 	origHeight = height;
871 
872 	// Clip at the right side (to avoid drawing "outside" the screen bounds).
873 	if (_left + origWidth > _right + 1)
874 		return;
875 
876 	if (_enableShadow) {
877 		width++;
878 		height++;
879 	}
880 
881 	if (_firstChar) {
882 		_str.left = _left;
883 		_str.top = _top;
884 		_str.right = _left;
885 		_str.bottom = _top;
886 		_firstChar = false;
887 	}
888 
889 	int drawTop = _top - vs->topline;
890 
891 	_vm->markRectAsDirty(vs->number, _left, _left + width, drawTop, drawTop + height);
892 
893 	if (!ignoreCharsetMask) {
894 		_hasMask = true;
895 		_textScreenID = vs->number;
896 	}
897 
898 	if ((ignoreCharsetMask || !vs->hasTwoBuffers)
899 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
900 		&& (_vm->_game.platform != Common::kPlatformFMTowns)
901 #endif
902 		)
903 		drawBits1(*vs, _left + vs->xstart, drawTop, charPtr, drawTop, origWidth, origHeight);
904 	else
905 		drawBits1(_vm->_textSurface, _left * _vm->_textSurfaceMultiplier, _top * _vm->_textSurfaceMultiplier, charPtr, drawTop, origWidth, origHeight);
906 
907 	if (is2byte) {
908 		origWidth /= _vm->_textSurfaceMultiplier;
909 		height /= _vm->_textSurfaceMultiplier;
910 	}
911 
912 	if (_str.left > _left)
913 		_str.left = _left;
914 
915 	_left += origWidth;
916 
917 	if (_str.right < _left) {
918 		_str.right = _left;
919 		if (_enableShadow)
920 			_str.right++;
921 	}
922 
923 	if (_str.bottom < _top + height)
924 		_str.bottom = _top + height;
925 }
926 
drawChar(int chr,Graphics::Surface & s,int x,int y)927 void CharsetRendererV3::drawChar(int chr, Graphics::Surface &s, int x, int y) {
928 	const byte *charPtr;
929 	int width;
930 	int height;
931 	int is2byte = (chr > 0xff && _vm->_useCJKMode) ? 1 : 0;
932 
933 	if (_vm->isScummvmKorTarget()) {
934 		if (is2byte) {
935 			charPtr = _vm->get2byteCharPtr(chr);
936 			width = _vm->_2byteWidth;
937 			height = _vm->_2byteHeight;
938 		} else {
939 			charPtr = _fontPtr + chr * 8;
940 			width = getDrawWidthIntern(chr);
941 			height = getDrawHeightIntern(chr);
942 		}
943 	} else {
944 		charPtr = (_vm->_useCJKMode && chr > 127) ? _vm->get2byteCharPtr(chr) : _fontPtr + chr * 8;
945 		width = getDrawWidthIntern(chr);
946 		height = getDrawHeightIntern(chr);
947 	}
948 	setDrawCharIntern(chr);
949 	drawBits1(s, x, y, charPtr, y, width, height);
950 }
951 
translateColor()952 void CharsetRenderer::translateColor() {
953 	// Based on disassembly
954 	if (_vm->_renderMode == Common::kRenderCGA) {
955 		static const byte CGAtextColorMap[16] = {0,  3, 3, 3, 5, 5, 5,  15,
956 										   15, 3, 3, 3, 5, 5, 15, 15};
957 		_color = CGAtextColorMap[_color & 0x0f];
958 	}
959 
960 	if (_vm->_renderMode == Common::kRenderHercA || _vm->_renderMode == Common::kRenderHercG) {
961 		static const byte HercTextColorMap[16] = {0, 15,  2, 15, 15,  5, 15,  15,
962 										   8, 15, 15, 15, 15, 15, 15, 15};
963 		_color = HercTextColorMap[_color & 0x0f];
964 	}
965 }
966 
saveLoadWithSerializer(Common::Serializer & ser)967 void CharsetRenderer::saveLoadWithSerializer(Common::Serializer &ser) {
968 	ser.syncAsByte(_curId, VER(73), VER(73));
969 	ser.syncAsSint32LE(_curId, VER(74));
970 	ser.syncAsByte(_color, VER(73));
971 
972 	if (ser.isLoading()) {
973 		setCurID(_curId);
974 		setColor(_color);
975 	}
976 }
977 
printChar(int chr,bool ignoreCharsetMask)978 void CharsetRendererClassic::printChar(int chr, bool ignoreCharsetMask) {
979 	VirtScreen *vs;
980 	bool is2byte = (chr >= 256 && _vm->_useCJKMode);
981 
982 	assertRange(1, _curId, _vm->_numCharsets - 1, "charset");
983 
984 	if ((vs = _vm->findVirtScreen(_top)) == NULL && (vs = _vm->findVirtScreen(_top + getFontHeight())) == NULL)
985 		return;
986 
987 	if (chr == '@')
988 		return;
989 
990 	translateColor();
991 
992 	_vm->_charsetColorMap[1] = _color;
993 	if (_vm->isScummvmKorTarget() && is2byte) {
994 		enableShadow(true);
995 		_charPtr = _vm->get2byteCharPtr(chr);
996 		_width = _vm->_2byteWidth;
997 		_height = _vm->_2byteHeight;
998 		_offsX = _offsY = 0;
999 	} else {
1000 		if (!prepareDraw(chr))
1001 			return;
1002 	}
1003 
1004 	if (_vm->isScummvmKorTarget()) {
1005 		_origWidth = _width;
1006 		_origHeight = _height;
1007 	}
1008 
1009 	if (_firstChar) {
1010 		_str.left = 0;
1011 		_str.top = 0;
1012 		_str.right = 0;
1013 		_str.bottom = 0;
1014 	}
1015 
1016 	_top += _offsY;
1017 	_left += _offsX;
1018 
1019 	if (_left + _origWidth > _right + 1 || _left < 0) {
1020 		_left += _origWidth;
1021 		_top -= _offsY;
1022 		return;
1023 	}
1024 
1025 	_disableOffsX = false;
1026 
1027 	if (_firstChar) {
1028 		_str.left = _left;
1029 		_str.top = _top;
1030 		_str.right = _left;
1031 		_str.bottom = _top;
1032 		_firstChar = false;
1033 	}
1034 
1035 	if (_left < _str.left)
1036 		_str.left = _left;
1037 
1038 	if (_top < _str.top)
1039 		_str.top = _top;
1040 
1041 	int drawTop = _top - vs->topline;
1042 
1043 	_vm->markRectAsDirty(vs->number, _left, _left + _width, drawTop, drawTop + _height);
1044 
1045 	// This check for kPlatformFMTowns and kMainVirtScreen is at least required for the chat with
1046 	// the navigator's head in front of the ghost ship in Monkey Island 1
1047 	if (!ignoreCharsetMask || (_vm->_game.platform == Common::kPlatformFMTowns && vs->number == kMainVirtScreen)) {
1048 		_hasMask = true;
1049 		_textScreenID = vs->number;
1050 	}
1051 
1052 	// We need to know the virtual screen we draw on for Indy 4 Amiga, since
1053 	// it selects the palette map according to this. We furthermore can not
1054 	// use _textScreenID here, since that will cause inventory graphics
1055 	// glitches.
1056 	if (_vm->_game.platform == Common::kPlatformAmiga && _vm->_game.id == GID_INDY4)
1057 		_drawScreen = vs->number;
1058 
1059 	printCharIntern(is2byte, _charPtr, _origWidth, _origHeight, _width, _height, vs, ignoreCharsetMask);
1060 
1061 	// Original keeps glyph width and character dimensions separately
1062 	if ((_vm->_language == Common::ZH_TWN || _vm->_language == Common::KO_KOR) && is2byte)
1063 		_origWidth++;
1064 
1065 	_left += _origWidth;
1066 
1067 	if (_str.right < _left) {
1068 		_str.right = _left;
1069 		if (_vm->_game.platform != Common::kPlatformFMTowns && _enableShadow)
1070 			_str.right++;
1071 	}
1072 
1073 	if (_str.bottom < _top + _origHeight)
1074 		_str.bottom = _top + _origHeight;
1075 
1076 	_top -= _offsY;
1077 }
1078 
printCharIntern(bool is2byte,const byte * charPtr,int origWidth,int origHeight,int width,int height,VirtScreen * vs,bool ignoreCharsetMask)1079 void CharsetRendererClassic::printCharIntern(bool is2byte, const byte *charPtr, int origWidth, int origHeight, int width, int height, VirtScreen *vs, bool ignoreCharsetMask) {
1080 	byte *dstPtr;
1081 	byte *back = NULL;
1082 	int drawTop = _top - vs->topline;
1083 
1084 	if ((_vm->_game.heversion >= 71 && _bytesPerPixel >= 8) || (_vm->_game.heversion >= 90 && _bytesPerPixel == 0)) {
1085 #ifdef ENABLE_HE
1086 		if (ignoreCharsetMask || !vs->hasTwoBuffers) {
1087 			dstPtr = vs->getPixels(0, 0);
1088 		} else {
1089 			dstPtr = (byte *)_vm->_textSurface.getPixels();
1090 		}
1091 
1092 		if (_blitAlso && vs->hasTwoBuffers) {
1093 			dstPtr = vs->getBackPixels(0, 0);
1094 		}
1095 
1096 		Common::Rect rScreen(vs->w, vs->h);
1097 		if (_bytesPerPixel >= 8) {
1098 			byte imagePalette[256];
1099 			memset(imagePalette, 0, sizeof(imagePalette));
1100 			memcpy(imagePalette, _vm->_charsetColorMap, 4);
1101 			Wiz::copyWizImage(dstPtr, charPtr, vs->pitch, kDstScreen, vs->w, vs->h, _left, _top, origWidth, origHeight, &rScreen, 0, imagePalette, NULL, _vm->_bytesPerPixel);
1102 		} else {
1103 			Wiz::copyWizImage(dstPtr, charPtr, vs->pitch, kDstScreen, vs->w, vs->h, _left, _top, origWidth, origHeight, &rScreen, 0, NULL, NULL, _vm->_bytesPerPixel);
1104 		}
1105 
1106 		if (_blitAlso && vs->hasTwoBuffers) {
1107 			Common::Rect dst(_left, _top, _left + origWidth, _top + origHeight);
1108 			((ScummEngine_v71he *)_vm)->restoreBackgroundHE(dst);
1109 		}
1110 #endif
1111 	} else {
1112 		Graphics::Surface dstSurface;
1113 		Graphics::Surface backSurface;
1114 		if (ignoreCharsetMask || !vs->hasTwoBuffers) {
1115 			dstSurface = *vs;
1116 			dstPtr = vs->getPixels(_left, drawTop);
1117 		} else {
1118 			dstSurface = _vm->_textSurface;
1119 			dstPtr = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier);
1120 		}
1121 
1122 		if (_blitAlso && vs->hasTwoBuffers) {
1123 			backSurface = dstSurface;
1124 			back = dstPtr;
1125 			dstSurface = *vs;
1126 			dstPtr = vs->getBackPixels(_left, drawTop);
1127 		}
1128 
1129 		if (!ignoreCharsetMask && vs->hasTwoBuffers) {
1130 			drawTop = _top - _vm->_screenTop;
1131 		}
1132 
1133 		if (is2byte && _vm->_game.platform != Common::kPlatformFMTowns)
1134 			drawBits1(dstSurface, (ignoreCharsetMask || !vs->hasTwoBuffers) ? _left + vs->xstart : _left, drawTop, charPtr, drawTop, origWidth, origHeight);
1135 		else
1136 			drawBitsN(dstSurface, dstPtr, charPtr, *_fontPtr, drawTop, origWidth, origHeight);
1137 
1138 		if (_blitAlso && vs->hasTwoBuffers) {
1139 			// FIXME: Revisiting this code, I think the _blitAlso mode is likely broken
1140 			// right now -- we are copying stuff from "dstPtr" to "back", but "dstPtr" really
1141 			// only conatains charset data...
1142 			// One way to fix this: don't copy etc.; rather simply render the char twice,
1143 			// once to each of the two buffers. That should hypothetically yield
1144 			// identical results, though I didn't try it and right now I don't know
1145 			// any spots where I can test this...
1146 			if (!ignoreCharsetMask)
1147 				error("This might be broken -- please report where you encountered this to Fingolfin");
1148 
1149 			// Perform some clipping
1150 			int w = MIN(width, dstSurface.w - _left);
1151 			int h = MIN(height, dstSurface.h - drawTop);
1152 			if (_left < 0) {
1153 				w += _left;
1154 				back -= _left;
1155 				dstPtr -= _left;
1156 			}
1157 			if (drawTop < 0) {
1158 				h += drawTop;
1159 				back -= drawTop * backSurface.pitch;
1160 				dstPtr -= drawTop * dstSurface.pitch;
1161 			}
1162 
1163 			// Blit the image data
1164 			if (w > 0) {
1165 				while (h-- > 0) {
1166 					memcpy(back, dstPtr, w);
1167 					back += backSurface.pitch;
1168 					dstPtr += dstSurface.pitch;
1169 				}
1170 			}
1171 		}
1172 	}
1173 }
1174 
prepareDraw(uint16 chr)1175 bool CharsetRendererClassic::prepareDraw(uint16 chr) {
1176 	bool is2byte = (chr >= 256 && _vm->_useCJKMode);
1177 	if (is2byte) {
1178 		if (_vm->_game.version >= 7)
1179 			enableShadow(true);
1180 
1181 		_charPtr = _vm->get2byteCharPtr(chr);
1182 		_width = _origWidth = _vm->_2byteWidth;
1183 		_height = _origHeight = _vm->_2byteHeight;
1184 		_offsX = _offsY = 0;
1185 
1186 		if (_enableShadow) {
1187 			_width++;
1188 			_height++;
1189 		}
1190 
1191 		return true;
1192 	} else {
1193 		enableShadow(false);
1194 	}
1195 
1196 	uint32 charOffs = READ_LE_UINT32(_fontPtr + chr * 4 + 4);
1197 	assert(charOffs < 0x14000);
1198 	if (!charOffs)
1199 		return false;
1200 	_charPtr = _fontPtr + charOffs;
1201 
1202 	_width = _origWidth = _charPtr[0];
1203 	_height = _origHeight = _charPtr[1];
1204 
1205 	if (_disableOffsX) {
1206 		_offsX = 0;
1207 	} else {
1208 		_offsX = (signed char)_charPtr[2];
1209 	}
1210 
1211 	_offsY = (signed char)_charPtr[3];
1212 
1213 	_charPtr += 4;	// Skip over char header
1214 	return true;
1215 }
1216 
drawChar(int chr,Graphics::Surface & s,int x,int y)1217 void CharsetRendererClassic::drawChar(int chr, Graphics::Surface &s, int x, int y) {
1218 	if (!prepareDraw(chr))
1219 		return;
1220 
1221 	byte *dst = (byte *)s.getBasePtr(x, y);
1222 
1223 	bool is2byte = (_vm->_useCJKMode && chr >= 256);
1224 	if (is2byte)
1225 		drawBits1(s, x, y, _charPtr, y, _width, _height);
1226 	else
1227 		drawBitsN(s, dst, _charPtr, *_fontPtr, y, _width, _height);
1228 }
1229 
drawBitsN(const Graphics::Surface & s,byte * dst,const byte * src,byte bpp,int drawTop,int width,int height)1230 void CharsetRendererClassic::drawBitsN(const Graphics::Surface &s, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) {
1231 	int y, x;
1232 	int color;
1233 	byte numbits, bits;
1234 
1235 	int pitch = s.pitch - width;
1236 
1237 	assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
1238 	bits = *src++;
1239 	numbits = 8;
1240 	byte *cmap = _vm->_charsetColorMap;
1241 
1242 	// Indy4 Amiga always uses the room or verb palette map to match colors to
1243 	// the currently setup palette, thus we need to select it over here too.
1244 	// Done like the original interpreter.
1245 	byte *amigaMap = 0;
1246 	if (_vm->_game.platform == Common::kPlatformAmiga && _vm->_game.id == GID_INDY4) {
1247 		if (_drawScreen == kVerbVirtScreen)
1248 			amigaMap = _vm->_verbPalette;
1249 		else
1250 			amigaMap = _vm->_roomPalette;
1251 	}
1252 
1253 	for (y = 0; y < height && y + drawTop < s.h; y++) {
1254 		for (x = 0; x < width; x++) {
1255 			color = (bits >> (8 - bpp)) & 0xFF;
1256 
1257 			if (color && y + drawTop >= 0) {
1258 				if (amigaMap)
1259 					*dst = amigaMap[cmap[color]];
1260 				else
1261 					*dst = cmap[color];
1262 			}
1263 			dst++;
1264 			bits <<= bpp;
1265 			numbits -= bpp;
1266 			if (numbits == 0) {
1267 				bits = *src++;
1268 				numbits = 8;
1269 			}
1270 		}
1271 		dst += pitch;
1272 	}
1273 }
1274 
CharsetRendererTownsV3(ScummEngine * vm)1275 CharsetRendererTownsV3::CharsetRendererTownsV3(ScummEngine *vm) : CharsetRendererV3(vm), _sjisCurChar(0) {
1276 }
1277 
getCharWidth(uint16 chr)1278 int CharsetRendererTownsV3::getCharWidth(uint16 chr) {
1279 	if (_vm->isScummvmKorTarget()) {
1280 		return CharsetRendererV3::getCharWidth(chr);
1281 	}
1282 
1283 	int spacing = 0;
1284 
1285 	if (_vm->_useCJKMode) {
1286 		if (chr >= 256)
1287 			spacing = 8;
1288 		else if (chr >= 128)
1289 			spacing = 4;
1290 	}
1291 
1292 	if (!spacing)
1293 		spacing = *(_widthTable + chr);
1294 
1295 	return spacing;
1296 }
1297 
getFontHeight()1298 int CharsetRendererTownsV3::getFontHeight() {
1299 	if (_vm->isScummvmKorTarget()) {
1300 		return CharsetRendererV3::getFontHeight();
1301 	}
1302 
1303 	return _vm->_useCJKMode ? 8 : _fontHeight;
1304 }
1305 
enableShadow(bool enable)1306 void CharsetRendererTownsV3::enableShadow(bool enable) {
1307 	if (_vm->isScummvmKorTarget()) {
1308 		CharsetRendererV3::enableShadow(enable);
1309 		return;
1310 	}
1311 
1312 	_shadowColor = 8;
1313 	_enableShadow = enable;
1314 
1315 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1316 	_shadowColor = 0x88;
1317 #ifdef USE_RGB_COLOR
1318 	if (_vm->_cjkFont)
1319 		_vm->_cjkFont->setDrawingMode(enable ? Graphics::FontSJIS::kFMTownsShadowMode : Graphics::FontSJIS::kDefaultMode);
1320 #endif
1321 #endif
1322 }
1323 
drawBits1(Graphics::Surface & dest,int x,int y,const byte * src,int drawTop,int width,int height)1324 void CharsetRendererTownsV3::drawBits1(Graphics::Surface &dest, int x, int y, const byte *src, int drawTop, int width, int height) {
1325 	if (_vm->isScummvmKorTarget()) {
1326 		CharsetRendererV3::drawBits1(dest, x, y, src, drawTop, width, height);
1327 		return;
1328 	}
1329 
1330 	if (y + height > dest.h)
1331 		error("Trying to draw below screen boundries");
1332 
1333 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1334 #ifdef USE_RGB_COLOR
1335 	if (_sjisCurChar) {
1336 		assert(_vm->_cjkFont);
1337 		_vm->_cjkFont->drawChar(dest, _sjisCurChar, x, y, _color, _shadowColor);
1338 		return;
1339 	}
1340 #endif
1341 	bool scale2x = ((&dest == &_vm->_textSurface) && (_vm->_textSurfaceMultiplier == 2) && !(_sjisCurChar >= 256 && _vm->_useCJKMode));
1342 #endif
1343 
1344 	byte bits = 0;
1345 	uint8 col = _color;
1346 	int pitch = dest.pitch - width * dest.format.bytesPerPixel;
1347 	byte *dst = (byte *)dest.getBasePtr(x, y);
1348 	byte *dst2 = dst + dest.pitch;
1349 
1350 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1351 	byte *dst3 = dst2;
1352 	byte *dst4 = dst2;
1353 	if (scale2x) {
1354 		dst3 = dst2 + dest.pitch;
1355 		dst4 = dst3 + dest.pitch;
1356 		pitch <<= 1;
1357 	}
1358 #endif
1359 
1360 	for (y = 0; y < height && y + drawTop < dest.h; y++) {
1361 		for (x = 0; x < width; x++) {
1362 			if ((x % 8) == 0)
1363 				bits = *src++;
1364 			if ((bits & revBitMask(x % 8)) && y + drawTop >= 0) {
1365 				if (dest.format.bytesPerPixel == 2) {
1366 					if (_enableShadow) {
1367 						WRITE_UINT16(dst + 2, _vm->_16BitPalette[_shadowColor]);
1368 						WRITE_UINT16(dst + dest.pitch, _vm->_16BitPalette[_shadowColor]);
1369 					}
1370 					WRITE_UINT16(dst, _vm->_16BitPalette[_color]);
1371 				} else {
1372 					if (_enableShadow) {
1373 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1374 						if (scale2x) {
1375 							dst[2] = dst[3] = dst2[2] = dst2[3] = _shadowColor;
1376 							dst3[0] = dst4[0] = dst3[1] = dst4[1] = _shadowColor;
1377 						} else
1378 #endif
1379 						{
1380 							dst[1] = dst2[0] = _shadowColor;
1381 						}
1382 					}
1383 					dst[0] = col;
1384 
1385 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1386 					if (scale2x)
1387 						dst[1] = dst2[0] = dst2[1] = col;
1388 #endif
1389 				}
1390 			}
1391 			dst += dest.format.bytesPerPixel;
1392 			dst2 += dest.format.bytesPerPixel;
1393 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1394 			if (scale2x) {
1395 				dst++;
1396 				dst2++;
1397 				dst3 += 2;
1398 				dst4 += 2;
1399 			}
1400 #endif
1401 		}
1402 
1403 		dst += pitch;
1404 		dst2 += pitch;
1405 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
1406 		dst3 += pitch;
1407 		dst4 += pitch;
1408 #endif
1409 	}
1410 }
1411 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
getDrawWidthIntern(uint16 chr)1412 int CharsetRendererTownsV3::getDrawWidthIntern(uint16 chr) {
1413 	if (_vm->isScummvmKorTarget()) {
1414 		return CharsetRendererV3::getDrawWidthIntern(chr);
1415 	}
1416 
1417 #ifdef USE_RGB_COLOR
1418 	if (_vm->_useCJKMode && chr > 127) {
1419 		assert(_vm->_cjkFont);
1420 		return _vm->_cjkFont->getCharWidth(chr);
1421 	}
1422 #endif
1423 	return CharsetRendererV3::getDrawWidthIntern(chr);
1424 }
1425 
getDrawHeightIntern(uint16 chr)1426 int CharsetRendererTownsV3::getDrawHeightIntern(uint16 chr) {
1427 	if (_vm->isScummvmKorTarget()) {
1428 		return CharsetRendererV3::getDrawHeightIntern(chr);
1429 	}
1430 
1431 #ifdef USE_RGB_COLOR
1432 	if (_vm->_useCJKMode && chr > 127) {
1433 		assert(_vm->_cjkFont);
1434 		return _vm->_cjkFont->getFontHeight();
1435 	}
1436 #endif
1437 	return CharsetRendererV3::getDrawHeightIntern(chr);
1438 }
1439 
setDrawCharIntern(uint16 chr)1440 void CharsetRendererTownsV3::setDrawCharIntern(uint16 chr) {
1441 	_sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0;
1442 }
1443 #endif
1444 
1445 #ifdef USE_RGB_COLOR
drawBits1(Graphics::Surface & dest,int x,int y,const byte * src,int drawTop,int width,int height)1446 void CharsetRendererPCE::drawBits1(Graphics::Surface &dest, int x, int y, const byte *src, int drawTop, int width, int height) {
1447 	byte *dst = (byte *)dest.getBasePtr(x, y);
1448 	if (_sjisCurChar) {
1449 		assert(_vm->_cjkFont);
1450 		uint16 col1 = _color;
1451 		uint16 col2 = _shadowColor;
1452 
1453 		if (dest.format.bytesPerPixel == 2) {
1454 			col1 = _vm->_16BitPalette[col1];
1455 			col2 = _vm->_16BitPalette[col2];
1456 		}
1457 
1458 		_vm->_cjkFont->drawChar(dst, _sjisCurChar, dest.pitch, dest.format.bytesPerPixel, col1, col2, -1, -1);
1459 		return;
1460 	}
1461 
1462 	byte bits = 0;
1463 
1464 	for (y = 0; y < height && y + drawTop < dest.h; y++) {
1465 		int bitCount = 0;
1466 		for (x = 0; x < width; x++) {
1467 			if ((bitCount % 8) == 0)
1468 				bits = *src++;
1469 			if ((bits & revBitMask(bitCount % 8)) && y + drawTop >= 0) {
1470 				if (dest.format.bytesPerPixel == 2) {
1471 					if (_enableShadow)
1472 						WRITE_UINT16(dst + dest.pitch + 2, _vm->_16BitPalette[_shadowColor]);
1473 					WRITE_UINT16(dst, _vm->_16BitPalette[_color]);
1474 				} else {
1475 					if (_enableShadow)
1476 						*(dst + dest.pitch + 1) = _shadowColor;
1477 					*dst = _color;
1478 				}
1479 			}
1480 			dst += dest.format.bytesPerPixel;
1481 			bitCount++;
1482 		}
1483 
1484 		dst += dest.pitch - width * dest.format.bytesPerPixel;
1485 	}
1486 }
1487 
getDrawWidthIntern(uint16 chr)1488 int CharsetRendererPCE::getDrawWidthIntern(uint16 chr) {
1489 	if (_vm->_useCJKMode && chr > 127)
1490 		return _vm->_2byteWidth;
1491 	return CharsetRendererV3::getDrawWidthIntern(chr);
1492 }
1493 
getDrawHeightIntern(uint16 chr)1494 int CharsetRendererPCE::getDrawHeightIntern(uint16 chr) {
1495 	if (_vm->_useCJKMode && chr > 127)
1496 		return _vm->_2byteHeight;
1497 	return CharsetRendererV3::getDrawHeightIntern(chr);
1498 }
1499 
setDrawCharIntern(uint16 chr)1500 void CharsetRendererPCE::setDrawCharIntern(uint16 chr) {
1501 	_sjisCurChar = (_vm->_useCJKMode && chr > 127) ? chr : 0;
1502 }
1503 #endif
1504 
CharsetRendererMac(ScummEngine * vm,const Common::String & fontFile,bool correctFontSpacing)1505 CharsetRendererMac::CharsetRendererMac(ScummEngine *vm, const Common::String &fontFile, bool correctFontSpacing)
1506 	 : CharsetRendererCommon(vm) {
1507 
1508 	_correctFontSpacing = correctFontSpacing;
1509 	_pad = false;
1510 	_glyphSurface = NULL;
1511 
1512 	// Indy 3 provides an "Indy" font in two sizes, 9 and 12, which are
1513 	// used for the text boxes. The smaller font can be used for a
1514 	// headline. The rest of the Mac GUI seems to use a system font, but
1515 	// that is not implemented.
1516 
1517 	// As far as I can tell, Loom uses only font size 13 for in-game text.
1518 	// The font is also provided in sizes 9 and 12, and it's possible that
1519 	// 12 is used for system messages, e.g. the original pause dialog. We
1520 	// don't support that.
1521 	//
1522 	// I have no idea what size 9 is used for. Possibly the original About
1523 	// dialog?
1524 	//
1525 	// As far as I can tell, the game does not use anything fancy, like
1526 	// different styles, and the font does not appear to have a kerning
1527 	// table.
1528 	//
1529 	// Special characters:
1530 	//
1531 	// 16-23 are the note names c through c'.
1532 	// 60 is an upside-down note, i.e. the one used for c'.
1533 	// 95 is a used for the rest of the notes.
1534 
1535 	Common::MacResManager resource;
1536 	resource.open(fontFile);
1537 
1538 	Common::String fontFamilyName = (_vm->_game.id == GID_LOOM) ? "Loom" : "Indy";
1539 
1540 	Common::SeekableReadStream *fond = resource.getResource(MKTAG('F', 'O', 'N', 'D'), fontFamilyName);
1541 
1542 	if (!fond)
1543 		return;
1544 
1545 	Graphics::MacFontFamily fontFamily;
1546 	if (!fontFamily.load(*fond)) {
1547 		delete fond;
1548 		return;
1549 	}
1550 
1551 	Common::Array<Graphics::MacFontFamily::AsscEntry> *assoc = fontFamily.getAssocTable();
1552 	for (uint i = 0; i < assoc->size(); i++) {
1553 		int fontId = -1;
1554 		int fontSize = (*assoc)[i]._fontSize;
1555 
1556 		if (_vm->_game.id == GID_INDY3) {
1557 			if (fontSize == 9)
1558 				fontId = 1;
1559 			else if (fontSize == 12)
1560 				fontId = 0;
1561 		} else {
1562 			if (fontSize == 13)
1563 				fontId = 0;
1564 		}
1565 		if (fontId != -1) {
1566 			Common::SeekableReadStream *font = resource.getResource(MKTAG('F', 'O', 'N', 'T'), (*assoc)[i]._fontID);
1567 			_macFonts[fontId].loadFont(*font, &fontFamily, fontSize, 0);
1568 			delete font;
1569 		}
1570 	}
1571 
1572 	delete fond;
1573 
1574 	if (_vm->_renderMode == Common::kRenderMacintoshBW) {
1575 		int numFonts = (_vm->_game.id == GID_INDY3) ? 2 : 1;
1576 		int maxHeight = -1;
1577 		int maxWidth = -1;
1578 
1579 		for (int i = 0; i < numFonts; i++) {
1580 			maxHeight = MAX(maxHeight, _macFonts[i].getFontHeight());
1581 			maxWidth = MAX(maxWidth, _macFonts[i].getMaxCharWidth());
1582 		}
1583 
1584 		_glyphSurface = new Graphics::Surface();
1585 		_glyphSurface->create(maxWidth, maxHeight, Graphics::PixelFormat::createFormatCLUT8());
1586 	}
1587 }
1588 
~CharsetRendererMac()1589 CharsetRendererMac::~CharsetRendererMac() {
1590 	if (_glyphSurface) {
1591 		_glyphSurface->free();
1592 		delete _glyphSurface;
1593 	}
1594 }
1595 
setCurID(int32 id)1596 void CharsetRendererMac::setCurID(int32 id) {
1597 	if  (id == -1)
1598 		return;
1599 
1600 	// Indiana Jones and the Last Crusade uses font id 1 in a number of
1601 	// places. In the DOS version, this is a bolder font than font 0, but
1602 	// by the looks of it the Mac version uses the same font for both
1603 	// cases. In ScummVM, we match id 0 and 1 to font 0 and id 2 (which is
1604 	// only used to print the text box caption) to font 1.
1605 	if (_vm->_game.id == GID_INDY3) {
1606 		if (id == 1) {
1607 			id = 0;
1608 		} else if (id == 2) {
1609 			id = 1;
1610 		}
1611 	}
1612 
1613 	int maxId = (_vm->_game.id == GID_LOOM) ? 0 : 1;
1614 
1615 	if (id > maxId) {
1616 		warning("CharsetRendererMac::setCurID(%d) - invalid charset", id);
1617 		id = 0;
1618 	}
1619 
1620 	_curId = id;
1621 }
1622 
getStringWidth(int arg,const byte * text,uint strLenMax)1623 int CharsetRendererMac::getStringWidth(int arg, const byte *text, uint strLenMax) {
1624 	int pos = 0;
1625 	int width = 0;
1626 	int chr;
1627 
1628 	while ((chr = text[pos++]) != 0) {
1629 		// The only control codes I've seen in use are line breaks in
1630 		// Loom. In Indy 3, I haven't seen anything at all like it.
1631 		if (chr == 255) {
1632 			chr = text[pos++];
1633 			if (chr == 1) // 'Newline'
1634 				break;
1635 			warning("getStringWidth: Unexpected escape sequence %d", chr);
1636 		} else {
1637 			width += getDrawWidthIntern(chr);
1638 		}
1639 	}
1640 
1641 	return width / 2;
1642 }
1643 
getDrawWidthIntern(uint16 chr)1644 int CharsetRendererMac::getDrawWidthIntern(uint16 chr) {
1645 	return _macFonts[_curId].getCharWidth(chr);
1646 }
1647 
1648 // HACK: Usually, we want the approximate width and height in the unscaled
1649 //       graphics resolution. But for font 1 in Indiana Jones and the Last
1650 //       crusade we want the actual dimensions for drawing the text boxes.
1651 
getFontHeight()1652 int CharsetRendererMac::getFontHeight() {
1653 	int height = _macFonts[_curId].getFontHeight();
1654 
1655         // If we ever need the height for font 1 in Last Crusade (we don't at
1656 	// the moment), we need the actual height.
1657 	if (_curId == 0 || _vm->_game.id != GID_INDY3)
1658 		height /= 2;
1659 
1660 	return height;
1661 }
1662 
getCharWidth(uint16 chr)1663 int CharsetRendererMac::getCharWidth(uint16 chr) {
1664 	int width = getDrawWidthIntern(chr);
1665 
1666 	// For font 1 in Last Crusade, we want the real width. It is used for
1667 	// text box titles, which are drawn outside the normal font rendering.
1668 	if (_curId == 0 || _vm->_game.id != GID_INDY3)
1669 		width /= 2;
1670 
1671 	return width;
1672 }
1673 
printChar(int chr,bool ignoreCharsetMask)1674 void CharsetRendererMac::printChar(int chr, bool ignoreCharsetMask) {
1675 	// This function does most of the heavy lifting printing the game
1676 	// text. It's the only function that needs to be able to handle
1677 	// disabled text.
1678 
1679 	// If this is the beginning of a line, assume the position will be
1680 	// correct without any padding.
1681 
1682 	if (_firstChar || _top != _lastTop) {
1683 		_pad = false;
1684 	}
1685 
1686 	VirtScreen *vs;
1687 
1688 	if ((vs = _vm->findVirtScreen(_top)) == NULL) {
1689 		warning("findVirtScreen(%d) failed, therefore printChar cannot print '%c'", _top, chr);
1690 		return;
1691 	}
1692 
1693 	if (chr == '@')
1694 		return;
1695 
1696 	// Scale up the virtual coordinates to get the high resolution ones.
1697 
1698 	int macLeft = 2 * _left;
1699 	int macTop = 2 * _top;
1700 
1701 	// The last character ended on an odd X coordinate. This information
1702 	// was lost in the rounding, so we compensate for it here.
1703 
1704 	if (_pad) {
1705 		macLeft++;
1706 		_pad = false;
1707 	}
1708 
1709 	bool enableShadow = _enableShadow;
1710 	int color = _color;
1711 
1712 	// HACK: Notes and their names should always be drawn with a shadow.
1713 	//       Actually, this doesn't quite match the original but I can't
1714 	//       figure out what the original does here. The "c" looks like
1715 	//       it's shadowed in the normal way, but everything else looks
1716 	//       kind-of-but-not-quite outlined instead. Weird.
1717 	//
1718 	//       Even weirder, I've seen screenshots where there is no
1719 	//       shadowing at all. I'll just keep it like this for now,
1720 	//       because it makes the notes stand out a bit better.
1721 
1722 	if (_vm->_game.id == GID_LOOM) {
1723 		if ((chr >= 16 && chr <= 23) || chr == 60 || chr == 95) {
1724 			enableShadow = true;
1725 		}
1726 	}
1727 
1728 	// HACK: Apparently, note names are never drawn in light gray. Only
1729 	//       white for known notes, and dark gray for unknown ones. This
1730 	//       hack ensures that we won't be left with a mix of white and
1731 	//       light gray note names, because apparently the game never
1732 	//       changes them back to light gray once the draft is done?
1733 
1734 	if (_vm->_game.id == GID_LOOM) {
1735 		if (chr >= 16 && chr <= 23 && _color == 7)
1736 			color = 15;
1737 	}
1738 
1739 	bool drawToTextBox = (vs->number == kTextVirtScreen && _vm->_game.id == GID_INDY3);
1740 
1741 	if (drawToTextBox)
1742 		printCharToTextBox(chr, color, macLeft, macTop);
1743 	else
1744 		printCharInternal(chr, color, enableShadow, macLeft, macTop);
1745 
1746 	// HACK: The way we combine high and low resolution graphics means
1747 	//       that sometimes, when a note name is drawn on the distaff, the
1748 	//       note itself gets overdrawn by the low-resolution graphics.
1749 	//
1750 	//       The only workaround I can think of is to force the note to be
1751 	//       redrawn along with its name. It's enough to redraw it on the
1752 	//       text surface. We can assume the correct color is already on
1753 	//       screen.
1754 	//
1755 	//       Note that this will not affect the Practice Mode box, since
1756 	//       this note names are drawn by drawChar(), not printChar().
1757 
1758 	if (_vm->_game.id == GID_LOOM) {
1759 		if (chr >= 16 && chr <= 23) {
1760 			int xOffset[] = { 16, 14, 12, 8, 6, 2, 0, 8 };
1761 
1762 			int note = (chr == 23) ? 60 : 95;
1763 			printCharInternal(note, -1, enableShadow, macLeft + 18, macTop + xOffset[chr - 16]);
1764 		}
1765 	}
1766 
1767 	// Mark the virtual screen as dirty, using downscaled coordinates.
1768 
1769 	int left, right, top, bottom, width;
1770 
1771 	width = getDrawWidthIntern(chr);
1772 
1773 	// HACK: Indiana Jones and the Last Crusade uses incorrect spacing
1774 	// betweeen letters. Note that this incorrect spacing does not extend
1775 	// to the text boxes, nor does it seem to be used when figuring out
1776 	// the width of a string (e.g. to center text on screen). It is,
1777 	// however, used for things like the Grail Diary.
1778 
1779 	if (!_correctFontSpacing && !drawToTextBox && (width & 1))
1780 		width++;
1781 
1782 	if (enableShadow) {
1783 		left = macLeft / 2;
1784 		right = (macLeft + width + 3) / 2;
1785 		top = macTop / 2;
1786 		bottom = (macTop + _macFonts[_curId].getFontHeight() + 3) / 2;
1787 	} else {
1788 		left = (macLeft + 1) / 2;
1789 		right = (macLeft + width + 1) / 2;
1790 		top = (macTop + 1) / 2;
1791 		bottom = (macTop + _macFonts[_curId].getFontHeight() + 1) / 2;
1792 	}
1793 
1794 	if (_firstChar) {
1795 		_str.left = left;
1796 		_str.top = top;
1797 		_str.right = right;
1798 		_str.bottom = top;
1799 		_firstChar = false;
1800 	} else {
1801 		if (_str.left > left)
1802 			_str.left = left;
1803 		if (_str.right < right)
1804 			_str.right = right;
1805 		if (_str.bottom < bottom)
1806 			_str.bottom = bottom;
1807 	}
1808 
1809 	if (!drawToTextBox)
1810 		_vm->markRectAsDirty(vs->number, left, right, top - vs->topline, bottom - vs->topline);
1811 
1812 	if (!ignoreCharsetMask) {
1813 		_hasMask = true;
1814 		_textScreenID = vs->number;
1815 	}
1816 
1817 	// The next character may have to be adjusted to compensate for
1818 	// rounding errors.
1819 
1820 	macLeft += width;
1821 	if (macLeft & 1)
1822 		_pad = true;
1823 
1824 	_left = macLeft / 2;
1825 	_lastTop = _top;
1826 }
1827 
getTextColor()1828 byte CharsetRendererMac::getTextColor() {
1829 	if (_vm->_renderMode == Common::kRenderMacintoshBW) {
1830 		// White and black can be rendered as is, and 8 is the color
1831 		// used for disabled text (verbs in Indy 3, notes in Loom).
1832 		// Everything else should be white.
1833 
1834 		if (_color == 0 || _color == 15 || _color == 8)
1835 			return _color;
1836 		return 15;
1837 	}
1838 	return _color;
1839 }
1840 
getTextShadowColor()1841 byte CharsetRendererMac::getTextShadowColor() {
1842 	if (_vm->_renderMode == Common::kRenderMacintoshBW) {
1843 		if (getTextColor() == 0)
1844 			return 15;
1845 		return 0;
1846 	}
1847 	return _shadowColor;
1848 }
1849 
printCharInternal(int chr,int color,bool shadow,int x,int y)1850 void CharsetRendererMac::printCharInternal(int chr, int color, bool shadow, int x, int y) {
1851 	if (_vm->_game.id == GID_LOOM) {
1852 		x++;
1853 		y++;
1854 	}
1855 
1856 	if (shadow) {
1857 		byte shadowColor = getTextShadowColor();
1858 
1859 		if (_vm->_game.id == GID_LOOM) {
1860 			// Shadowing is a bit of guesswork. It doesn't look
1861 			// like it's using the Mac's built-in form of shadowed
1862 			// text (which, as I recall it, never looked
1863 			// particularly good anyway). This seems to match the
1864 			// original look for normal text.
1865 
1866 			_macFonts[_curId].drawChar(&_vm->_textSurface, chr, x + 1, y - 1, 0);
1867 			_macFonts[_curId].drawChar(&_vm->_textSurface, chr, x - 1, y + 1, 0);
1868 			_macFonts[_curId].drawChar(&_vm->_textSurface, chr, x + 2, y + 2, 0);
1869 
1870 			if (color != -1) {
1871 				_macFonts[_curId].drawChar(_vm->_macScreen, chr, x + 1, y - 1, shadowColor);
1872 				_macFonts[_curId].drawChar(_vm->_macScreen, chr, x - 1, y + 1, shadowColor);
1873 				_macFonts[_curId].drawChar(_vm->_macScreen, chr, x + 2, y + 2, shadowColor);
1874 			}
1875 		} else {
1876 			// Indy 3 uses simpler shadowing, and doesn't need the
1877 			// "draw only on text surface" hack.
1878 
1879 			_macFonts[_curId].drawChar(&_vm->_textSurface, chr, x + 1, y + 1, 0);
1880 			_macFonts[_curId].drawChar(_vm->_macScreen, chr, x + 1, y + 1, shadowColor);
1881 		}
1882 	}
1883 
1884 	_macFonts[_curId].drawChar(&_vm->_textSurface, chr, x, y, 0);
1885 
1886 	if (color != -1) {
1887 		color = getTextColor();
1888 
1889 		if (_vm->_renderMode == Common::kRenderMacintoshBW && color != 0 && color != 15) {
1890 			_glyphSurface->fillRect(Common::Rect(_glyphSurface->w, _glyphSurface->h), 0);
1891 			_macFonts[_curId].drawChar(_glyphSurface, chr, 0, 0, 15);
1892 
1893 			byte *src = (byte *)_glyphSurface->getBasePtr(0, 0);
1894 			byte *dst = (byte *)_vm->_macScreen->getBasePtr(x, y);
1895 
1896 			for (int h = 0; h < _glyphSurface->h; h++) {
1897 				bool pixel = ((y + h + 1) & 1) == 0;
1898 
1899 				for (int w = 0; w < _glyphSurface->w; w++) {
1900 					if (src[w]) {
1901 						if (pixel)
1902 							dst[w] = 15;
1903 						else
1904 							dst[w] = 0;
1905 					}
1906 					pixel = !pixel;
1907 				}
1908 				src += _glyphSurface->pitch;
1909 				dst += _vm->_macScreen->pitch;
1910 			}
1911 		} else {
1912 			_macFonts[_curId].drawChar(_vm->_macScreen, chr, x, y, color);
1913 		}
1914 	}
1915 }
1916 
printCharToTextBox(int chr,int color,int x,int y)1917 void CharsetRendererMac::printCharToTextBox(int chr, int color, int x, int y) {
1918 	// This function handles printing most of the text in the text boxes
1919 	// in Indiana Jones and the last crusade. In black and white mode, all
1920 	// text is white. Text is never disabled.
1921 
1922 	if (_vm->_renderMode == Common::kRenderMacintoshBW)
1923 		color = 15;
1924 
1925 	// Since we're working with unscaled coordinates most of the time, the
1926 	// lines of the text box weren't spaced quite as much as in the
1927 	// original. I thought no one would notice, but I was wrong. This is
1928 	// the best way I can think of to fix that.
1929 
1930 	if (y > 0)
1931 		y = 17;
1932 
1933 	_macFonts[_curId].drawChar(_vm->_macIndy3TextBox, chr, x + 5, y + 11, color);
1934 }
1935 
drawChar(int chr,Graphics::Surface & s,int x,int y)1936 void CharsetRendererMac::drawChar(int chr, Graphics::Surface &s, int x, int y) {
1937 	// This function is used for drawing most of the text outside of what
1938 	// the game scripts request. It's used for the text box captions in
1939 	// Indiana Jones and the Last Crusade, and for the practice mode box
1940 	// in Loom.
1941 	int color = _color;
1942 
1943 	if (_vm->_renderMode == Common::kRenderMacintoshBW)
1944 		color = 15;
1945 
1946 	_macFonts[_curId].drawChar(&s, chr, x, y, color);
1947 }
1948 
setColor(byte color)1949 void CharsetRendererMac::setColor(byte color) {
1950 	_color = color;
1951 	_enableShadow = false;
1952 	_shadowColor = 0;
1953 
1954 	_enableShadow = ((color & 0xF0) != 0);
1955 	// Anything outside the ordinary palette should be fine.
1956 	_shadowColor = 255;
1957 	_color &= 0x0F;
1958 }
1959 
1960 #ifdef ENABLE_SCUMM_7_8
CharsetRendererNut(ScummEngine * vm)1961 CharsetRendererNut::CharsetRendererNut(ScummEngine *vm)
1962 	 : CharsetRenderer(vm) {
1963 	_current = 0;
1964 
1965 	for (int i = 0; i < 5; i++) {
1966 		_fr[i] = NULL;
1967 	}
1968 }
1969 
~CharsetRendererNut()1970 CharsetRendererNut::~CharsetRendererNut() {
1971 	for (int i = 0; i < 5; i++) {
1972 		delete _fr[i];
1973 	}
1974 }
1975 
setCurID(int32 id)1976 void CharsetRendererNut::setCurID(int32 id) {
1977 	if (id == -1)
1978 		return;
1979 
1980 	int numFonts = ((_vm->_game.id == GID_CMI) && (_vm->_game.features & GF_DEMO)) ? 4 : 5;
1981 	assert(id < numFonts);
1982 	_curId = id;
1983 	if (!_fr[id]) {
1984 		char fontname[11];
1985 		sprintf(fontname, "font%d.nut", id);
1986 		_fr[id] = new NutRenderer(_vm, fontname);
1987 	}
1988 	_current = _fr[id];
1989 	assert(_current);
1990 }
1991 
getCharHeight(byte chr)1992 int CharsetRendererNut::getCharHeight(byte chr) {
1993 	assert(_current);
1994 	return _current->getCharHeight(chr);
1995 }
1996 
getCharWidth(uint16 chr)1997 int CharsetRendererNut::getCharWidth(uint16 chr) {
1998 	assert(_current);
1999 	return _current->getCharWidth(chr);
2000 }
2001 
getFontHeight()2002 int CharsetRendererNut::getFontHeight() {
2003 	// FIXME / TODO: how to implement this properly???
2004 	assert(_current);
2005 	return _current->getCharHeight('|');
2006 }
2007 
getStringWidth(int arg,const byte * text,uint strLenMax)2008 int CharsetRendererNut::getStringWidth(int arg, const byte *text, uint strLenMax) {
2009 	// SCUMM7 games actually use the same implemention (minus the strLenMax parameter). If
2010 	// any text placement bugs in one of these games come up it might be worth to look at
2011 	// that. Or simply for the fact that we could get rid of SmushFont::getStringWidth()...
2012 	if (!strLenMax)
2013 		return 0;
2014 
2015 	int maxWidth = 0;
2016 	int width = 0;
2017 
2018 	while (*text && strLenMax) {
2019 		while (text[0] == '^') {
2020 			switch (text[1]) {
2021 			case 'f':
2022 				// We should change the font on the fly at this point
2023 				// which would result in a different width result.
2024 				// This has never been observed in the game though, and
2025 				// as such, we don't handle it.
2026 				text += 4;
2027 				break;
2028 			case 'c':
2029 				text += 5;
2030 				break;
2031 			default:
2032 				error("CharsetRenderer::getStringWidth(): Invalid escape code in text string");
2033 			}
2034 		}
2035 
2036 		if (is2ByteCharacter(_vm->_language, *text)) {
2037 			width += _vm->_2byteWidth + (_vm->_language != Common::JA_JPN ? 1 : 0);
2038 			++text;
2039 			--strLenMax;
2040 		} else if (*text == '\n') {
2041 			maxWidth = MAX<int>(width, maxWidth);
2042 			width = 0;
2043 		} else if (*text != '\r' && *text != _vm->_newLineCharacter) {
2044 			width += getCharWidth(*text);
2045 		}
2046 
2047 		++text;
2048 		--strLenMax;
2049 	}
2050 
2051 	return MAX<int>(width, maxWidth);
2052 }
2053 
printChar(int chr,bool ignoreCharsetMask)2054 void CharsetRendererNut::printChar(int chr, bool ignoreCharsetMask) {
2055 	Common::Rect shadow;
2056 
2057 	assert(_current);
2058 	if (chr == '@')
2059 		return;
2060 
2061 	shadow.left = _left;
2062 	shadow.top = _top;
2063 
2064 	if (_firstChar) {
2065 		_str.left = (shadow.left >= 0) ? shadow.left : 0;
2066 		_str.top = (shadow.top >= 0) ? shadow.top : 0;
2067 		_str.right = _str.left;
2068 		_str.bottom = _str.top;
2069 		_firstChar = false;
2070 	}
2071 
2072 	int width = _current->getCharWidth(chr);
2073 	int height = _current->getCharHeight(chr);
2074 
2075 	bool is2byte = chr >= 256 && _vm->_useCJKMode;
2076 	if (is2byte) {
2077 		width = _vm->_2byteWidth;
2078 		if (_vm->_game.id == GID_CMI)
2079 			height++; // One extra pixel for the shadow
2080 	}
2081 
2082 	shadow.right = _left + width;
2083 	shadow.bottom = _top + height;
2084 
2085 	Graphics::Surface s;
2086 	if (!ignoreCharsetMask) {
2087 		_hasMask = true;
2088 		_textScreenID = kMainVirtScreen;
2089 	}
2090 
2091 	int drawTop = _top;
2092 	if (ignoreCharsetMask) {
2093 		VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
2094 		s = *vs;
2095 		s.setPixels(vs->getPixels(0, 0));
2096 	} else {
2097 		s = _vm->_textSurface;
2098 		drawTop -= _vm->_screenTop;
2099 	}
2100 
2101 	if (chr >= 256 && _vm->_useCJKMode)
2102 		_current->draw2byte(s, chr, _left, drawTop, _color);
2103 	else
2104 		_current->drawChar(s, (byte)chr, _left, drawTop, _color);
2105 	_vm->markRectAsDirty(kMainVirtScreen, shadow);
2106 
2107 	if (_str.left > _left)
2108 		_str.left = _left;
2109 
2110 	// Original keeps glyph width and character dimensions separately
2111 	if ((_vm->_language == Common::ZH_TWN || _vm->_language == Common::KO_KOR) && is2byte)
2112 		width++;
2113 
2114 	_left += width;
2115 
2116 	if (_str.right < shadow.right)
2117 		_str.right = shadow.right;
2118 
2119 	if (_str.bottom < shadow.bottom)
2120 		_str.bottom = shadow.bottom;
2121 }
2122 #endif
2123 
printChar(int chr,bool ignoreCharsetMask)2124 void CharsetRendererNES::printChar(int chr, bool ignoreCharsetMask) {
2125 	int width, height, origWidth, origHeight;
2126 	VirtScreen *vs;
2127 	byte *charPtr;
2128 
2129 	// Init it here each time since it is cheap and fixes bug with
2130 	// charset after game load
2131 	_trTable = _vm->getResourceAddress(rtCostume, 77) + 2;
2132 
2133 	// HACK: how to set it properly?
2134 	if (_top == 0)
2135 		_top = 16;
2136 
2137 	if ((vs = _vm->findVirtScreen(_top)) == NULL)
2138 		return;
2139 
2140 	if (chr == '@')
2141 		return;
2142 
2143 	charPtr = _vm->_NESPatTable[1] + _trTable[chr - 32] * 16;
2144 	width = getCharWidth(chr);
2145 	height = 8;
2146 
2147 	origWidth = width;
2148 	origHeight = height;
2149 
2150 	if (_firstChar) {
2151 		_str.left = _left;
2152 		_str.top = _top;
2153 		_str.right = _left;
2154 		_str.bottom = _top;
2155 		_firstChar = false;
2156 	}
2157 
2158 	int drawTop = _top - vs->topline;
2159 
2160 	_vm->markRectAsDirty(vs->number, _left, _left + width, drawTop, drawTop + height);
2161 
2162 	if (!ignoreCharsetMask) {
2163 		_hasMask = true;
2164 		_textScreenID = vs->number;
2165 	}
2166 
2167 	if (ignoreCharsetMask || !vs->hasTwoBuffers)
2168 		drawBits1(*vs, _left + vs->xstart, drawTop, charPtr, drawTop, origWidth, origHeight);
2169 	else
2170 		drawBits1(_vm->_textSurface, _left, _top, charPtr, drawTop, origWidth, origHeight);
2171 
2172 	if (_str.left > _left)
2173 		_str.left = _left;
2174 
2175 	_left += origWidth;
2176 
2177 	if (_str.right < _left) {
2178 		_str.right = _left;
2179 		if (_enableShadow)
2180 			_str.right++;
2181 	}
2182 
2183 	if (_str.bottom < _top + height)
2184 		_str.bottom = _top + height;
2185 }
2186 
drawChar(int chr,Graphics::Surface & s,int x,int y)2187 void CharsetRendererNES::drawChar(int chr, Graphics::Surface &s, int x, int y) {
2188 	byte *charPtr;
2189 	int width, height;
2190 
2191 	if (!_trTable)
2192 		_trTable = _vm->getResourceAddress(rtCostume, 77) + 2;
2193 
2194 	charPtr = _vm->_NESPatTable[1] + _trTable[chr - 32] * 16;
2195 	width = getCharWidth(chr);
2196 	height = 8;
2197 
2198 	drawBits1(s, x, y, charPtr, y, width, height);
2199 }
2200 
2201 #ifdef USE_RGB_COLOR
2202 #ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
CharsetRendererTownsClassic(ScummEngine * vm)2203 CharsetRendererTownsClassic::CharsetRendererTownsClassic(ScummEngine *vm) : CharsetRendererClassic(vm), _sjisCurChar(0) {
2204 }
2205 
getCharWidth(uint16 chr)2206 int CharsetRendererTownsClassic::getCharWidth(uint16 chr) {
2207 	int spacing = 0;
2208 
2209 	if (_vm->_useCJKMode) {
2210 		if ((chr & 0xff00) == 0xfd00) {
2211 			chr &= 0xff;
2212 		} else if (chr >= 256) {
2213 			spacing = 8;
2214 		} else if (useFontRomCharacter(chr)) {
2215 			spacing = 4;
2216 		}
2217 
2218 		if (spacing) {
2219 			if (_vm->_game.id == GID_MONKEY) {
2220 				spacing++;
2221 				if (_curId == 2)
2222 					spacing++;
2223 			} else if (_vm->_game.id != GID_INDY4 && _curId == 1) {
2224 				spacing++;
2225 			}
2226 		}
2227 	}
2228 
2229 	if (!spacing) {
2230 		int offs = READ_LE_UINT32(_fontPtr + chr * 4 + 4);
2231 		if (offs)
2232 			spacing = _fontPtr[offs] + (signed char)_fontPtr[offs + 2];
2233 	}
2234 
2235 	return spacing;
2236 }
2237 
getFontHeight()2238 int CharsetRendererTownsClassic::getFontHeight() {
2239 	static const uint8 sjisFontHeightM1[] = { 0, 8, 9, 8, 9, 8, 9, 0, 0, 0 };
2240 	static const uint8 sjisFontHeightM2[] = { 0, 8, 9, 9, 9, 8, 9, 9, 9, 8 };
2241 	static const uint8 sjisFontHeightI4[] = { 0, 8, 9, 9, 9, 8, 8, 8, 8, 8 };
2242 	const uint8 *htbl = (_vm->_game.id == GID_MONKEY) ? sjisFontHeightM1 : ((_vm->_game.id == GID_INDY4) ? sjisFontHeightI4 : sjisFontHeightM2);
2243 	return _vm->_useCJKMode ? htbl[_curId] : _fontHeight;
2244 }
2245 
drawBitsN(const Graphics::Surface &,byte * dst,const byte * src,byte bpp,int drawTop,int width,int height)2246 void CharsetRendererTownsClassic::drawBitsN(const Graphics::Surface&, byte *dst, const byte *src, byte bpp, int drawTop, int width, int height) {
2247 	if (_sjisCurChar) {
2248 		assert(_vm->_cjkFont);
2249 		_vm->_cjkFont->drawChar(_vm->_textSurface, _sjisCurChar, _left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier, _vm->_townsCharsetColorMap[1], _shadowColor);
2250 		return;
2251 	}
2252 
2253 	bool scale2x = (_vm->_textSurfaceMultiplier == 2);
2254 	dst = (byte *)_vm->_textSurface.getBasePtr(_left * _vm->_textSurfaceMultiplier, (_top - _vm->_screenTop) * _vm->_textSurfaceMultiplier);
2255 
2256 	int y, x;
2257 	int color;
2258 	byte numbits, bits;
2259 
2260 	int pitch = _vm->_textSurface.pitch - width;
2261 
2262 	assert(bpp == 1 || bpp == 2 || bpp == 4 || bpp == 8);
2263 	bits = *src++;
2264 	numbits = 8;
2265 	byte *cmap = _vm->_charsetColorMap;
2266 	byte *dst2 = dst;
2267 
2268 	if (_vm->_game.platform == Common::kPlatformFMTowns)
2269 		cmap = _vm->_townsCharsetColorMap;
2270 	if (scale2x) {
2271 		dst2 += _vm->_textSurface.pitch;
2272 		pitch <<= 1;
2273 	}
2274 
2275 	for (y = 0; y < height && y + drawTop < _vm->_textSurface.h; y++) {
2276 		for (x = 0; x < width; x++) {
2277 			color = (bits >> (8 - bpp)) & 0xFF;
2278 
2279 			if (color && y + drawTop >= 0) {
2280 				*dst = cmap[color];
2281 				if (scale2x)
2282 					dst[1] = dst2[0] = dst2[1] = dst[0];
2283 			}
2284 			dst++;
2285 
2286 			if (scale2x) {
2287 				dst++;
2288 				dst2 += 2;
2289 			}
2290 
2291 			bits <<= bpp;
2292 			numbits -= bpp;
2293 			if (numbits == 0) {
2294 				bits = *src++;
2295 				numbits = 8;
2296 			}
2297 		}
2298 		dst += pitch;
2299 		dst2 += pitch;
2300 	}
2301 }
2302 
prepareDraw(uint16 chr)2303 bool CharsetRendererTownsClassic::prepareDraw(uint16 chr) {
2304 	processCharsetColors();
2305 	bool noSjis = false;
2306 
2307 	if (_vm->_game.platform == Common::kPlatformFMTowns && _vm->_useCJKMode) {
2308 		if ((chr & 0x00ff) == 0x00fd) {
2309 			chr >>= 8;
2310 			noSjis = true;
2311 		}
2312 	}
2313 
2314 	if (useFontRomCharacter(chr) && !noSjis) {
2315 		setupShadowMode();
2316 		_charPtr = 0;
2317 		_sjisCurChar = chr;
2318 
2319 		_width = getCharWidth(chr);
2320 		// For whatever reason MI1 uses a different font width
2321 		// for alignment calculation and for drawing when
2322 		// charset 2 is active. This fixes some subtle glitches.
2323 		if (_vm->_game.id == GID_MONKEY && _curId == 2)
2324 			_width--;
2325 		_origWidth = _width;
2326 
2327 		_origHeight = _height = getFontHeight();
2328 		_offsX = _offsY = 0;
2329 	} else if (_vm->_useCJKMode && (chr >= 128) && !noSjis) {
2330 		setupShadowMode();
2331 		_origWidth = _width = _vm->_2byteWidth;
2332 		_origHeight = _height = _vm->_2byteHeight;
2333 		_charPtr = _vm->get2byteCharPtr(chr);
2334 		_offsX = _offsY = 0;
2335 		if (_enableShadow) {
2336 			_width++;
2337 			_height++;
2338 		}
2339 	} else {
2340 		_sjisCurChar = 0;
2341 		return CharsetRendererClassic::prepareDraw(chr);
2342 	}
2343 	return true;
2344 }
2345 
setupShadowMode()2346 void CharsetRendererTownsClassic::setupShadowMode() {
2347 	_enableShadow = true;
2348 	_shadowColor = _vm->_townsCharsetColorMap[0];
2349 	assert(_vm->_cjkFont);
2350 
2351 	if (((_vm->_game.id == GID_MONKEY) && (_curId == 2 || _curId == 4 || _curId == 6)) ||
2352 		((_vm->_game.id == GID_MONKEY2) && (_curId != 1 && _curId != 5 && _curId != 9)) ||
2353 		((_vm->_game.id == GID_INDY4) && (_curId == 2 || _curId == 3 || _curId == 4))) {
2354 			_vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kOutlineMode);
2355 	} else {
2356 		_vm->_cjkFont->setDrawingMode(Graphics::FontSJIS::kDefaultMode);
2357 	}
2358 
2359 	_vm->_cjkFont->toggleFlippedMode((_vm->_game.id == GID_MONKEY || _vm->_game.id == GID_MONKEY2) && _curId == 3);
2360 }
2361 
useFontRomCharacter(uint16 chr)2362 bool CharsetRendererTownsClassic::useFontRomCharacter(uint16 chr) {
2363 	if (!_vm->_useCJKMode)
2364 		return false;
2365 
2366 	// Some SCUMM 5 games contain hard coded logic to determine whether to use
2367 	// the SCUMM fonts or the FM-Towns font rom to draw a character. For the other
2368 	// games we will simply check for a character greater 127.
2369 	if (chr < 128) {
2370 		if (((_vm->_game.id == GID_MONKEY2 && _curId != 0) || (_vm->_game.id == GID_INDY4 && _curId != 3)) && (chr > 31 && chr != 94 && chr != 95 && chr != 126 && chr != 127))
2371 			return true;
2372 		return false;
2373 	}
2374 	return true;
2375 }
2376 
processCharsetColors()2377 void CharsetRendererTownsClassic::processCharsetColors() {
2378 	for (int i = 0; i < (1 << _bytesPerPixel); i++) {
2379 		uint8 c = _vm->_charsetColorMap[i];
2380 
2381 		if (c > 16) {
2382 			uint8 t = (_vm->_currentPalette[c * 3] < 32) ? 4 : 12;
2383 			t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 2 : 10);
2384 			t |= ((_vm->_currentPalette[c * 3 + 1] < 32) ? 1 : 9);
2385 			c = t;
2386 		}
2387 
2388 		if (c == 0)
2389 			c = _vm->_townsOverrideShadowColor;
2390 
2391 		c = ((c & 0x0f) << 4) | (c & 0x0f);
2392 		_vm->_townsCharsetColorMap[i] = c;
2393 	}
2394 }
2395 #endif
2396 #endif
2397 
drawBits1(Graphics::Surface & dest,int x,int y,const byte * src,int drawTop,int width,int height)2398 void CharsetRendererNES::drawBits1(Graphics::Surface &dest, int x, int y, const byte *src, int drawTop, int width, int height) {
2399 	byte *dst = (byte *)dest.getBasePtr(x, y);
2400 	for (int i = 0; i < 8; i++) {
2401 		byte c0 = src[i];
2402 		byte c1 = src[i + 8];
2403 		for (int j = 0; j < 8; j++)
2404 			dst[j] = _vm->_NESPalette[0][((c0 >> (7 - j)) & 1) | (((c1 >> (7 - j)) & 1) << 1) |
2405 			(_color ? 12 : 8)];
2406 		dst += dest.pitch;
2407 	}
2408 }
2409 
2410 } // End of namespace Scumm
2411