/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* * 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_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" 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() { delete _subframe; delete _sprite; _subframe = nullptr; _sprite = nullptr; } ////////////////////////////////////////////////////////////////////// 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 (_gameRef->_textEncoding == TEXT_UTF8) { WideString wstr = StringUtil::utf8ToWide(Utf8String((const char *)text)); str = StringUtil::wideToAnsi(wstr); } else { str = AnsiString((const char *)text); } if (maxLength >= 0 && str.size() > (uint32)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 (_gameRef->_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; if (draw) { _gameRef->_renderer->startSpriteBatch(); } while (!done) { if (maxHeight > 0 && (numLines + 1)*_tileHeight > maxHeight) { if (draw) { _gameRef->_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; 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: error("BaseFontBitmap::TextHeightDraw - Unhandled enum"); break; } for (i = start; i < end + 1; i++) { if (draw) { drawChar(str[i], startX, y); } startX += getCharWidth(str[i]); } y += _tileHeight; last_end = end; if (longLine) { end--; } start = end + 2; index = end + 1; lineLength = 0; newLine = false; longLine = false; numLines++; } } if (draw) { _gameRef->_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; Rect32 rect; /* l t r b */ int tileWidth; if (_wholeCell) { tileWidth = _tileWidth; } else { tileWidth = _widths[c]; } rect.setRect(col * _tileWidth, row * _tileHeight, col * _tileWidth + tileWidth, (row + 1) * _tileHeight); bool handled = false; if (_sprite) { _sprite->getCurrentFrame(); if (_sprite->_currentFrame >= 0 && _sprite->_currentFrame < (int32)_sprite->_frames.size() && _sprite->_frames[_sprite->_currentFrame]) { if (_sprite->_frames[_sprite->_currentFrame]->_subframes.size() > 0) { _sprite->_frames[_sprite->_currentFrame]->_subframes[0]->_surface->displayTrans(x, y, rect); } handled = true; } } if (!handled && _subframe) { _subframe->_surface->displayTrans(x, y, rect); } } ////////////////////////////////////////////////////////////////////// bool BaseFontBitmap::loadFile(const Common::String &filename) { char *buffer = (char *)BaseFileManager::getEngineInstance()->readWholeFile(filename); if (buffer == nullptr) { _gameRef->LOG(0, "BaseFontBitmap::LoadFile failed for file '%s'", filename.c_str()); return STATUS_FAILED; } bool ret; setFilename(filename.c_str()); if (DID_FAIL(ret = loadBuffer(buffer))) { _gameRef->LOG(0, "Error parsing FONT file '%s'", filename.c_str()); } 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) 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) TOKEN_TABLE_END char *params; int cmd; BaseParser parser; if (parser.getCommand(&buffer, commands, ¶ms) != TOKEN_FONT) { _gameRef->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 custoTrans = false; char *surfaceFile = nullptr; char *spriteFile = nullptr; bool autoWidth = false; int spaceWidth = 0; int expandWidth = 0; while ((cmd = parser.getCommand(&buffer, commands, ¶ms)) > 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); custoTrans = true; break; 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) { _gameRef->LOG(0, "Syntax error in FONT definition"); return STATUS_FAILED; } if (spriteFile != nullptr) { delete _sprite; _sprite = new BaseSprite(_gameRef, this); if (!_sprite || DID_FAIL(_sprite->loadFile(spriteFile))) { delete _sprite; _sprite = nullptr; } } if (surfaceFile != nullptr && !_sprite) { _subframe = new BaseSubFrame(_gameRef); if (custoTrans) { _subframe->setSurface(surfaceFile, false, r, g, b); } else { _subframe->setSurface(surfaceFile); } } if (((_subframe == nullptr || _subframe->_surface == nullptr) && _sprite == nullptr) || _numColumns == 0 || _tileWidth == 0 || _tileHeight == 0) { _gameRef->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[(uint)'m'] + _widths[(uint)'i']) / 2; } } } else { for (i = lastWidth; i < NUM_CHARACTERS; i++) { _widths[i] = defaultWidth; } } 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 < (int32)_sprite->_frames.size()) { if (_sprite->_frames[_widthsFrame] && (int32)_sprite->_frames[_widthsFrame]->_subframes.size() > 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(); /* _gameRef->LOG(0, "----- %s ------", _filename); for(int j=0; j<16; j++) { _gameRef->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