/* 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 . * */ #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