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