mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-21 09:21:08 +00:00
378 lines
9.9 KiB
C++
378 lines
9.9 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 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"
|
|
|
|
namespace Xeen {
|
|
|
|
FontSurface::FontSurface() : XSurface(), _fontData(nullptr), _bgColor(DEFAULT_BG_COLOR),
|
|
_fontReduced(false),_fontJustify(JUSTIFY_NONE), _msgWraps(false) {
|
|
setTextColor(0);
|
|
}
|
|
|
|
FontSurface::FontSurface(int wv, int hv) : XSurface(wv, hv), _fontData(nullptr), _msgWraps(false),
|
|
_bgColor(DEFAULT_BG_COLOR), _fontReduced(false), _fontJustify(JUSTIFY_NONE) {
|
|
create(w, h);
|
|
setTextColor(0);
|
|
}
|
|
|
|
/**
|
|
* Draws a symbol to the surface.
|
|
* @param symbolId Symbol number from 0 to 19
|
|
*/
|
|
void FontSurface::writeSymbol(int symbolId) {
|
|
const byte *srcP = &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;
|
|
}
|
|
|
|
/**
|
|
* Write a string to the surface
|
|
* @param s String to display
|
|
* @param clipRect Window bounds to display string within
|
|
* @returns Any string remainder that couldn't be displayed
|
|
* @remarks Note that bounds is just used for wrapping purposes. Unless
|
|
* justification is set, the message will be written at _writePos
|
|
*/
|
|
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))
|
|
break;
|
|
} 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;
|
|
}
|
|
|
|
/**
|
|
* Return the next pending character to display
|
|
*/
|
|
char FontSurface::getNextChar() {
|
|
return *_displayString++ & 0x7f;
|
|
}
|
|
|
|
/**
|
|
* Return the width of a given character
|
|
*/
|
|
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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles moving to the next line of the given bounded area
|
|
*/
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Extract a number of a given maximum length from the string
|
|
*/
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Set the text colors based on the specified index in the master text colors list
|
|
*/
|
|
void FontSurface::setTextColor(int idx) {
|
|
const byte *colP = &TEXT_COLORS[idx][0];
|
|
Common::copy(colP, colP + 4, &_textColors[0]);
|
|
}
|
|
|
|
/**
|
|
* Wrie a character to the surface
|
|
*/
|
|
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;
|
|
|
|
// 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, _writePos.y, _writePos.x + FONT_WIDTH,
|
|
_writePos.y + FONT_HEIGHT));
|
|
_writePos.x += _fontData[0x1000 + charIndex];
|
|
}
|
|
|
|
} // End of namespace Xeen
|