/* 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/scummsys.h" #include "zvision/zvision.h" #include "zvision/core/search_manager.h" #include "zvision/text/string_manager.h" #include "zvision/fonts/truetype_font.h" #include "common/file.h" #include "common/tokenizer.h" #include "common/debug.h" #include "graphics/fontman.h" #include "graphics/colormasks.h" namespace ZVision { StringManager::StringManager(ZVision *engine) : _engine(engine) { } StringManager::~StringManager() { for (Common::HashMap::iterator iter = _fonts.begin(); iter != _fonts.end(); ++iter) { delete iter->_value; } } void StringManager::initialize(ZVisionGameId gameId) { if (gameId == GID_NEMESIS) { // TODO: Check this hardcoded filename against all versions of Nemesis loadStrFile("nemesis.str"); } else if (gameId == GID_GRANDINQUISITOR) { // TODO: Check this hardcoded filename against all versions of Grand Inquisitor loadStrFile("inquis.str"); } } void StringManager::loadStrFile(const Common::String &fileName) { Common::File file; if (!_engine->getSearchManager()->openFile(file, fileName)) { warning("%s does not exist. String parsing failed", fileName.c_str()); return; } uint lineNumber = 0; while (!file.eos()) { _lines[lineNumber] = readWideLine(file); lineNumber++; assert(lineNumber <= NUM_TEXT_LINES); } } void StringManager::parseStrFile(const Common::String &fileName) { Common::File file; if (!file.open(fileName)) { warning("%s does not exist. String parsing failed", fileName.c_str()); return; } uint lineNumber = 0; while (!file.eos()) { _lastStyle.align = Graphics::kTextAlignLeft; _lastStyle.color = 0; _lastStyle.font = nullptr; Common::String asciiLine = readWideLine(file); if (asciiLine.empty()) { continue; } char tagString[150]; uint tagStringCursor = 0; char textString[150]; uint textStringCursor = 0; bool inTag = false; for (uint i = 0; i < asciiLine.size(); ++i) { switch (asciiLine[i]) { case '<': inTag = true; if (!_inGameText[lineNumber].fragments.empty()) { _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); textStringCursor = 0; } break; case '>': inTag = false; parseTag(Common::String(tagString, tagStringCursor), lineNumber); tagStringCursor = 0; break; default: if (inTag) { tagString[tagStringCursor] = asciiLine[i]; tagStringCursor++; } else { textString[textStringCursor] = asciiLine[i]; textStringCursor++; } break; } } if (textStringCursor > 0) { _inGameText[lineNumber].fragments.back().text = Common::String(textString, textStringCursor); } lineNumber++; assert(lineNumber <= NUM_TEXT_LINES); } } void StringManager::parseTag(const Common::String &tagString, uint lineNumber) { Common::StringTokenizer tokenizer(tagString); Common::String token = tokenizer.nextToken(); Common::String fontName; bool bold = false; Graphics::TextAlign align = _lastStyle.align; int point = _lastStyle.font != nullptr ? _lastStyle.font->_fontHeight : 12; int red = 0; int green = 0; int blue = 0; while (!token.empty()) { if (token.matchString("font", true)) { fontName = tokenizer.nextToken(); } else if (token.matchString("bold", true)) { token = tokenizer.nextToken(); if (token.matchString("on", false)) { bold = true; } } else if (token.matchString("justify", true)) { token = tokenizer.nextToken(); if (token.matchString("center", false)) { align = Graphics::kTextAlignCenter; } else if (token.matchString("right", false)) { align = Graphics::kTextAlignRight; } } else if (token.matchString("point", true)) { point = atoi(tokenizer.nextToken().c_str()); } else if (token.matchString("red", true)) { red = atoi(tokenizer.nextToken().c_str()); } else if (token.matchString("green", true)) { green = atoi(tokenizer.nextToken().c_str()); } else if (token.matchString("blue", true)) { blue = atoi(tokenizer.nextToken().c_str()); } token = tokenizer.nextToken(); } TextFragment fragment; if (fontName.empty()) { fragment.style.font = _lastStyle.font; } else { Common::String newFontName; if (fontName.matchString("*times new roman*", true)) { if (bold) { newFontName = "timesbd.ttf"; } else { newFontName = "times.ttf"; } } else if (fontName.matchString("*courier new*", true)) { if (bold) { newFontName = "courbd.ttf"; } else { newFontName = "cour.ttf"; } } else if (fontName.matchString("*century schoolbook*", true)) { if (bold) { newFontName = "censcbkbd.ttf"; } else { newFontName = "censcbk.ttf"; } } else if (fontName.matchString("*garamond*", true)) { if (bold) { newFontName = "garabd.ttf"; } else { newFontName = "gara.ttf"; } } else { debug("Could not identify font: %s. Reverting to Arial", fontName.c_str()); if (bold) { newFontName = "zorknorm.ttf"; } else { newFontName = "arial.ttf"; } } Common::String fontKey = Common::String::format("%s-%d", newFontName.c_str(), point); if (_fonts.contains(fontKey)) { fragment.style.font = _fonts[fontKey]; } else { fragment.style.font = new TruetypeFont(_engine, point); fragment.style.font->loadFile(newFontName); _fonts[fontKey] = fragment.style.font; } } fragment.style.align = align; fragment.style.color = Graphics::ARGBToColor >(0, red, green, blue); _inGameText[lineNumber].fragments.push_back(fragment); _lastStyle = fragment.style; } Common::String StringManager::readWideLine(Common::SeekableReadStream &stream) { Common::String asciiString; // Don't spam the user with warnings about UTF-16 support. // Just do one warning per String bool charOverflowWarning = false; uint16 value = stream.readUint16LE(); while (!stream.eos()) { // Check for CRLF if (value == 0x0A0D) { // Read in the extra NULL char stream.readByte(); // \0 // End of the line. Break break; } // Crush each octet pair to a single octet with a simple cast if (value > 255) { charOverflowWarning = true; value = '?'; } char charValue = (char)value; asciiString += charValue; value = stream.readUint16LE(); } if (charOverflowWarning) { warning("UTF-16 is not supported. Characters greater than 255 are replaced with '?'"); } return asciiString; } StringManager::TextStyle StringManager::getTextStyle(uint stringNumber) { return _inGameText[stringNumber].fragments.front().style; } const Common::String StringManager::getTextLine(uint stringNumber) { return _lines[stringNumber]; } } // End of namespace ZVision