Files
2026-02-02 04:50:13 +01:00

477 lines
13 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/>.
*
*/
#include "graphics/fonts/ttf.h"
#include "bagel/spacebar/boflib/app.h"
#include "bagel/spacebar/boflib/gfx/text.h"
namespace Bagel {
namespace SpaceBar {
#define START_SIZE 8
#define MONO_FONT "LiberationMono-Regular.ttf"
#define SERIF_FONT_REGULAR "LiberationSans-Regular.ttf"
#define SERIF_FONT_BOLD "LiberationSans-Bold.ttf"
#define TAB_SIZE 50
int CBofText::_tabStop;
bool CBofText::_initialized;
Graphics::Font *CBofText::_defaultFonts[NUM_POINT_SIZES];
Graphics::Font *CBofText::_fixedFonts[NUM_POINT_SIZES];
ErrorCode CBofText::initialize() {
_initialized = true;
_tabStop = 20; // tabstops every 20 pixels
Common::fill(_defaultFonts, _defaultFonts + NUM_POINT_SIZES,
(Graphics::Font *)nullptr);
Common::fill(_fixedFonts, _fixedFonts + NUM_POINT_SIZES,
(Graphics::Font *)nullptr);
return ERR_NONE;
}
ErrorCode CBofText::shutdown() {
for (int i = 0; i < NUM_POINT_SIZES; i++) {
delete _defaultFonts[i];
delete _fixedFonts[i];
}
_initialized = false;
return ERR_NONE;
}
CBofText::CBofText() {
initializeFields(); // Initialize stuff
}
CBofText::CBofText(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
// Can't access null pointers
assert(pRect != nullptr);
// Initialize stuff
initializeFields();
// Build the work areas
setupText(pRect, nJustify, nFormatFlags);
}
CBofText::~CBofText() {
delete _pWork;
_pWork = nullptr;
delete _pBackground;
_pBackground = nullptr;
}
void CBofText::initializeFields() {
_pBackground = nullptr;
_pWork = nullptr;
_bSaved = false;
_cPosition = CBofPoint(0, 0);
_cSize = CBofSize(0, 0);
_cRect.setRect(0, 0, 0, 0);
_cShadowColor = RGB(0, 0, 0);
_nShadow_DX = 0;
_nShadow_DY = 0;
_nJustify = JUSTIFY_LEFT;
_nFormatFlags = FORMAT_DEFAULT;
_bMultiLine = false;
_nCurSize = 10;
_nCurWeight = TEXT_DONTCARE;
_cTextColor = CTEXT_COLOR;
}
ErrorCode CBofText::setupText(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
// Can't access null pointers
assert(pRect != nullptr);
_nJustify = nJustify;
// Setup the fields for location and size of the text area
_cRect = *pRect;
_cSize.cx = _cRect.width();
_cSize.cy = _cRect.height();
delete _pWork;
_pWork = nullptr;
delete _pBackground;
_pBackground = nullptr;
CBofPalette *pPalette = CBofApp::getApp()->getPalette();
// Create a bitmap to serve as our work area as we output text
_pWork = new CBofBitmap(_cSize.cx, _cSize.cy, pPalette);
// Create a bitmap to hold the background we overwrite
_pBackground = new CBofBitmap(_cSize.cx, _cSize.cy, pPalette);
return _errCode;
}
ErrorCode CBofText::setupTextOpt(const CBofRect *pRect, int nJustify, uint32 nFormatFlags) {
// Can't access null pointers
assert(pRect != nullptr);
_nJustify = nJustify;
_nFormatFlags = nFormatFlags;
// Setup the fields for location and size of the text area
_cRect = *pRect;
_cSize.cx = _cRect.width();
_cSize.cy = _cRect.height();
return _errCode;
}
ErrorCode CBofText::erase(CBofWindow *pWnd) {
// Can't access null pointers
assert(pWnd != nullptr);
if (_pBackground != nullptr && _bSaved) {
// Simply splat the background art back where it came from
_errCode = _pBackground->paint(pWnd, &_cRect);
}
return _errCode;
}
ErrorCode CBofText::erase(CBofBitmap *pBmp) {
// Can't access null pointers
assert(pBmp != nullptr);
if (_pBackground != nullptr && _bSaved) {
// Simply splat the background art back where it came from
_errCode = _pBackground->paint(pBmp, &_cRect);
}
return _errCode;
}
ErrorCode CBofText::display(CBofWindow *pWnd, const char *pszText, const int nSize, const int nWeight, const COLORREF cColor, int nFont) {
assert(isValidObject(this));
// Can't access null pointers
assert(pWnd != nullptr);
_cTextColor = cColor;
return displayText(pWnd, pszText, &_cRect, nSize, nWeight, false, nFont);
}
ErrorCode CBofText::display(CBofWindow *pWnd) {
assert(isValidObject(this));
assert(pWnd != nullptr);
return display(pWnd, _cCurString, _nCurSize, _nCurWeight, _cTextColor);
}
ErrorCode CBofText::display(CBofBitmap *pBmp) {
assert(isValidObject(this));
assert(pBmp != nullptr);
return display(pBmp, _cCurString, _nCurSize, _nCurWeight, _cTextColor);
}
ErrorCode CBofText::display(CBofBitmap *pBmp, const char *pszText, const int nSize, const int nWeight, const COLORREF cColor, int nFont) {
// Can't access null pointers
assert(pBmp != nullptr);
_cTextColor = cColor;
return displayText(pBmp, pszText, &_cRect, nSize, nWeight, false, nFont);
}
ErrorCode CBofText::displayShadowed(CBofWindow *pWnd, const char *pszText, const int nSize, const int nWeight, const COLORREF cColor, const COLORREF cShadow, const int nDX, const int nDY, int nFont) {
// Can't access null pointers
assert(pWnd != nullptr);
_cTextColor = cColor;
_cShadowColor = cShadow;
_nShadow_DX = nDX;
_nShadow_DY = nDY;
return displayText(pWnd, pszText, &_cRect, nSize, nWeight, true, nFont);
}
ErrorCode CBofText::displayText(CBofWindow *pWnd, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
assert(isValidObject(this));
assert(pWnd != nullptr);
assert(pszText != nullptr);
assert(pRect != nullptr);
CBofRect cRect(0, 0, pRect->width() - 1, pRect->height() - 1);
assert(_pBackground != nullptr);
assert(_pWork != nullptr);
if (!_bSaved) {
CBofBitmap::setUseBackdrop(true);
_pBackground->captureScreen(pWnd, pRect);
CBofBitmap::setUseBackdrop(false);
_bSaved = true;
}
_pBackground->paint(_pWork, 0, 0);
displayTextEx(_pWork, pszText, &cRect, nSize, nWeight, bShadowed, nFont);
_pWork->paint(pWnd, pRect);
return _errCode;
}
ErrorCode CBofText::displayText(CBofBitmap *pBmp, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
assert(isValidObject(this));
assert(pBmp != nullptr);
assert(pszText != nullptr);
assert(pRect != nullptr);
CBofRect cRect(0, 0, pRect->width() - 1, pRect->height() - 1);
assert(_pWork != nullptr);
assert(_pBackground != nullptr);
if (!_bSaved) {
CBofRect r = _pBackground->getRect();
pBmp->paint(_pBackground, &r, pRect);
_bSaved = true;
}
_pBackground->paint(_pWork, 0, 0);
displayTextEx(_pWork, pszText, &cRect, nSize, nWeight, bShadowed, nFont);
_pWork->paint(pBmp, pRect);
return _errCode;
}
Graphics::Font *CBofText::getFont(int nFont, int nSize, int nWeight) {
Graphics::Font *font;
// Attempt to use one of the fonts that we pre-allocated
if (nFont != FONT_MONO) {
font = _defaultFonts[nSize - START_SIZE];
} else {
font = _fixedFonts[nSize - START_SIZE];
}
// Last resort - create the font now
if (font == nullptr) {
if (nFont != FONT_MONO) {
font = Graphics::loadTTFFontFromArchive(SERIF_FONT_REGULAR, nSize, Graphics::kTTFSizeModeCell);
_defaultFonts[nSize - START_SIZE] = font;
} else {
font = Graphics::loadTTFFontFromArchive(MONO_FONT, nSize, Graphics::kTTFSizeModeCell);
_fixedFonts[nSize - START_SIZE] = font;
}
}
return font;
}
ErrorCode CBofText::displayTextEx(CBofBitmap *pBmp, const char *pszText, CBofRect *pRect, const int nSize, const int nWeight, const bool bShadowed, int nFont) {
assert(isValidObject(this));
// can't access null pointers
assert(pBmp != nullptr);
assert(pszText != nullptr);
assert(pRect != nullptr);
Graphics::ManagedSurface surface = pBmp->getSurface();
Graphics::Font *font = getFont(nFont, nSize, nWeight);
int color;
// Split lines
Common::U32StringArray lines;
font->wordWrapText(Common::U32String(pszText, Common::kWindows1252), pRect->width(), lines);
// Iterate the lines to get the maximum width
int maxWidth = 0;
for (uint i = 0; i < lines.size(); ++i)
maxWidth = MAX(maxWidth, font->getStringWidth(lines[i]));
Common::Point textInfo(maxWidth, (int)lines.size() * font->getFontHeight());
_cPosition.y = (_cSize.cy - textInfo.y) >> 1;
Graphics::TextAlign align = Graphics::kTextAlignLeft;
switch (_nJustify) {
case JUSTIFY_CENTER:
_cPosition.x = (_cSize.cx - textInfo.x) >> 1;
align = Graphics::kTextAlignCenter;
break;
case JUSTIFY_LEFT:
// align left
_cPosition.x = 0;
break;
case JUSTIFY_RIGHT:
_cPosition.x = _cSize.cx - textInfo.x;
align = Graphics::kTextAlignRight;
break;
case JUSTIFY_WRAP:
// Align left
_bMultiLine = true;
break;
default:
break;
}
// text starts relative to area for painting
_cPosition += pRect->topLeft();
// Note: Under ScummVM, even single line drawing uses the multiLine code
Common::Rect newRect = *pRect;
if ((_nFormatFlags & FORMAT_TOP_CENTER) == FORMAT_TOP_CENTER) {
int h = lines.size() * font->getFontHeight();
newRect.top = (newRect.top + newRect.bottom) / 2 - h / 2;
newRect.bottom = newRect.top + h;
}
Common::Rect shadowRect = newRect;
shadowRect.translate(_nShadow_DX, _nShadow_DY);
for (uint i = 0; i < lines.size(); ++i) {
const Common::U32String &line = lines[i];
if (bShadowed) {
color = CBofApp::getApp()->getPalette()->getNearestIndex(_cShadowColor);
displayLine(font, surface, line, shadowRect.left, shadowRect.top,
shadowRect.width(), color, align);
}
color = CBofApp::getApp()->getPalette()->getNearestIndex(_cTextColor);
displayLine(font, surface, line, newRect.left, newRect.top,
newRect.width(), color, align);
newRect.top += font->getFontHeight();
shadowRect.top += font->getFontHeight();
}
return _errCode;
}
void CBofText::displayLine(Graphics::Font *font, Graphics::ManagedSurface &surface,
const Common::U32String &line, int left, int top, int width, int color, Graphics::TextAlign align) {
if (!line.contains('\t')) {
font->drawString(&surface, line, left, top, width, color, align);
} else {
// Special rendering of tabbed text
Common::U32String str = line;
while (!str.empty()) {
if (str[0] == '\t') {
// Move to next tab stop
left = (left + TAB_SIZE) / TAB_SIZE * TAB_SIZE;
str.deleteChar(0);
} else {
Common::U32String fragment;
size_t tab = str.findFirstOf('\t');
if (tab == Common::U32String::npos) {
fragment = str;
str.clear();
} else {
fragment = Common::U32String(str.c_str(), str.c_str() + tab);
str = Common::U32String(str.c_str() + tab);
}
int fragmentWidth = font->getStringWidth(fragment);
font->drawString(&surface, fragment, left, top, width, color, align);
left += fragmentWidth;
width -= fragmentWidth;
}
}
}
}
ErrorCode paintText(CBofWindow *pWnd, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const COLORREF cColor, int nJustify, uint32 nFormatFlags, int nFont) {
assert(pWnd != nullptr);
assert(pRect != nullptr);
CBofText cText(pRect, nJustify, nFormatFlags);
return cText.display(pWnd, pszString, nSize, nWeight, cColor, nFont);
}
ErrorCode paintText(CBofBitmap *pBmp, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const COLORREF cColor, int nJustify, uint32 nFormatFlags, int nFont) {
assert(pBmp != nullptr);
assert(pRect != nullptr);
CBofText cText;
cText.setupTextOpt(pRect, nJustify, nFormatFlags);
cText.setColor(cColor);
return cText.displayTextEx(pBmp, pszString, pRect, nSize, nWeight, false, nFont);
}
ErrorCode paintShadowedText(CBofBitmap *pBmp, CBofRect *pRect, const char *pszString, const int nSize, const int nWeight, const COLORREF cColor, int nJustify, uint32 nFormatFlags, int nFont) {
assert(pBmp != nullptr);
assert(pRect != nullptr);
CBofText cText;
cText.setupTextOpt(pRect, nJustify, nFormatFlags);
cText.setColor(cColor);
cText.setShadowColor(CTEXT_SHADOW_COLOR);
cText.setShadowSize(CTEXT_SHADOW_DX, CTEXT_SHADOW_DY);
return cText.displayTextEx(pBmp, pszString, pRect, nSize, nWeight, true, nFont);
}
CBofRect calculateTextRect(CBofWindow *pWnd, const CBofString *pStr, int nSize, int nFont) {
return calculateTextRect(pWnd->getRect(), pStr, nSize, nFont);
}
CBofRect calculateTextRect(CBofRect rect, const CBofString *pStr, int nSize, int nFont) {
// Get the font to use
Graphics::Font *font = CBofText::getFont(nFont, nSize, TEXT_NORMAL);
// Wrap the text as necessary
Common::U32StringArray lines;
font->wordWrapText(Common::U32String(pStr->getBuffer(), Common::kWindows1252), rect.width(), lines);
// Iterate the lines to get the maximum width
int maxWidth = 0;
for (uint i = 0; i < lines.size(); ++i)
maxWidth = MAX(maxWidth, font->getStringWidth(lines[i]));
return CBofRect(0, 0, maxWidth, (int)lines.size() * font->getFontHeight());
}
} // namespace SpaceBar
} // namespace Bagel