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