Files
scummvm-cursorfix/engines/wintermute/base/font/base_font_bitmap.cpp
2026-02-02 04:50:13 +01:00

629 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* This file is based on WME Lite.
* http://dead-code.org/redir.php?target=wmelite
* Copyright (c) 2011 Jan Nedoma
*/
#include "engines/wintermute/base/font/base_font_bitmap.h"
#include "engines/wintermute/utils/string_util.h"
#include "engines/wintermute/base/base_engine.h"
#include "engines/wintermute/base/base_parser.h"
#include "engines/wintermute/base/base_frame.h"
#include "engines/wintermute/base/gfx/base_surface.h"
#include "engines/wintermute/base/gfx/base_renderer.h"
#include "engines/wintermute/base/base_game.h"
#include "engines/wintermute/base/base_sub_frame.h"
#include "engines/wintermute/base/base_frame.h"
#include "engines/wintermute/base/base_sprite.h"
#include "engines/wintermute/base/base_file_manager.h"
#include "engines/wintermute/platform_osystem.h"
#include "engines/wintermute/dcgf.h"
namespace Wintermute {
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
IMPLEMENT_PERSISTENT(BaseFontBitmap, false)
//////////////////////////////////////////////////////////////////////
BaseFontBitmap::BaseFontBitmap(BaseGame *inGame) : BaseFont(inGame) {
_subframe = nullptr;
_sprite = nullptr;
_widthsFrame = 0;
memset(_widths, 0, NUM_CHARACTERS);
_tileWidth = _tileHeight = _numColumns = 0;
_fontextFix = false;
_freezable = false;
_wholeCell = false;
}
//////////////////////////////////////////////////////////////////////
BaseFontBitmap::~BaseFontBitmap() {
SAFE_DELETE(_subframe);
SAFE_DELETE(_sprite);
}
//////////////////////////////////////////////////////////////////////
void BaseFontBitmap::drawText(const byte *text, int x, int y, int width, TTextAlign align, int maxHeight, int maxLength) {
textHeightDraw(text, x, y, width, align, true, maxHeight, maxLength);
}
//////////////////////////////////////////////////////////////////////
int BaseFontBitmap::getTextHeight(const byte *text, int width) {
return textHeightDraw(text, 0, 0, width, TAL_LEFT, false);
}
//////////////////////////////////////////////////////////////////////
int BaseFontBitmap::getTextWidth(const byte *text, int maxLength) {
AnsiString str;
if (_game->_textEncoding == TEXT_UTF8) {
WideString wstr = StringUtil::utf8ToWide(Utf8String((const char *)text));
str = StringUtil::wideToAnsi(wstr);
} else {
str = AnsiString((const char *)text);
}
if (maxLength >= 0 && (int)str.size() > maxLength) {
str = Common::String(str.c_str(), (uint32)maxLength);
}
//str.substr(0, maxLength); // TODO: Remove
int textWidth = 0;
for (int i = 0; (uint32)i < str.size(); i++) {
textWidth += getCharWidth((byte)str[i]);
}
return textWidth;
}
//////////////////////////////////////////////////////////////////////
int BaseFontBitmap::textHeightDraw(const byte *text, int x, int y, int width, TTextAlign align, bool draw, int maxHeight, int maxLength) {
if (maxLength == 0) {
return 0;
}
if (text == nullptr || text[0] == '\0') {
return _tileHeight;
}
AnsiString str;
if (_game->_textEncoding == TEXT_UTF8) {
WideString wstr = StringUtil::utf8ToWide(Utf8String((const char *)text));
str = StringUtil::wideToAnsi(wstr);
} else {
str = AnsiString((const char *)text);
}
if (str.empty()) {
return 0;
}
int lineLength = 0;
int realLength = 0;
int numLines = 0;
int i;
int index = -1;
int start = 0;
int end = 0;
int last_end = 0;
bool done = false;
bool newLine = false;
bool longLine = false;
#ifdef ENABLE_FOXTAIL
bool minimizeSpacing = BaseEngine::instance().isFoxTail();
#endif
if (draw) {
_game->_renderer->startSpriteBatch();
}
while (!done) {
if (maxHeight > 0 && (numLines + 1) * _tileHeight > maxHeight) {
if (draw) {
_game->_renderer->endSpriteBatch();
}
return numLines * _tileHeight;
}
index++;
if (str[index] == ' ' && (maxHeight < 0 || maxHeight / _tileHeight > 1)) {
end = index - 1;
realLength = lineLength;
}
if (str[index] == '\n') {
end = index - 1;
realLength = lineLength;
newLine = true;
}
if (lineLength + getCharWidth(str[index]) > width && last_end == end) {
end = index - 1;
realLength = lineLength;
newLine = true;
longLine = true;
}
if ((int)str.size() == (index + 1) || (maxLength >= 0 && index == maxLength - 1)) {
done = true;
if (!newLine) {
end = index;
lineLength += getCharWidth(str[index]);
realLength = lineLength;
}
} else {
lineLength += getCharWidth(str[index]);
}
if ((lineLength > width) || done || newLine) {
if (end < 0) {
done = true;
}
int startX = x;
switch (align) {
case TAL_CENTER:
startX = x + (width - realLength) / 2;
break;
case TAL_RIGHT:
startX = x + width - realLength;
break;
case TAL_LEFT:
startX = x;
break;
default:
break;
}
for (i = start; i < end + 1; i++) {
if (draw) {
drawChar(str[i], startX, y);
}
startX += getCharWidth(str[i]);
}
y += _tileHeight;
#ifdef ENABLE_FOXTAIL
if (minimizeSpacing) {
y -= 3;
}
#endif
last_end = end;
if (longLine) {
end--;
}
start = end + 2;
index = end + 1;
lineLength = 0;
newLine = false;
longLine = false;
numLines++;
}
}
if (draw) {
_game->_renderer->endSpriteBatch();
}
return numLines * _tileHeight;
}
//////////////////////////////////////////////////////////////////////
void BaseFontBitmap::drawChar(byte c, int x, int y) {
if (_fontextFix) {
c--;
}
int row, col;
row = c / _numColumns;
col = c % _numColumns;
Common::Rect32 rect;
/* l t r b */
int tileWidth;
if (_wholeCell) {
tileWidth = _tileWidth;
} else {
tileWidth = _widths[c];
}
BasePlatform::setRect(&rect, col * _tileWidth, row * _tileHeight, col * _tileWidth + tileWidth, (row + 1) * _tileHeight);
bool handled = false;
if (_sprite) {
_sprite->getCurrentFrame();
if (_sprite->_currentFrame >= 0 && _sprite->_currentFrame < _sprite->_frames.getSize() && _sprite->_frames[_sprite->_currentFrame]) {
if (_sprite->_frames[_sprite->_currentFrame]->_subframes.getSize() > 0) {
_sprite->_frames[_sprite->_currentFrame]->_subframes[0]->_surface->displayTrans(x, y, rect);
}
handled = true;
}
}
if (!handled && _subframe) {
_subframe->_surface->displayTrans(x, y, rect, _subframe->_alpha);
}
}
//////////////////////////////////////////////////////////////////////
bool BaseFontBitmap::loadFile(const char *filename) {
char *buffer = (char *)_game->_fileManager->readWholeFile(filename);
if (buffer == nullptr) {
_game->LOG(0, "BaseFontBitmap::loadFile failed for file '%s'", filename);
return STATUS_FAILED;
}
bool ret;
setFilename(filename);
if (DID_FAIL(ret = loadBuffer(buffer))) {
_game->LOG(0, "Error parsing FONT file '%s'", filename);
}
delete[] buffer;
return ret;
}
TOKEN_DEF_START
TOKEN_DEF(FONTEXT_FIX)
TOKEN_DEF(FONT)
TOKEN_DEF(IMAGE)
TOKEN_DEF(TRANSPARENT)
TOKEN_DEF(COLUMNS)
TOKEN_DEF(TILE_WIDTH)
TOKEN_DEF(TILE_HEIGHT)
TOKEN_DEF(DEFAULT_WIDTH)
TOKEN_DEF(WIDTHS)
TOKEN_DEF(AUTO_WIDTH)
TOKEN_DEF(SPACE_WIDTH)
TOKEN_DEF(EXPAND_WIDTH)
TOKEN_DEF(EDITOR_PROPERTY)
TOKEN_DEF(SPRITE)
TOKEN_DEF(WIDTHS_FRAME)
TOKEN_DEF(PAINT_WHOLE_CELL)
#ifdef ENABLE_FOXTAIL
TOKEN_DEF(COLOR)
#endif
TOKEN_DEF_END
//////////////////////////////////////////////////////////////////////
bool BaseFontBitmap::loadBuffer(char *buffer) {
TOKEN_TABLE_START(commands)
TOKEN_TABLE(FONTEXT_FIX)
TOKEN_TABLE(FONT)
TOKEN_TABLE(IMAGE)
TOKEN_TABLE(TRANSPARENT)
TOKEN_TABLE(COLUMNS)
TOKEN_TABLE(TILE_WIDTH)
TOKEN_TABLE(TILE_HEIGHT)
TOKEN_TABLE(DEFAULT_WIDTH)
TOKEN_TABLE(WIDTHS)
TOKEN_TABLE(AUTO_WIDTH)
TOKEN_TABLE(SPACE_WIDTH)
TOKEN_TABLE(EXPAND_WIDTH)
TOKEN_TABLE(EDITOR_PROPERTY)
TOKEN_TABLE(SPRITE)
TOKEN_TABLE(WIDTHS_FRAME)
TOKEN_TABLE(PAINT_WHOLE_CELL)
#ifdef ENABLE_FOXTAIL
TOKEN_TABLE(COLOR)
#endif
TOKEN_TABLE_END
char *params;
int cmd;
BaseParser parser(_game);
if (parser.getCommand(&buffer, commands, &params) != TOKEN_FONT) {
_game->LOG(0, "'FONT' keyword expected.");
return STATUS_FAILED;
}
buffer = params;
int widths[300];
int num = 0, defaultWidth = 8;
int lastWidth = 0;
int i;
int r = 255, g = 255, b = 255;
bool customTrans = false;
#ifdef ENABLE_FOXTAIL
int ar = 255, ag = 255, ab = 255;
bool customAlpha = false;
#endif
char *surfaceFile = nullptr;
char *spriteFile = nullptr;
bool autoWidth = false;
int spaceWidth = 0;
int expandWidth = 0;
while ((cmd = parser.getCommand(&buffer, commands, &params)) > 0) {
switch (cmd) {
case TOKEN_IMAGE:
surfaceFile = params;
break;
case TOKEN_SPRITE:
spriteFile = params;
break;
case TOKEN_TRANSPARENT:
parser.scanStr(params, "%d,%d,%d", &r, &g, &b);
customTrans = true;
break;
#ifdef ENABLE_FOXTAIL
case TOKEN_COLOR:
parser.scanStr(params, "%d,%d,%d", &ar, &ag, &ab);
customAlpha = true;
break;
#endif
case TOKEN_WIDTHS:
parser.scanStr(params, "%D", widths, &num);
for (i = 0; lastWidth < NUM_CHARACTERS && num > 0; lastWidth++, num--, i++) {
_widths[lastWidth] = (byte)widths[i];
}
break;
case TOKEN_DEFAULT_WIDTH:
parser.scanStr(params, "%d", &defaultWidth);
break;
case TOKEN_WIDTHS_FRAME:
parser.scanStr(params, "%d", &_widthsFrame);
break;
case TOKEN_COLUMNS:
parser.scanStr(params, "%d", &_numColumns);
break;
case TOKEN_TILE_WIDTH:
parser.scanStr(params, "%d", &_tileWidth);
break;
case TOKEN_TILE_HEIGHT:
parser.scanStr(params, "%d", &_tileHeight);
break;
case TOKEN_AUTO_WIDTH:
parser.scanStr(params, "%b", &autoWidth);
break;
case TOKEN_FONTEXT_FIX:
parser.scanStr(params, "%b", &_fontextFix);
break;
case TOKEN_PAINT_WHOLE_CELL:
parser.scanStr(params, "%b", &_wholeCell);
break;
case TOKEN_SPACE_WIDTH:
parser.scanStr(params, "%d", &spaceWidth);
break;
case TOKEN_EXPAND_WIDTH:
parser.scanStr(params, "%d", &expandWidth);
break;
case TOKEN_EDITOR_PROPERTY:
parseEditorProperty(params, false);
break;
default:
break;
}
}
if (cmd == PARSERR_TOKENNOTFOUND) {
_game->LOG(0, "Syntax error in FONT definition");
return STATUS_FAILED;
}
if (spriteFile != nullptr) {
SAFE_DELETE(_sprite);
_sprite = new BaseSprite(_game, this);
if (!_sprite || DID_FAIL(_sprite->loadFile(spriteFile))) {
SAFE_DELETE(_sprite);
}
}
if (surfaceFile != nullptr && !_sprite) {
_subframe = new BaseSubFrame(_game);
if (customTrans) {
_subframe->setSurface(surfaceFile, false, r, g, b);
} else {
_subframe->setSurface(surfaceFile);
}
#ifdef ENABLE_FOXTAIL
if (customAlpha) {
_subframe->_alpha = BYTETORGBA(ar, ag, ab, 255);
}
#endif
}
if (((_subframe == nullptr || _subframe->_surface == nullptr) && _sprite == nullptr) || _numColumns == 0 || _tileWidth == 0 || _tileHeight == 0) {
_game->LOG(0, "Incomplete font definition");
return STATUS_FAILED;
}
if (autoWidth) {
// calculate characters width
getWidths();
// do we need to modify widths?
if (expandWidth != 0) {
for (i = 0; i < NUM_CHARACTERS; i++) {
int newWidth = (int)_widths[i] + expandWidth;
if (newWidth < 0) {
newWidth = 0;
}
_widths[i] = (byte)newWidth;
}
}
// handle space character
uint32 spaceChar = ' ';
if (_fontextFix) {
spaceChar--;
}
if (spaceWidth != 0) {
_widths[spaceChar] = spaceWidth;
} else {
if (_widths[spaceChar] == expandWidth || _widths[spaceChar] == 0) {
_widths[spaceChar] = (_widths[(uint32)'m'] + _widths[(uint32)'i']) / 2;
}
}
} else {
for (i = lastWidth; i < NUM_CHARACTERS; i++) {
_widths[i] = defaultWidth;
}
}
#ifdef ENABLE_FOXTAIL
if (BaseEngine::instance().isFoxTail()) {
for (i = lastWidth; i < NUM_CHARACTERS; i++) {
_widths[i]--;
}
}
#endif
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
bool BaseFontBitmap::persist(BasePersistenceManager *persistMgr) {
BaseFont::persist(persistMgr);
persistMgr->transferSint32(TMEMBER(_numColumns));
persistMgr->transferPtr(TMEMBER_PTR(_subframe));
persistMgr->transferSint32(TMEMBER(_tileHeight));
persistMgr->transferSint32(TMEMBER(_tileWidth));
persistMgr->transferPtr(TMEMBER_PTR(_sprite));
persistMgr->transferSint32(TMEMBER(_widthsFrame));
if (persistMgr->getIsSaving()) {
persistMgr->putBytes(_widths, sizeof(_widths));
} else {
persistMgr->getBytes(_widths, sizeof(_widths));
}
persistMgr->transferBool(TMEMBER(_fontextFix));
persistMgr->transferBool(TMEMBER(_wholeCell));
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
int BaseFontBitmap::getCharWidth(byte index) {
if (_fontextFix) {
index--;
}
return _widths[index];
}
//////////////////////////////////////////////////////////////////////////
bool BaseFontBitmap::getWidths() {
BaseSurface *surf = nullptr;
if (_sprite) {
if (_widthsFrame >= 0 && _widthsFrame < _sprite->_frames.getSize()) {
if (_sprite->_frames[_widthsFrame] && _sprite->_frames[_widthsFrame]->_subframes.getSize() > 0) {
surf = _sprite->_frames[_widthsFrame]->_subframes[0]->_surface;
}
}
}
if (surf == nullptr && _subframe) {
surf = _subframe->_surface;
}
if (!surf || DID_FAIL(surf->startPixelOp())) {
return STATUS_FAILED;
}
for (int i = 0; i < NUM_CHARACTERS; i++) {
int xxx = (i % _numColumns) * _tileWidth;
int yyy = (i / _numColumns) * _tileHeight;
int minCol = -1;
for (int row = 0; row < _tileHeight; row++) {
for (int col = _tileWidth - 1; col >= minCol + 1; col--) {
if (xxx + col < 0 || xxx + col >= surf->getWidth() || yyy + row < 0 || yyy + row >= surf->getHeight()) {
continue;
}
if (!surf->isTransparentAtLite(xxx + col, yyy + row)) {
//min_col = col;
minCol = MAX(col, minCol);
break;
}
}
if (minCol == _tileWidth - 1) {
break;
}
}
_widths[i] = minCol + 1;
}
surf->endPixelOp();
/*
_game->LOG(0, "----- %s ------", _filename);
for(int j=0; j<16; j++)
{
_game->LOG(0, "%02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d %02d", _widths[j*16+0], _widths[j*16+1], _widths[j*16+2], _widths[j*16+3], _widths[j*16+4], _widths[j*16+5], _widths[j*16+6], _widths[j*16+7], _widths[j*16+8], _widths[j*16+9], _widths[j*16+10], _widths[j*16+11], _widths[j*16+12], _widths[j*16+13], _widths[j*16+14], _widths[j*16+15]);
}
*/
return STATUS_OK;
}
//////////////////////////////////////////////////////////////////////////
int BaseFontBitmap::getLetterHeight() {
return _tileHeight;
}
} // End of namespace Wintermute