SCUMM: (SCUMM7/8) - reorganize font rendering - second part

- Attach actor talk texts to the appropriate text renderer and get rid of redundant code.
- Cleanup subtitle text handling.
- Fix handling of ^codes.
- Fix more regressions from last commit.
- Correct some x/y positioning.
This commit is contained in:
athrxx 2021-08-28 17:38:38 +02:00
parent 1f56132725
commit 0256e92c25
10 changed files with 164 additions and 448 deletions

View file

@ -1965,21 +1965,39 @@ void CharsetRendererMac::setColor(byte color) {
} }
#ifdef ENABLE_SCUMM_7_8 #ifdef ENABLE_SCUMM_7_8
int CharsetRendererV7::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) { int CharsetRendererV7::draw2byte(byte*, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) {
if (!prepareDraw(chr)) if (_vm->isScummvmKorTarget()) {
enableShadow(true);
_charPtr = _vm->get2byteCharPtr(chr);
_origWidth = _width = _vm->_2byteWidth;
_origHeight = _height = _vm->_2byteHeight;
_offsX = _offsY = 0;
} else if (!prepareDraw(chr)) {
return 0; return 0;
}
_color = col;
VirtScreen &vs = _vm->_virtscr[kMainVirtScreen]; VirtScreen &vs = _vm->_virtscr[kMainVirtScreen];
drawBits1(vs, x + vs.xstart, y, _charPtr, MAX<int>(clipRect.top, y), _width - 1, _height); drawBits1(vs, x + vs.xstart, y, _charPtr, MAX<int>(clipRect.top, y), _origWidth, _origHeight);
return _width;
return _origWidth + _spacing;
} }
int CharsetRendererV7::drawChar(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, byte chr) { int CharsetRendererV7::drawChar(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, byte chr) {
if (!prepareDraw(chr)) if (!prepareDraw(chr))
return 0; return 0;
if (_vm->isScummvmKorTarget()) {
_origWidth = _width;
_origHeight = _height;
}
_width = getCharWidth(chr);
_vm->_charsetColorMap[1] = col;
VirtScreen &vs = _vm->_virtscr[kMainVirtScreen]; VirtScreen &vs = _vm->_virtscr[kMainVirtScreen];
drawBitsN(vs, buffer + y * vs.pitch + x, _charPtr, _vm->_bytesPerPixel, y, _origWidth, _origHeight); drawBitsN(vs, buffer + (y + _offsY) * vs.pitch + vs.xstart + x, _charPtr, *_fontPtr, y, _origWidth, _origHeight);
return _width; return _width;
} }
@ -2027,7 +2045,7 @@ int CharsetRendererNut::getCharHeight(uint16 chr) const {
int CharsetRendererNut::getCharWidth(uint16 chr) const { int CharsetRendererNut::getCharWidth(uint16 chr) const {
assert(_current); assert(_current);
return _current->getCharWidth(chr); return _current->getCharWidth(chr & 0xFF);
} }
int CharsetRendererNut::getFontHeight() const { int CharsetRendererNut::getFontHeight() const {
@ -2035,122 +2053,6 @@ int CharsetRendererNut::getFontHeight() const {
return _current->getFontHeight(); return _current->getFontHeight();
} }
int CharsetRendererNut::getStringWidth(int arg, const byte *text, uint strLenMax) {
// SCUMM7 games actually use the same implemention (minus the strLenMax parameter). If
// any text placement bugs in one of these games come up it might be worth to look at
// that. Or simply for the fact that we could get rid of SmushFont::getStringWidth()...
if (!strLenMax)
return 0;
int maxWidth = 0;
int width = 0;
while (*text && strLenMax) {
while (text[0] == '^') {
switch (text[1]) {
case 'f':
// We should change the font on the fly at this point
// which would result in a different width result.
// This has never been observed in the game though, and
// as such, we don't handle it.
text += 4;
break;
case 'c':
text += 5;
break;
default:
error("CharsetRenderer::getStringWidth(): Invalid escape code in text string");
}
}
if (is2ByteCharacter(_vm->_language, *text)) {
width += _vm->_2byteWidth + (_vm->_language != Common::JA_JPN ? 1 : 0);
++text;
--strLenMax;
} else if (*text == '\n') {
maxWidth = MAX<int>(width, maxWidth);
width = 0;
} else if (*text != '\r' && *text != _vm->_newLineCharacter) {
width += getCharWidth(*text);
}
++text;
--strLenMax;
}
return MAX<int>(width, maxWidth);
}
void CharsetRendererNut::printChar(int chr, bool ignoreCharsetMask) {
/*Common::Rect shadow;
assert(_current);
if (chr == '@')
return;
shadow.left = _left;
shadow.top = _top;
if (_firstChar) {
_str.left = (shadow.left >= 0) ? shadow.left : 0;
_str.top = (shadow.top >= 0) ? shadow.top : 0;
_str.right = _str.left;
_str.bottom = _str.top;
_firstChar = false;
}
int width = _current->getCharWidth(chr);
int height = _current->getCharHeight(chr);
bool is2byte = chr >= 256 && _vm->_useCJKMode;
if (is2byte) {
width = _vm->_2byteWidth;
if (_vm->_game.id == GID_CMI)
height++; // One extra pixel for the shadow
}
shadow.right = _left + width;
shadow.bottom = _top + height;
Graphics::Surface s;
if (!ignoreCharsetMask) {
_hasMask = true;
_textScreenID = kMainVirtScreen;
}
int drawTop = _top;
if (ignoreCharsetMask) {
VirtScreen *vs = &_vm->_virtscr[kMainVirtScreen];
s = *vs;
s.setPixels(vs->getPixels(0, 0));
} else {
s = _vm->_textSurface;
drawTop -= _vm->_screenTop;
}
Common::Rect clipRect(s.w, s.h);
if (chr >= 256 && _vm->_useCJKMode)
_current->draw2byte((uint8*)s.getBasePtr(0, 0), clipRect, _left, drawTop, s.pitch, _color, chr);
else
_current->drawChar((uint8*)s.getBasePtr(0, 0), clipRect, _left, drawTop, s.pitch, _color, (byte)chr);
_vm->markRectAsDirty(kMainVirtScreen, shadow);
if (_str.left > _left)
_str.left = _left;
// Original keeps glyph width and character dimensions separately
if ((_vm->_language == Common::ZH_TWN || _vm->_language == Common::KO_KOR) && is2byte)
width++;
_left += width;
if (_str.right < shadow.right)
_str.right = shadow.right;
if (_str.bottom < shadow.bottom)
_str.bottom = shadow.bottom;*/
}
int CharsetRendererNut::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) { int CharsetRendererNut::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) {
assert(_current); assert(_current);
return _current->draw2byte(buffer, clipRect, x, y, pitch, col, chr); return _current->draw2byte(buffer, clipRect, x, y, pitch, col, chr);

View file

@ -310,15 +310,20 @@ public:
#ifdef ENABLE_SCUMM_7_8 #ifdef ENABLE_SCUMM_7_8
class CharsetRendererV7 : public CharsetRendererClassic, public GlyphRenderer_v7 { class CharsetRendererV7 : public CharsetRendererClassic, public GlyphRenderer_v7 {
public: public:
CharsetRendererV7(ScummEngine *vm) : CharsetRendererClassic(vm) {} CharsetRendererV7(ScummEngine *vm) : CharsetRendererClassic(vm), _spacing(vm->_useCJKMode && vm->_language != Common::JA_JPN ? 1 : 0) {}
~CharsetRendererV7() override {}; ~CharsetRendererV7() override {};
void printChar(int chr, bool ignoreCharsetMask) override { error("CharsetRendererV7::printChar(): Unexpected legacy function call"); }
int draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) override; int draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, uint16 chr) override;
int drawChar(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, byte chr) override; int drawChar(byte *buffer, Common::Rect &clipRect, int x, int y, int pitch, int16 col, byte chr) override;
int getCharWidth(uint16 chr) const override { return CharsetRendererClassic::getCharWidth(chr); } int getCharWidth(uint16 chr) const override { return ((chr & 0x80) && _vm->_useCJKMode) ? _vm->_2byteWidth + _spacing : CharsetRendererClassic::getCharWidth(chr); }
int getCharHeight(uint16 chr) const override { return ((chr & 0x80) && _vm->_useCJKMode) ? _vm->_2byteHeight + 1 : _fontHeight; } int getCharHeight(uint16 chr) const override { return ((chr & 0x80) && _vm->_useCJKMode) ? _vm->_2byteHeight + 1 : _fontHeight; }
int getFontHeight() const override { return _fontHeight; } int getFontHeight() const override { return _fontHeight; }
int setFont(int) override { return 0; } int setFont(int) override { return 0; }
private:
const int _spacing;
}; };
class CharsetRendererNut : public CharsetRenderer, public GlyphRenderer_v7 { class CharsetRendererNut : public CharsetRenderer, public GlyphRenderer_v7 {
@ -326,7 +331,7 @@ public:
CharsetRendererNut(ScummEngine *vm); CharsetRendererNut(ScummEngine *vm);
~CharsetRendererNut() override; ~CharsetRendererNut() override;
void printChar(int chr, bool ignoreCharsetMask) override; void printChar(int chr, bool ignoreCharsetMask) override { error("CharsetRendererNut::printChar(): Unexpected legacy function call"); }
void setCurID(int32 id) override; void setCurID(int32 id) override;
int setFont(int id) override; int setFont(int id) override;

View file

@ -33,14 +33,28 @@ NutRenderer::NutRenderer(ScummEngine *vm, const char *filename) :
_maxCharSize(0), _maxCharSize(0),
_fontHeight(0), _fontHeight(0),
_charBuffer(0), _charBuffer(0),
_decodedData(0) { _decodedData(0),
memset(_chars, 0, sizeof(_chars)); _2byteColorTable(0),
loadFont(filename); _2byteShadowXOffsetTable(0),
_2byteShadowYOffsetTable(0),
_2byteMainColor(0),
_spacing(vm->_useCJKMode && vm->_language != Common::JA_JPN ? 1 : 0),
_2byteSteps(vm->_game.version == 8 ? 4 : 2) {
static const int8 cjkShadowOffsetsX[4] = { -1, 0, 1, 0 };
static const int8 cjkShadowOffsetsY[4] = { 0, 1, 0, 0 };
_2byteShadowXOffsetTable = &cjkShadowOffsetsX[ARRAYSIZE(cjkShadowOffsetsX) - _2byteSteps];
_2byteShadowYOffsetTable = &cjkShadowOffsetsY[ARRAYSIZE(cjkShadowOffsetsY) - _2byteSteps];
_2byteColorTable = new uint8[_2byteSteps];
memset(_2byteColorTable, 0, _2byteSteps);
_2byteMainColor = &_2byteColorTable[_2byteSteps - 1];
memset(_chars, 0, sizeof(_chars));
loadFont(filename);
} }
NutRenderer::~NutRenderer() { NutRenderer::~NutRenderer() {
delete[] _charBuffer; delete[] _charBuffer;
delete[] _decodedData; delete[] _decodedData;
delete[] _2byteColorTable;
} }
void smush_decode_codec1(byte *dst, const byte *src, int left, int top, int width, int height, int pitch); void smush_decode_codec1(byte *dst, const byte *src, int left, int top, int width, int height, int pitch);
@ -260,7 +274,7 @@ void NutRenderer::loadFont(const char *filename) {
int NutRenderer::getCharWidth(byte c) const { int NutRenderer::getCharWidth(byte c) const {
if (c >= 0x80 && _vm->_useCJKMode) if (c >= 0x80 && _vm->_useCJKMode)
return _vm->_2byteWidth / 2; return _vm->_2byteWidth + _spacing;
if (c >= _numChars) if (c >= _numChars)
error("invalid character in NutRenderer::getCharWidth : %d (%d)", c, _numChars); error("invalid character in NutRenderer::getCharWidth : %d (%d)", c, _numChars);
@ -369,6 +383,9 @@ int NutRenderer::drawChar(byte *buffer, Common::Rect &clipRect, int x, int y, in
dst += minY * pitch; dst += minY * pitch;
} }
if (minX)
dst += minX;
char color = (col != -1) ? col : 1; char color = (col != -1) ? col : 1;
if (_vm->_game.version == 7) { if (_vm->_game.version == 7) {
@ -426,39 +443,33 @@ int NutRenderer::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, i
int height = MIN((int)_vm->_2byteHeight, clipRect.bottom - y); int height = MIN((int)_vm->_2byteHeight, clipRect.bottom - y);
int minX = x < clipRect.left ? clipRect.left - x : 0; int minX = x < clipRect.left ? clipRect.left - x : 0;
int minY = y < clipRect.top ? clipRect.top - y : 0; int minY = y < clipRect.top ? clipRect.top - y : 0;
*_2byteMainColor = col;
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0)
return 0; return 0;
const byte *src = _vm->get2byteCharPtr(chr); const byte *src = _vm->get2byteCharPtr(chr);
byte bits = 0;
if (width <= 0 || height <= 0) if (width <= 0 || height <= 0)
return 0; return 0;
if (minY) { if (minY) {
src += minY * _vm->_2byteWidth; src += ((minY * _vm->_2byteWidth) >> 3);
buffer += minY * pitch; buffer += (minY * pitch);
} }
enum ShadowMode { if (minX) {
kNone, src += (minX >> 3);
kNormalShadowMode, buffer += minX;
kCJKv7ShadowMode, }
kCJKv8ShadowMode
};
ShadowMode shadowMode = _vm->_useCJKMode ? (_vm->_game.version == 8 ? kCJKv8ShadowMode : kCJKv7ShadowMode) : kNone;
int shadowOffsetXTable[4] = { -1, 0, 1, 0 };
int shadowOffsetYTable[4] = { 0, 1, 0, 0 };
int shadowOffsetColorTable[4] = { 0, 0, 0, col };
byte bits = *src;
const byte *origSrc = src; const byte *origSrc = src;
for (int shadowIdx = (shadowMode == kCJKv8ShadowMode) ? 0 : (shadowMode == kCJKv7ShadowMode ? 2 : 3); shadowIdx < 4; shadowIdx++) {
int offX = MAX<int>(x + shadowOffsetXTable[shadowIdx], clipRect.left); for (int step = 0; step < _2byteSteps; ++step) {
int offY = MAX<int>(y + shadowOffsetYTable[shadowIdx], clipRect.top); int offX = MAX<int>(x + _2byteShadowXOffsetTable[step], clipRect.left);
byte drawColor = shadowOffsetColorTable[shadowIdx]; int offY = MAX<int>(y + _2byteShadowYOffsetTable[step], clipRect.top);
byte drawColor = _2byteColorTable[step];
src = origSrc; src = origSrc;
byte *dst = buffer + pitch * offY + offX; byte *dst = buffer + pitch * offY + offX;
@ -469,99 +480,14 @@ int NutRenderer::draw2byte(byte *buffer, Common::Rect &clipRect, int x, int y, i
continue; continue;
if ((i % 8) == 0) if ((i % 8) == 0)
bits = *src++; bits = *src++;
if (bits & revBitMask(i % 8)) { if (bits & revBitMask(i % 8))
if (shadowMode == kNormalShadowMode) {
dst[i + 1] = 0;
dst[pitch + i] = 0;
dst[pitch + i + 1] = 0;
}
dst[i] = drawColor; dst[i] = drawColor;
}
} }
dst += pitch; dst += pitch;
} }
} }
return width + 1;
return width + _spacing;
} }
/*
void NutRenderer::drawChar(const Graphics::Surface &s, byte c, int x, int y, byte color) {
// FIXME: This gets passed a const destination Surface. Intuitively this
// should never get written to. But sadly it does... For now we simply
// cast the const qualifier away.
byte *dst = (byte *)const_cast<void *>(s.getBasePtr(x, y));
const int width = MIN((int)_chars[c].width, s.w - x);
const int height = MIN((int)_chars[c].height, s.h - y);
const byte *src = unpackChar(c);
int srcPitch = _chars[c].width;
const int minX = x < 0 ? -x : 0;
const int minY = y < 0 ? -y : 0;
if (height <= 0 || width <= 0) {
return;
}
if (minY) {
src += minY * srcPitch;
dst += minY * s.pitch;
}
for (int ty = minY; ty < height; ty++) {
for (int tx = minX; tx < width; tx++) {
if (src[tx] != _chars[c].transparency) {
if (src[tx] == 1) {
dst[tx] = color;
} else {
dst[tx] = src[tx];
}
}
}
src += srcPitch;
dst += s.pitch;
}
}
void NutRenderer::draw2byte(const Graphics::Surface &s, int c, int x, int y, byte color) {
const int width = _vm->_2byteWidth;
const int height = MIN(_vm->_2byteHeight, s.h - y);
const byte *src = _vm->get2byteCharPtr(c);
byte bits = 0;
if (height <= 0 || width <= 0) {
return;
}
int shadowOffsetXTable[4] = {-1, 0, 1, 0};
int shadowOffsetYTable[4] = {0, 1, 0, 0};
int shadowOffsetColorTable[4] = {0, 0, 0, color};
int shadowIdx = (_vm->_useCJKMode && _vm->_game.id == GID_CMI) ? 0 : 3;
const byte *origSrc = src;
for (; shadowIdx < 4; shadowIdx++) {
int offX = x + shadowOffsetXTable[shadowIdx];
int offY = y + shadowOffsetYTable[shadowIdx];
byte drawColor = shadowOffsetColorTable[shadowIdx];
// FIXME: This gets passed a const destination Surface. Intuitively this
// should never get written to. But sadly it does... For now we simply
// cast the const qualifier away.
byte *dst = (byte *)const_cast<void *>(s.getBasePtr(offX, offY));
src = origSrc;
for (int ty = 0; ty < height; ty++) {
for (int tx = 0; tx < width; tx++) {
if ((tx & 7) == 0)
bits = *src++;
if (offX + tx < 0 || offX + tx >= s.w || offY + ty < 0)
continue;
if (bits & revBitMask(tx % 8)) {
dst[tx] = drawColor;
}
}
dst += s.pitch;
}
}
}*/
} // End of namespace Scumm } // End of namespace Scumm

View file

@ -41,12 +41,19 @@ protected:
int _numChars; int _numChars;
int _maxCharSize; int _maxCharSize;
int _fontHeight; int _fontHeight;
int _spacing;
byte *_charBuffer; byte *_charBuffer;
byte *_decodedData; byte *_decodedData;
byte *_paletteMap; byte *_paletteMap;
byte _bpp; byte _bpp;
byte _palette[16]; byte _palette[16];
const int8 *_2byteShadowXOffsetTable;
const int8 *_2byteShadowYOffsetTable;
uint8 *_2byteColorTable;
uint8 *_2byteMainColor;
const int _2byteSteps;
struct { struct {
uint16 width; uint16 width;
uint16 height; uint16 height;

View file

@ -1076,6 +1076,8 @@ ScummEngine_v7::ScummEngine_v7(OSystem *syst, const DetectorResult &dr)
clearSubtitleQueue(); clearSubtitleQueue();
_textV7 = NULL; _textV7 = NULL;
_defaultTextClipRect = Common::Rect(_screenWidth, _screenHeight);
_wrappedTextClipRect = Common::Rect(10, 10, _screenWidth - 10, _screenHeight - 10);
_game.features |= GF_NEW_COSTUMES; _game.features |= GF_NEW_COSTUMES;
} }
@ -2980,7 +2982,6 @@ void ScummEngine::restart() {
// subclass which is implemented using a memory buffer (i.e. no actual file is // subclass which is implemented using a memory buffer (i.e. no actual file is
// created). Then to restart we just have to load that pseudo save state. // created). Then to restart we just have to load that pseudo save state.
int i; int i;
// Reset some stuff // Reset some stuff

View file

@ -66,6 +66,9 @@ public:
protected: protected:
TextRenderer_v7 *_textV7; TextRenderer_v7 *_textV7;
Common::Rect _defaultTextClipRect;
Common::Rect _wrappedTextClipRect;
int _verbLineSpacing; int _verbLineSpacing;
bool _existLanguageFile; bool _existLanguageFile;
char *_languageBuffer; char *_languageBuffer;
@ -80,10 +83,18 @@ protected:
byte charset; byte charset;
byte text[256]; byte text[256];
bool actorSpeechMsg; bool actorSpeechMsg;
bool center;
bool wrap;
}; };
#else #else
struct SubtitleText : TextObject { struct SubtitleText : TextObject {
void clear() {
TextObject::clear();
actorSpeechMsg = center = wrap = false;
}
bool actorSpeechMsg; bool actorSpeechMsg;
bool center;
bool wrap;
}; };
#endif #endif
@ -94,7 +105,7 @@ protected:
public: public:
void processSubtitleQueue(); void processSubtitleQueue();
void addSubtitleToQueue(const byte *text, const Common::Point &pos, byte color, byte charset); void addSubtitleToQueue(const byte *text, const Common::Point &pos, byte color, byte charset, bool center, bool wrap);
void clearSubtitleQueue(); void clearSubtitleQueue();
void CHARSET_1() override; void CHARSET_1() override;
bool isSmushActive() { return _smushActive; } bool isSmushActive() { return _smushActive; }
@ -128,7 +139,7 @@ protected:
int getObjectIdFromOBIM(const byte *obim) override; int getObjectIdFromOBIM(const byte *obim) override;
void createTextRenderer(GlyphRenderer_v7 *gr) override; void createTextRenderer(GlyphRenderer_v7 *gr) override;
void enqueueText(const byte *text, int x, int y, byte color, byte charset, bool center, bool wrapped = false); void enqueueText(const byte *text, int x, int y, byte color, byte charset, bool center, bool wrap = false);
void drawBlastTexts() override; void drawBlastTexts() override;
void actorTalk(const byte *msg) override; void actorTalk(const byte *msg) override;
void translateText(const byte *text, byte *trans_buff) override; void translateText(const byte *text, byte *trans_buff) override;

View file

@ -627,26 +627,24 @@ void SmushPlayer::handleTextResource(uint32 subType, int32 subSize, Common::Seek
string2[0] = 0; string2[0] = 0;
} }
const char *str2 = str;
while (str[0] == '^') { while (str[0] == '^') {
switch (str[1]) { switch (str[1]) {
case 'f': case 'f':
{
fontId = str[3] - '0'; fontId = str[3] - '0';
str += 4; str += 4;
} break;
break;
case 'c': case 'c':
{
color = str[4] - '0' + 10 *(str[3] - '0'); color = str[4] - '0' + 10 *(str[3] - '0');
str += 5; str += 5;
}
break; break;
default: default:
error("invalid escape code in text string"); error("invalid escape code in text string");
} }
} }
str = str2;
if (_vm->_game.id == GID_CMI && string2[0] != 0)
str = string2;
// This is a hack from the original COMI CJK interpreter. Its purpose is to avoid // This is a hack from the original COMI CJK interpreter. Its purpose is to avoid
// ugly combinations of two byte characters (rendered with the respective special // ugly combinations of two byte characters (rendered with the respective special
// font) and standard one byte (NUT font) characters (see bug #11947). // font) and standard one byte (NUT font) characters (see bug #11947).
@ -658,60 +656,11 @@ void SmushPlayer::handleTextResource(uint32 subType, int32 subSize, Common::Seek
SmushFont *sf = getFont(fontId); SmushFont *sf = getFont(fontId);
assert(sf != NULL); assert(sf != NULL);
// The hack that used to be here to prevent bug #2220 is no longer necessary and
// The HACK that used to be here to prevent bug #2220 is no longer necessary and
// has been removed. The font renderer can handle all ^codes it encounters (font // has been removed. The font renderer can handle all ^codes it encounters (font
// changes on the fly will be ignored for Smush texts, since our code design does // changes on the fly will be ignored for Smush texts, since our code design does
// not permit it and the feature isn't used anyway). // not permit it and the feature isn't used anyway).
// HACK. This is to prevent bug #2220. In updated Win95 dig
// there is such line:
//
// ^f01^c001LEAD TESTER
// Chris Purvis
// ^f01
// ^f01^c001WINDOWS COMPATIBILITY
// Chip Hinnenberg
// ^f01^c001WINDOWS TESTING
// Jim Davison
// Lynn Selk
//
// i.e. formatting exists not in the first line only
// We just strip that off and assume that neither font
// nor font color was altered. Proper fix would be to feed
// drawString() with each line sequentally
/* char *string3 = NULL, *sptr2;
const char *sptr;
if (strchr(str, '^')) {
string3 = (char *)malloc(strlen(str) + 1);
for (sptr = str, sptr2 = string3; *sptr;) {
if (*sptr == '^') {
switch (sptr[1]) {
case 'f':
sptr += 4;
break;
case 'c':
sptr += 5;
break;
default:
error("invalid escape code in text string");
}
} else {
if (TextRenderer_v7::is2ByteCharacter(_vm->_language, *sptr))
*sptr2++ = *sptr++;
*sptr2++ = *sptr++;
}
}
*sptr2++ = *sptr++; // copy zero character
str = string3;
}*/
if (_vm->_game.id == GID_CMI && string2[0] != 0) {
str = string2;
}
// flags: // flags:
// bit 0 - center 0x01 // bit 0 - center 0x01
// bit 1 - not used (align right) 0x02 // bit 1 - not used (align right) 0x02
@ -743,7 +692,6 @@ void SmushPlayer::handleTextResource(uint32 subType, int32 subSize, Common::Seek
} }
free(string); free(string);
//free(string3);
} }
const char *SmushPlayer::getString(int id) { const char *SmushPlayer::getString(int id) {

View file

@ -677,12 +677,12 @@ void ScummEngine::CHARSET_1() {
} }
if (c == 13) { if (c == 13) {
#ifdef ENABLE_SCUMM_7_8 /*#ifdef ENABLE_SCUMM_7_8
if (_game.version >= 7 && subtitleLine != subtitleBuffer) { if (_game.version >= 7 && subtitleLine != subtitleBuffer) {
((ScummEngine_v7 *)this)->addSubtitleToQueue(subtitleBuffer, subtitlePos, _charsetColor, _charset->getCurID()); ((ScummEngine_v7 *)this)->addSubtitleToQueue(subtitleBuffer, subtitlePos, _charsetColor, _charset->getCurID());
subtitleLine = subtitleBuffer; subtitleLine = subtitleBuffer;
} }
#endif #endif*/
if (!newLine()) if (!newLine())
break; break;
continue; continue;
@ -762,12 +762,12 @@ void ScummEngine::CHARSET_1() {
if (_game.platform == Common::kPlatformFMTowns && (c == 0 || c == 2 || c == 3)) if (_game.platform == Common::kPlatformFMTowns && (c == 0 || c == 2 || c == 3))
memcpy(&_curStringRect, &_charset->_str, sizeof(Common::Rect)); memcpy(&_curStringRect, &_charset->_str, sizeof(Common::Rect));
#endif #endif
/*
#ifdef ENABLE_SCUMM_7_8 #ifdef ENABLE_SCUMM_7_8
if (_game.version >= 7 && subtitleLine != subtitleBuffer) { if (_game.version >= 7 && subtitleLine != subtitleBuffer) {
((ScummEngine_v7 *)this)->addSubtitleToQueue(subtitleBuffer, subtitlePos, _charsetColor, _charset->getCurID()); ((ScummEngine_v7 *)this)->addSubtitleToQueue(subtitleBuffer, subtitlePos, _charsetColor, _charset->getCurID());
} }
#endif #endif*/
} }
void ScummEngine::drawString(int a, const byte *msg) { void ScummEngine::drawString(int a, const byte *msg) {

View file

@ -33,7 +33,7 @@
namespace Scumm { namespace Scumm {
TextRenderer_v7::TextRenderer_v7(ScummEngine *vm, GlyphRenderer_v7 *gr) TextRenderer_v7::TextRenderer_v7(ScummEngine *vm, GlyphRenderer_v7 *gr)
: _gameId(vm->_game.id), _lang(vm->_language), _2byteCharWidth(vm->_2byteWidth), _screenWidth(vm->_screenWidth), _useCJKMode(vm->_useCJKMode), _lineBreakMarker(vm->_newLineCharacter), _gr(gr) { : _gameId(vm->_game.id), _lang(vm->_language), _2byteCharWidth(vm->_2byteWidth), _screenWidth(vm->_screenWidth), _useCJKMode(vm->_useCJKMode), _spacing(vm->_language != Common::JA_JPN ? 1 : 0), _lineBreakMarker(vm->_newLineCharacter), _gr(gr) {
} }
int TextRenderer_v7::getStringWidth(const char *str, uint numBytesMax) { int TextRenderer_v7::getStringWidth(const char *str, uint numBytesMax) {
@ -47,22 +47,25 @@ int TextRenderer_v7::getStringWidth(const char *str, uint numBytesMax) {
int font = _gr->setFont(-1); int font = _gr->setFont(-1);
while (*str && numBytesMax) { while (*str && numBytesMax) {
while (str[0] == '^') { if (*str == '^') {
switch (str[1]) { if (str[1] == 'f') {
case 'f':
_gr->setFont(str[3] - '0'); _gr->setFont(str[3] - '0');
str += 4; str += 4;
break; numBytesMax -= 4;
case 'c': continue;
} else if (str[1] == 'c') {
str += 5; str += 5;
break; numBytesMax -= 5;
default: continue;
error("CharsetRenderer::getStringWidth(): Invalid escape code in text string"); } else if (str[1] == 'l') {
str += 2;
numBytesMax -= 2;
continue;
} }
} }
if (is2ByteCharacter(_lang, *str)) { if (is2ByteCharacter(_lang, *str)) {
width += _2byteCharWidth + (_lang != Common::JA_JPN ? 1 : 0); width += _2byteCharWidth + _spacing;
++str; ++str;
--numBytesMax; --numBytesMax;
} else if (*str == '\n') { } else if (*str == '\n') {
@ -91,17 +94,20 @@ int TextRenderer_v7::getStringHeight(const char *str, uint numBytesMax) {
int font = _gr->setFont(-1); int font = _gr->setFont(-1);
while (*str && numBytesMax) { while (*str && numBytesMax) {
while (str[0] == '^') { if (*str == '^') {
switch (str[1]) { if (str[1] == 'f') {
case 'f':
_gr->setFont(str[3] - '0'); _gr->setFont(str[3] - '0');
str += 4; str += 4;
break; numBytesMax -= 4;
case 'c': continue;
} else if (str[1] == 'c') {
str += 5; str += 5;
break; numBytesMax -= 5;
default: continue;
error("CharsetRenderer::getStringWidth(): Invalid escape code in text string"); } else if (str[1] == 'l') {
str += 2;
numBytesMax -= 2;
continue;
} }
} }
@ -131,18 +137,21 @@ void TextRenderer_v7::drawSubstring(const char *str, uint numBytesMax, byte *buf
} }
} else { } else {
for (int i = 0; str[i] != 0 && numBytesMax; ++i) { for (int i = 0; str[i] != 0 && numBytesMax; ++i) {
while (str[i] == '^') { if (str[i] == '^') {
switch (str[i + 1]) { if (str[i + 1] == 'f') {
case 'f':
_gr->setFont(str[i + 3] - '0'); _gr->setFont(str[i + 3] - '0');
i += 3;
numBytesMax -= 4;
continue;
} else if (str[i + 1] == 'c') {
col = str[i + 4] - '0' + 10 *(str[i + 3] - '0');
i += 4; i += 4;
break; numBytesMax -= 5;
case 'c': continue;
col = str[4] - '0' + 10 *(str[3] - '0'); } else if (str[i + 1] == 'l') {
i += 5; i++;
break; numBytesMax -= 2;
default: continue;
error("CharsetRenderer::getStringWidth(): Invalid escape code in text string");
} }
} }
@ -197,7 +206,7 @@ void TextRenderer_v7::drawString(const char *str, byte *buffer, Common::Rect &cl
_gr->setFont(font); _gr->setFont(font);
clipRect.left = center ? x - maxWidth : x; clipRect.left = center ? x - maxWidth / 2: x;
clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth); clipRect.right = MIN<int>(clipRect.right, clipRect.left + maxWidth);
clipRect.top = y2; clipRect.top = y2;
clipRect.bottom = y; clipRect.bottom = y;
@ -334,7 +343,7 @@ void ScummEngine_v7::createTextRenderer(GlyphRenderer_v7 *gr) {
_textV7 = new TextRenderer_v7(this, gr); _textV7 = new TextRenderer_v7(this, gr);
} }
void ScummEngine_v7::enqueueText(const byte *text, int x, int y, byte color, byte charset, bool center, bool wrapped) { void ScummEngine_v7::enqueueText(const byte *text, int x, int y, byte color, byte charset, bool center, bool wrap) {
assert(_blastTextQueuePos + 1 <= ARRAYSIZE(_blastTextQueue)); assert(_blastTextQueuePos + 1 <= ARRAYSIZE(_blastTextQueue));
if (_useCJKMode) { if (_useCJKMode) {
@ -347,7 +356,9 @@ void ScummEngine_v7::enqueueText(const byte *text, int x, int y, byte color, byt
BlastText &bt = _blastTextQueue[_blastTextQueuePos]; BlastText &bt = _blastTextQueue[_blastTextQueuePos];
convertMessageToString(text, bt.text, sizeof(bt.text)); convertMessageToString(text, bt.text, sizeof(bt.text));
if (!*bt.text)
// The original DIG interpreter expressly checks for " " strings here. And the game also sends these quite frequently...
if (!bt.text[0] || (bt.text[0] == (byte)' ' && !bt.text[1]))
return; return;
_blastTextQueuePos++; _blastTextQueuePos++;
@ -356,13 +367,11 @@ void ScummEngine_v7::enqueueText(const byte *text, int x, int y, byte color, byt
bt.color = color; bt.color = color;
bt.charset = charset; bt.charset = charset;
bt.center = center; bt.center = center;
bt.wrap = wrapped; bt.wrap = wrap;
} }
void ScummEngine_v7::drawBlastTexts() { void ScummEngine_v7::drawBlastTexts() {
VirtScreen *vs = &_virtscr[kMainVirtScreen]; VirtScreen *vs = &_virtscr[kMainVirtScreen];
Common::Rect _defaultTextClipRect = Common::Rect(vs->w, vs->h);
Common::Rect _wrappedTextClipRect = _game.id == GID_CMI ? Common::Rect(10, 10, 630, 470) : _defaultTextClipRect;
for (int i = 0; i < _blastTextQueuePos; i++) { for (int i = 0; i < _blastTextQueuePos; i++) {
BlastText &bt = _blastTextQueue[i]; BlastText &bt = _blastTextQueue[i];
@ -371,10 +380,10 @@ void ScummEngine_v7::drawBlastTexts() {
if (bt.wrap) { if (bt.wrap) {
bt.rect = _wrappedTextClipRect; bt.rect = _wrappedTextClipRect;
_textV7->drawStringWrap((const char*)bt.text, (byte*)vs->getPixels(0, 0), bt.rect, bt.xpos, bt.ypos, vs->pitch, bt.color, bt.center); _textV7->drawStringWrap((const char*)bt.text, (byte*)vs->getBasePtr(0, 0), bt.rect, bt.xpos, bt.ypos, vs->pitch, bt.color, bt.center);
} else { } else {
bt.rect = _defaultTextClipRect; bt.rect = _defaultTextClipRect;
_textV7->drawString((const char*)bt.text, (byte*)vs->getPixels(0, 0), bt.rect, bt.xpos, bt.ypos, vs->pitch, bt.color, bt.center); _textV7->drawString((const char*)bt.text, (byte*)vs->getBasePtr(0, 0), bt.rect, bt.xpos, bt.ypos, vs->pitch, bt.color, bt.center);
} }
markRectAsDirty(vs->number, bt.rect); markRectAsDirty(vs->number, bt.rect);
@ -400,11 +409,11 @@ void ScummEngine_v7::processSubtitleQueue() {
if (!st->actorSpeechMsg && (!ConfMan.getBool("subtitles") || VAR(VAR_VOICE_MODE) == 0)) if (!st->actorSpeechMsg && (!ConfMan.getBool("subtitles") || VAR(VAR_VOICE_MODE) == 0))
// no subtitles and there's a speech variant of the message, don't display the text // no subtitles and there's a speech variant of the message, don't display the text
continue; continue;
enqueueText(st->text, st->xpos, st->ypos, st->color, st->charset, false); enqueueText(st->text, st->xpos, st->ypos, st->color, st->charset, st->center, st->wrap);
} }
} }
void ScummEngine_v7::addSubtitleToQueue(const byte *text, const Common::Point &pos, byte color, byte charset) { void ScummEngine_v7::addSubtitleToQueue(const byte *text, const Common::Point &pos, byte color, byte charset, bool center, bool wrap) {
if (text[0] && strcmp((const char *)text, " ") != 0) { if (text[0] && strcmp((const char *)text, " ") != 0) {
assert(_subtitleQueuePos < ARRAYSIZE(_subtitleQueue)); assert(_subtitleQueuePos < ARRAYSIZE(_subtitleQueue));
SubtitleText *st = &_subtitleQueue[_subtitleQueuePos]; SubtitleText *st = &_subtitleQueue[_subtitleQueuePos];
@ -420,6 +429,8 @@ void ScummEngine_v7::addSubtitleToQueue(const byte *text, const Common::Point &p
st->color = color; st->color = color;
st->charset = charset; st->charset = charset;
st->actorSpeechMsg = _haveActorSpeechMsg; st->actorSpeechMsg = _haveActorSpeechMsg;
st->center = center;
st->wrap = wrap;
++_subtitleQueuePos; ++_subtitleQueuePos;
} }
} }
@ -435,10 +446,6 @@ void ScummEngine_v7::CHARSET_1() {
return; return;
} }
byte subtitleBuffer[2048];
byte *subtitleLine = subtitleBuffer;
Common::Point subtitlePos;
processSubtitleQueue(); processSubtitleQueue();
if (!_haveMsg) if (!_haveMsg)
@ -452,21 +459,20 @@ void ScummEngine_v7::CHARSET_1() {
if (a && _string[0].overhead) { if (a && _string[0].overhead) {
int s; int s;
_string[0].xpos = a->getPos().x - _virtscr[kMainVirtScreen].xstart; _string[0].xpos = a->getPos().x + _screenWidth / 2 - camera._cur.x;
s = a->_scalex * a->_talkPosX / 255; s = a->_scalex * a->_talkPosX / 255;
_string[0].xpos += (a->_talkPosX - s) / 2 + s; _string[0].xpos += (a->_talkPosX - s) / 2 + s;
_string[0].ypos = a->getPos().y - a->getElevation() - _screenTop; int yyy1 = a->getPos().y;
int yyy2 = a->getElevation();
_string[0].ypos = a->getPos().y - a->getElevation() + _screenHeight / 2 - camera._cur.y;
s = a->_scaley * a->_talkPosY / 255; s = a->_scaley * a->_talkPosY / 255;
_string[0].ypos += (a->_talkPosY - s) / 2 + s; _string[0].ypos += (a->_talkPosY - s) / 2 + s;
} }
_charset->setColor(_charsetColor); _charset->setColor(_charsetColor);
_charset->setCurID(_string[0].charset);
if (a && a->_charset)
_charset->setCurID(a->_charset);
else
_charset->setCurID(_string[0].charset);
if (_talkDelay) if (_talkDelay)
return; return;
@ -482,110 +488,19 @@ void ScummEngine_v7::CHARSET_1() {
a->runActorTalkScript(a->_talkStartFrame); a->runActorTalkScript(a->_talkStartFrame);
} }
if (!_keepText) { if (!_keepText)
clearSubtitleQueue(); clearSubtitleQueue();
_nextLeft = _string[0].xpos;
_nextTop = _string[0].ypos + _screenTop;
}
_charset->_disableOffsX = _charset->_firstChar = !_keepText;
_talkDelay = VAR(VAR_DEFAULT_TALK_DELAY); _talkDelay = VAR(VAR_DEFAULT_TALK_DELAY);
for (int i = _charsetBufPos; _charsetBuffer[i]; ++i) { int newPos = _charsetBufPos;
while (_charsetBuffer[newPos++])
_talkDelay += VAR(VAR_CHARINC); _talkDelay += VAR(VAR_CHARINC);
}
if (_string[0].wrapping) { Common::Point subtitlePos(_string[0].xpos, _string[0].ypos);
_charset->addLinebreaks(0, _charsetBuffer, _charsetBufPos, _screenWidth - 20); addSubtitleToQueue(_charsetBuffer + _charsetBufPos, subtitlePos, _charsetColor, _charset->getCurID(), _string[0].center, _string[0].wrapping);
_charsetBufPos = newPos;
struct { int pos, w; } substring[10]; _haveMsg = VAR(VAR_HAVE_MSG) = (_game.version == 8 && _string[0].no_talk_anim) ? 2 : 1;
int count = 0;
int maxLineWidth = 0;
int lastPos = 0;
int code = 0;
while (handleNextCharsetCode(a, &code)) {
if (code == 13 || code == 0) {
*subtitleLine++ = '\0';
assert(count < 10);
substring[count].w = _charset->getStringWidth(0, subtitleBuffer + lastPos);
if (maxLineWidth < substring[count].w) {
maxLineWidth = substring[count].w;
}
substring[count].pos = lastPos;
++count;
lastPos = subtitleLine - subtitleBuffer;
} else {
*subtitleLine++ = code;
*subtitleLine = '\0';
}
if (code == 0) {
break;
}
}
int h = count * _charset->getFontHeight();
h += _charset->getFontHeight() / 2;
subtitlePos.y = _string[0].ypos;
if (subtitlePos.y + h > _screenHeight - 10) {
subtitlePos.y = _screenHeight - 10 - h;
}
if (subtitlePos.y < 10) {
subtitlePos.y = 10;
}
for (int i = 0; i < count; ++i) {
subtitlePos.x = _string[0].xpos;
if (_string[0].center) {
if (subtitlePos.x + maxLineWidth / 2 > _screenWidth - 10) {
subtitlePos.x = _screenWidth - 10 - maxLineWidth / 2;
}
if (subtitlePos.x - maxLineWidth / 2 < 10) {
subtitlePos.x = 10 + maxLineWidth / 2;
}
subtitlePos.x -= substring[i].w / 2;
} else {
if (subtitlePos.x + maxLineWidth > _screenWidth - 10) {
subtitlePos.x = _screenWidth - 10 - maxLineWidth;
}
if (subtitlePos.x - maxLineWidth < 10) {
subtitlePos.x = 10;
}
}
if (subtitlePos.y < _screenHeight - 10) {
addSubtitleToQueue(subtitleBuffer + substring[i].pos, subtitlePos, _charsetColor, _charset->getCurID());
}
subtitlePos.y += _charset->getFontHeight();
}
} else {
int code = 0;
subtitlePos.y = _string[0].ypos;
if (subtitlePos.y < 10) {
subtitlePos.y = 10;
}
while (handleNextCharsetCode(a, &code)) {
if (code == 13 || code == 0) {
subtitlePos.x = _string[0].xpos;
if (_string[0].center) {
subtitlePos.x -= _charset->getStringWidth(0, subtitleBuffer) / 2;
}
if (subtitlePos.x < 10) {
subtitlePos.x = 10;
}
if (subtitlePos.y < _screenHeight - 10) {
addSubtitleToQueue(subtitleBuffer, subtitlePos, _charsetColor, _charset->getCurID());
subtitlePos.y += _charset->getFontHeight();
}
subtitleLine = subtitleBuffer;
} else {
*subtitleLine++ = code;
}
*subtitleLine = '\0';
if (code == 0) {
break;
}
}
}
_haveMsg = (_game.version == 8) ? 2 : 1;
_keepText = false; _keepText = false;
_string[0] = saveStr; _string[0] = saveStr;
} }

View file

@ -61,6 +61,7 @@ private:
const Common::Language _lang; const Common::Language _lang;
const byte _gameId; const byte _gameId;
const bool _useCJKMode; const bool _useCJKMode;
const int _spacing;
const byte _2byteCharWidth; const byte _2byteCharWidth;
const byte _lineBreakMarker; const byte _lineBreakMarker;
const uint16 _screenWidth; const uint16 _screenWidth;