/* 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. * */ #include "common/endian.h" #include "xeen/font.h" #include "xeen/resources.h" #include "xeen/xeen.h" namespace Xeen { const byte *FontData::_fontData; Common::Point *FontData::_fontWritePos; byte FontData::_textColors[4]; byte FontData::_bgColor; bool FontData::_fontReduced; Justify FontData::_fontJustify; FontSurface::FontSurface() : XSurface(), _msgWraps(false), _displayString(nullptr), _writePos(*FontData::_fontWritePos) { setTextColor(0); } FontSurface::FontSurface(int wv, int hv) : XSurface(wv, hv), _msgWraps(false), _displayString(nullptr), _writePos(*FontData::_fontWritePos) { create(w, h); setTextColor(0); } void FontSurface::writeSymbol(int symbolId) { const byte *srcP = &Res.SYMBOLS[symbolId][0]; for (int yp = 0; yp < FONT_HEIGHT; ++yp) { byte *destP = (byte *)getBasePtr(_writePos.x, _writePos.y + yp); for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) { byte b = *srcP++; if (b) *destP = b; } } _writePos.x += 8; } const char *FontSurface::writeString(const Common::String &s, const Common::Rect &bounds) { _displayString = s.c_str(); assert(_fontData); for (;;) { const char *msgStartP = _displayString; _msgWraps = false; // Get the size of the string that can be displayed on the line int xp = _fontJustify == JUSTIFY_CENTER ? bounds.left : _writePos.x; while (!getNextCharWidth(xp)) { if (xp >= bounds.right) { --_displayString; _msgWraps = true; break; } } // Get the end point of the text that can be displayed const char *displayEnd = _displayString; _displayString = msgStartP; if (_msgWraps && _fontJustify != JUSTIFY_RIGHT && xp >= bounds.right) { // Need to handle justification of text // First, move backwards to find the end of the previous word // for a convenient point to break the line at const char *endP = displayEnd; while (endP > _displayString && (*endP & 0x7f) != ' ') --endP; if (endP == _displayString) { // There was no word breaks at all in the string --displayEnd; if (_fontJustify == JUSTIFY_NONE && _writePos.x != bounds.left) { // Move to the next line if (!newLine(bounds)) continue; // Ran out of space to display string break; } } else { // Found word break, find end of previous word while (endP > _displayString && (*endP & 0x7f) == ' ') --endP; displayEnd = endP; } } // Justification adjustment if (_fontJustify != JUSTIFY_NONE) { // Figure out the width of the selected portion of the string int totalWidth = 0; while (!getNextCharWidth(totalWidth)) { if (_displayString > displayEnd) { if (*displayEnd == ' ') { // Don't include any ending space as part of the total totalWidth -= _fontReduced ? 4 : 5; } break; } } // Reset starting position back to the start of the string portion _displayString = msgStartP; if (_fontJustify == JUSTIFY_RIGHT) { // Right aligned if (_writePos.x == bounds.left) _writePos.x = bounds.right; _writePos.x -= totalWidth + 1; } else { // Center aligned if (_writePos.x == bounds.left) _writePos.x = (bounds.left + bounds.right + 1 - totalWidth) / 2; else _writePos.x = (_writePos.x * 2 - totalWidth) / 2; } } // Main character display loop while (_displayString <= displayEnd) { char c = getNextChar(); if (c == ' ') { _writePos.x += _fontReduced ? 3 : 4; } else if (c == '\r') { fillRect(bounds, _bgColor); addDirtyRect(bounds); _writePos = Common::Point(bounds.left, bounds.top); } else if (c == 1) { // Turn off reduced font mode _fontReduced = false; } else if (c == 2) { // Turn on reduced font mode _fontReduced = true; } else if (c == 3) { // Justify text c = getNextChar(); if (c == 'r') _fontJustify = JUSTIFY_RIGHT; else if (c == 'c') _fontJustify = JUSTIFY_CENTER; else _fontJustify = JUSTIFY_NONE; } else if (c == 4) { // Draw an empty box of a given width int wv = fontAtoi(); Common::Point pt = _writePos; if (_fontJustify == JUSTIFY_RIGHT) pt.x -= wv; Common::Rect r(pt.x, pt.y, pt.x + wv, pt.y + (_fontReduced ? 9 : 10)); fillRect(r, _bgColor); } else if (c == 5) { continue; } else if (c == 6) { // Non-breakable space writeChar(' ', bounds); } else if (c == 7) { // Set text background color int bgColor = fontAtoi(); _bgColor = (bgColor < 0 || bgColor > 255) ? DEFAULT_BG_COLOR : bgColor; } else if (c == 8) { // Draw a character outline c = getNextChar(); if (c == ' ') { c = '\0'; _writePos.x -= 3; } else { if (c == 6) c = ' '; byte charSize = _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)]; _writePos.x -= charSize; } if (_writePos.x < bounds.left) _writePos.x = bounds.left; if (c) { int oldX = _writePos.x; byte oldColor[4]; Common::copy(&_textColors[0], &_textColors[4], &oldColor[0]); _textColors[1] = _textColors[2] = _textColors[3] = _bgColor; writeChar(c, bounds); Common::copy(&oldColor[0], &oldColor[4], &_textColors[0]); _writePos.x = oldX; } } else if (c == 9) { // Skip x position int xAmount = fontAtoi(); _writePos.x = MIN(bounds.left + xAmount, (int)bounds.right); } else if (c == 10) { // Newline if (newLine(bounds)) return _displayString; } else if (c == 11) { // Set y position int yp = fontAtoi(); _writePos.y = MIN(bounds.top + yp, (int)bounds.bottom); } else if (c == 12) { // Set text colors int idx = fontAtoi(2); if (idx < 0) idx = 0; setTextColor(idx); } else if (c < ' ') { // End of string or invalid command _displayString = nullptr; break; } else { // Standard character - write it out writeChar(c, bounds); } } if (!_displayString) break; if ( _displayString > displayEnd && _fontJustify != JUSTIFY_RIGHT && _msgWraps && newLine(bounds)) break; } return _displayString; } void FontSurface::writeCharacter(char c, const Common::Rect &clipRect) { Justify justify = _fontJustify; _fontJustify = JUSTIFY_NONE; writeString(Common::String::format("%c", c), clipRect); _fontJustify = justify; } char FontSurface::getNextChar() { return *_displayString++ & 0x7f; } bool FontSurface::getNextCharWidth(int &total) { char c = getNextChar(); if (c > ' ') { total += _fontData[0x1000 + (int)c + (_fontReduced ? 0x80 : 0)]; return false; } else if (c == ' ') { total += 4; return false; } else if (c == 8) { c = getNextChar(); if (c == ' ') { total -= 2; return false; } else { _displayString -= 2; return true; } } else if (c == 12) { c = getNextChar(); if (c != 'd') getNextChar(); return false; } else { --_displayString; return true; } } bool FontSurface::newLine(const Common::Rect &bounds) { // Move past any spaces currently being pointed to while ((*_displayString & 0x7f) == ' ') ++_displayString; _msgWraps = false; _writePos.x = bounds.left; int hv = _fontReduced ? 9 : 10; _writePos.y += hv; return ((_writePos.y + hv - 1) > bounds.bottom); } int FontSurface::fontAtoi(int len) { int total = 0; for (int i = 0; i < len; ++i) { char c = getNextChar(); if (c == ' ') c = '0'; int digit = c - '0'; if (digit < 0 || digit > 9) return -1; total = total * 10 + digit; } return total; } void FontSurface::setTextColor(int idx) { const byte *colP = (g_vm->_mode == MODE_STARTUP) ? &Res.TEXT_COLORS_STARTUP[idx][0] : &Res.TEXT_COLORS[idx][0]; Common::copy(colP, colP + 4, &_textColors[0]); } void FontSurface::writeChar(char c, const Common::Rect &clipRect) { // Get y position, handling kerning int y = _writePos.y; if (c == 'g' || c == 'p' || c == 'q' || c == 'y') ++y; int yStart = y; // Get pointers into font data and surface to write pixels to int charIndex = (int)c + (_fontReduced ? 0x80 : 0); const byte *srcP = &_fontData[charIndex * 16]; for (int yp = 0; yp < FONT_HEIGHT; ++yp, ++y) { uint16 lineData = READ_LE_UINT16(srcP); srcP += 2; byte *destP = (byte *)getBasePtr(_writePos.x, y); // Ignore line if it's outside the clipping rect if (y < clipRect.top || y >= clipRect.bottom) continue; const byte *lineStart = (const byte *)getBasePtr(clipRect.left, y); const byte *lineEnd = (const byte *)getBasePtr(clipRect.right, y); for (int xp = 0; xp < FONT_WIDTH; ++xp, ++destP) { int colIndex = lineData & 3; lineData >>= 2; if (colIndex && destP >= lineStart && destP < lineEnd) *destP = _textColors[colIndex]; } } addDirtyRect(Common::Rect(_writePos.x, yStart, _writePos.x + FONT_WIDTH, yStart + FONT_HEIGHT)); _writePos.x += _fontData[0x1000 + charIndex]; } } // End of namespace Xeen