/* 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 "engines/nancy/font.h"
#include "engines/nancy/nancy.h"
#include "engines/nancy/resource.h"
#include "engines/nancy/graphics.h"
#include "engines/nancy/util.h"
#include "engines/nancy/enginedata.h"
namespace Nancy {
void Font::read(Common::SeekableReadStream &stream) {
_transColor = g_nancy->_graphics->getTransColor();
_maxCharWidth = 0;
_fontHeight = 0;
uint16 numCharacters = 78;
Common::Path imageName;
readFilename(stream, imageName);
g_nancy->_resource->loadImage(imageName, _image);
_image.setTransparentColor(g_nancy->_graphics->getTransColor());
char desc[31];
stream.read(desc, 30);
desc[30] = '\0';
_description = desc;
_color0CoordsOffset.x = stream.readUint32LE();
_color0CoordsOffset.y = stream.readUint32LE();
_color1CoordsOffset.x = stream.readUint32LE();
_color1CoordsOffset.y = stream.readUint32LE();
_spaceWidth = stream.readUint16LE();
_charSpace = stream.readSint16LE() - 1; // Account for the added pixel in readRect
_uppercaseOffset = stream.readUint16LE();
_lowercaseOffset = stream.readUint16LE();
_digitOffset = stream.readUint16LE();
_periodOffset = stream.readUint16LE();
_commaOffset = stream.readUint16LE();
_equalitySignOffset = stream.readUint16LE();
_colonOffset = stream.readUint16LE();
_dashOffset = stream.readUint16LE();
_questionMarkOffset = stream.readUint16LE();
_exclamationMarkOffset = stream.readUint16LE();
_percentOffset = stream.readUint16LE();
_ampersandOffset = stream.readUint16LE();
_asteriskOffset = stream.readUint16LE();
_leftBracketOffset = stream.readUint16LE();
_rightBracketOffset = stream.readUint16LE();
_plusOffset = stream.readUint16LE();
_apostropheOffset = stream.readUint16LE();
_semicolonOffset = stream.readUint16LE();
_slashOffset = stream.readUint16LE();
if (g_nancy->getGameLanguage() == Common::RU_RUS && g_nancy->getGameType() >= kGameTypeNancy5 && g_nancy->getGameType() != kGameTypeNancy6) {
// Only extract the lowercase/uppercase offsets, since the letters are in order in the FONT data
_cyrillicLowercaseOffset = stream.readUint16LE();
stream.skip(72);
_cyrillicUppercaseOffset = stream.readUint16LE();
stream.skip(2);
numCharacters = 179;
} else {
if (g_nancy->getGameType() >= kGameTypeNancy6) {
// Nancy6 added more characters to its fonts
_aWithGraveOffset = stream.readUint16LE();
_cWithCedillaOffset = stream.readUint16LE();
_eWithGraveOffset = stream.readUint16LE();
_eWithAcuteOffset = stream.readUint16LE();
_eWithCircumflexOffset = stream.readUint16LE();
_eWithDiaeresisOffset = stream.readUint16LE();
_oWithCircumflexOffset = stream.readUint16LE();
_uppercaseAWithGraveOffset = stream.readUint16LE();
_aWithCircumflexOffset = stream.readUint16LE();
_iWithCircumflexOffset = stream.readUint16LE();
_uWithGraveOffset = stream.readUint16LE();
_uppercaseAWithDiaeresisOffset = stream.readUint16LE();
_aWithDiaeresisOffset = stream.readUint16LE();
_uppercaseOWithDiaeresisOffset = stream.readUint16LE();
_oWithDiaeresisOffset = stream.readUint16LE();
_uppercaseUWithDiaeresisOffset = stream.readUint16LE();
_uWithDiaeresisOffset = stream.readUint16LE();
_invertedExclamationMarkOffset = stream.readUint16LE();
_invertedQuestionMarkOffset = stream.readUint16LE();
_uppercaseNWithTildeOffset = stream.readUint16LE();
_nWithTildeOffset = stream.readUint16LE();
_uppercaseEWithAcuteOffset = stream.readUint16LE();
_aWithAcuteOffset = stream.readUint16LE();
_iWithAcuteOffset = stream.readUint16LE();
_oWithAcuteOffset = stream.readUint16LE();
_uWithAcuteOffset = stream.readUint16LE();
_eszettOffset = stream.readUint16LE();
numCharacters = 105;
}
}
_characterRects.resize(numCharacters);
for (uint i = 0; i < numCharacters; ++i) {
Common::Rect &cur = _characterRects[i];
readRect(stream, cur);
if (g_nancy->getGameType() == kGameTypeVampire) {
++cur.bottom;
++cur.right;
}
_maxCharWidth = MAX(cur.width(), _maxCharWidth);
_fontHeight = MAX(cur.height(), _fontHeight);
}
if (g_nancy->getGameType() >= kGameTypeNancy6) {
_fontHeight = getCharWidth('o') * 2 - 1;
}
_textboxData = GetEngineData(TBOX);
assert(_textboxData);
}
int Font::getCharWidth(uint32 chr) const {
return getCharacterSourceRect(chr).width() + _charSpace;
}
void Font::drawChar(Graphics::Surface *dst, uint32 chr, int x, int y, uint32 color) const {
Common::Rect srcRect = getCharacterSourceRect(chr);
if (color == 0) {
srcRect.translate(_color0CoordsOffset.x, _color0CoordsOffset.y);
} else if (color == 1) {
srcRect.translate(_color1CoordsOffset.x, _color1CoordsOffset.y);
}
uint vampireAdjust = g_nancy->getGameType() == kGameTypeVampire ? 1 : 0;
srcRect.setWidth(MAX(srcRect.width() - vampireAdjust, 0));
uint height = srcRect.height();
uint yOffset = getFontHeight() - height;
srcRect.setHeight(MAX(height - vampireAdjust, 0));
// Create a wrapper ManagedSurface to handle blitting/format differences
Graphics::ManagedSurface dest;
dest.w = dst->w;
dest.h = dst->h;
dest.pitch = dst->pitch;
dest.setPixels(dst->getPixels());
dest.format = dst->format;
dest.blitFrom(_image, srcRect, Common::Point(x, y + yOffset));
}
void Font::wordWrap(const Common::String &str, int maxWidth, Common::Array &lines, int initWidth) const {
Common::String temp;
for (const char *c = str.begin(); c != str.end(); ++c) {
if (*c == '\n') {
lines.push_back(temp);
temp.clear();
continue;
}
temp += *c;
int size = getStringWidth(temp) + (lines.size() == 0 ? initWidth : 0);
if (size >= maxWidth) {
do {
temp.deleteLastChar();
--c;
} while (temp.size() && temp.lastChar() != ' ');
lines.push_back(temp);
temp.clear();
}
}
if (temp.size()) {
lines.push_back(temp);
}
}
Common::Rect Font::getCharacterSourceRect(char chr) const {
// Map a character to its source rect inside the font data.
// The original engine devs had some _interesting_ ideas on how to store font data,
// which makes the ridiculous switch statements below a necessity
using namespace Common;
int offset = -1;
Common::Rect ret;
if ((uint8)chr > 127) {
bool selectedRussian = false;
if (g_nancy->getGameType() >= kGameTypeNancy5 && g_nancy->getGameType() != kGameTypeNancy6 && g_nancy->getGameLanguage() == Common::RU_RUS) {
// Handle Russian strings in nancy5 and up, which use Windows-1251 encoding
// (except for nancy6, which goes back to latinizing russian)
// We cannot rely on isUpper/isLower, since they use the C locale settings
if ((uint8)chr >= 0xC0 && (uint8)chr <= 0xDF) {
// capital letters
offset = (uint8)chr + _cyrillicUppercaseOffset - 0xC0;
selectedRussian = true;
} else if ((uint8)chr >= 0xE0) {
offset = (uint8)chr + _cyrillicLowercaseOffset - 0xE0;
selectedRussian = true;
}
}
if (!selectedRussian) {
// Nancy6 introduced extended ASCII characters
switch (chr) {
case '\xe0':
offset = _aWithGraveOffset;
break;
case '\xe7':
offset = _cWithCedillaOffset;
break;
case '\xe8':
offset = _eWithGraveOffset;
break;
case '\xe9':
offset = _eWithAcuteOffset;
break;
case '\xea':
offset = _eWithCircumflexOffset;
break;
case '\xeb':
offset = _eWithDiaeresisOffset;
break;
case '\xf4':
offset = _oWithCircumflexOffset;
break;
case '\xc0':
offset = _uppercaseAWithGraveOffset;
break;
case '\xe2':
offset = _aWithCircumflexOffset;
break;
case '\xee':
offset = _iWithCircumflexOffset;
break;
case '\xf9':
offset = _uWithGraveOffset;
break;
case '\xc4':
offset = _uppercaseAWithDiaeresisOffset;
break;
case '\xe4':
offset = _aWithDiaeresisOffset;
break;
case '\xd6':
offset = _uppercaseOWithDiaeresisOffset;
break;
case '\xf6':
offset = _oWithDiaeresisOffset;
break;
case '\xdc':
offset = _uppercaseUWithDiaeresisOffset;
break;
case '\xfc':
offset = _uWithDiaeresisOffset;
break;
case '\xa1':
offset = _invertedExclamationMarkOffset;
break;
case '\xbf':
offset = _invertedQuestionMarkOffset;
break;
case '\xd1':
offset = _uppercaseNWithTildeOffset;
break;
case '\xf1':
offset = _nWithTildeOffset;
break;
case '\xc9':
offset = _uppercaseEWithAcuteOffset;
break;
case '\xe1':
offset = _aWithAcuteOffset;
break;
case '\xed':
offset = _iWithAcuteOffset;
break;
case '\xf3':
offset = _oWithAcuteOffset;
break;
case '\xfa':
offset = _uWithAcuteOffset;
break;
case '\xdf':
offset = _eszettOffset;
break;
case '\x92':
if (g_nancy->getGameType() == kGameTypeNancy4) {
// Improvement: we fix a specific broken string in nancy4 ("It's too dark..." when entering a dark staircase)
offset = _apostropheOffset;
} else {
offset = -1;
}
break;
default:
offset = -1;
break;
}
}
} else if (isUpper(chr)) {
offset = chr + _uppercaseOffset - 0x41;
} else if (isLower(chr)) {
offset = chr + _lowercaseOffset - 0x61;
} else if (isDigit(chr)) {
offset = chr + _digitOffset - 0x30;
} else if (chr == '\t') {
ret.setWidth((_spaceWidth - 1) * _textboxData->tabWidth);
return ret;
} else if (isSpace(chr)) {
ret.setWidth(_spaceWidth - 1);
return ret;
} else if (isPunct(chr)) {
switch (chr) {
case '.':
offset = _periodOffset;
break;
case ',':
offset = _commaOffset;
break;
case '=':
offset = _equalitySignOffset;
break;
case ':':
offset = _colonOffset;
break;
case '-':
offset = _dashOffset;
break;
case '?':
offset = _questionMarkOffset;
break;
case '!':
offset = _exclamationMarkOffset;
break;
case '%':
offset = _percentOffset;
break;
case '&':
offset = _ampersandOffset;
break;
case '*':
offset = _asteriskOffset;
break;
case '(':
offset = _leftBracketOffset;
break;
case ')':
offset = _rightBracketOffset;
break;
case '+':
offset = _plusOffset;
break;
case '\'':
offset = _apostropheOffset;
break;
case ';':
offset = _semicolonOffset;
break;
case '/':
offset = _slashOffset;
break;
default:
offset = -1;
break;
}
}
if (offset == -1) {
// There is at least one malformed string in nancy4 that will reach this.
// Also, this helps support displaying saves that were originally made in the GMM
// inside the in-game load/save menu, since those _may_ contain characters not present in the font
// When reaching an invalid char, the original engine repeated the last printed character,
// but we print a question mark instead.
offset = _questionMarkOffset;
}
ret = _characterRects[offset];
return ret;
}
} // End of namespace Nancy