Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/tokenizer.h"
#include "graphics/fontman.h"
#include "zvision/zvision.h"
#include "zvision/text/string_manager.h"
#include "zvision/text/text.h"
namespace ZVision {
StringManager::StringManager(ZVision *engine) {
}
StringManager::~StringManager() {
}
void StringManager::initialize(ZVisionGameId gameId) {
if (gameId == GID_NEMESIS)
loadStrFile("nemesis.str");
else if (gameId == GID_GRANDINQUISITOR)
loadStrFile("inquis.str");
}
void StringManager::loadStrFile(const Common::Path &fileName) {
Common::File file;
if (!file.open(fileName))
error("%s does not exist. String parsing failed", fileName.toString().c_str());
uint lineNumber = 0;
while (!file.eos()) {
_lines[lineNumber] = readWideLine(file).encode();
lineNumber++;
assert(lineNumber <= NUM_TEXT_LINES);
}
}
const Common::String StringManager::getTextLine(uint stringNumber) {
return _lines[stringNumber];
}
} // End of namespace ZVision

View File

@@ -0,0 +1,66 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_STRING_MANAGER_H
#define ZVISION_STRING_MANAGER_H
#include "zvision/text/truetype_font.h"
namespace Graphics {
class FontManager;
}
namespace ZVision {
class ZVision;
class StringManager {
public:
StringManager(ZVision *engine);
~StringManager();
public:
enum {
ZVISION_STR_SAVEEXIST = 23,
ZVISION_STR_SAVED = 4,
ZVISION_STR_SAVEEMPTY = 21,
ZVISION_STR_EXITPROMT = 6
};
private:
enum {
NUM_TEXT_LINES = 56 // Max number of lines in a .str file. We hardcode this number because we know ZNem uses 42 strings and ZGI uses 56
};
private:
Common::String _lines[NUM_TEXT_LINES];
public:
void initialize(ZVisionGameId gameId);
const Common::String getTextLine(uint stringNumber);
private:
void loadStrFile(const Common::Path &fileName);
};
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,430 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/file.h"
#include "common/system.h"
#include "zvision/detection.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/text/subtitle_manager.h"
#include "zvision/text/text.h"
namespace ZVision {
SubtitleManager::SubtitleManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS) :
_engine(engine),
_system(engine->_system),
_renderManager(engine->getRenderManager()),
_pixelFormat(pixelFormat),
_textOffset(layout.workingArea.origin() - layout.textArea.origin()),
_textArea(layout.textArea.width(), layout.textArea.height()),
_redraw(false),
_doubleFPS(doubleFPS),
_subId(0) {
}
SubtitleManager::~SubtitleManager() {
// Delete all subtitles referenced in subslist
for (auto &it : _subsList)
delete it._value;
}
void SubtitleManager::process(int32 deltatime) {
for (SubtitleMap::iterator it = _subsList.begin(); it != _subsList.end(); it++) {
// Update all automatic subtitles
if (it->_value->selfUpdate())
_redraw = true;
// Update all subtitles' respective deletion timers
if (it->_value->process(deltatime)) {
debugC(4, kDebugSubtitle, "Deleting subtitle, subId=%d", it->_key);
_subsFocus.remove(it->_key);
delete it->_value;
_subsList.erase(it);
_redraw = true;
}
}
if (_subsList.size() == 0)
if (_subId != 0) {
debugC(4, kDebugSubtitle, "Resetting subId to 0");
_subId = 0;
_subsFocus.clear();
}
if (_redraw) {
debugC(4, kDebugSubtitle, "Redrawing subtitles");
// Blank subtitle buffer
_renderManager->clearTextSurface();
// Render just the most recent subtitle
if (_subsFocus.size()) {
uint16 curSub = _subsFocus.front();
debugC(4, kDebugSubtitle, "Rendering subtitle %d", curSub);
Subtitle *sub = _subsList[curSub];
if (sub->_lineId >= 0) {
Graphics::Surface textSurface;
//TODO - make this surface a persistent member of the manager; only call create() when currently displayed subtitle is changed.
textSurface.create(sub->_textArea.width(), sub->_textArea.height(), _engine->_resourcePixelFormat);
textSurface.fillRect(Common::Rect(sub->_textArea.width(), sub->_textArea.height()), (uint32)-1); // TODO Unnecessary operation? Check later.
_engine->getTextRenderer()->drawTextWithWordWrapping(sub->_lines[sub->_lineId].subStr, textSurface, _engine->isWidescreen());
_renderManager->blitSurfaceToText(textSurface, sub->_textArea.left, sub->_textArea.top, -1);
textSurface.free();
sub->_redraw = false;
}
}
_redraw = false;
}
}
void SubtitleManager::update(int32 count, uint16 subid) {
if (_subsList.contains(subid))
if (_subsList[subid]->update(count)) {
// _subsFocus.set(subid);
_redraw = true;
}
}
uint16 SubtitleManager::create(const Common::Path &subname, bool vob) {
_subId++;
debugC(2, kDebugSubtitle, "Creating scripted subtitle, subId=%d", _subId);
_subsList[_subId] = new Subtitle(_engine, subname, vob);
_subsFocus.set(_subId);
return _subId;
}
uint16 SubtitleManager::create(const Common::Path &subname, Audio::SoundHandle handle) {
_subId++;
debugC(2, kDebugSubtitle, "Creating scripted subtitle, subId=%d", _subId);
_subsList[_subId] = new AutomaticSubtitle(_engine, subname, handle);
_subsFocus.set(_subId);
return _subId;
}
uint16 SubtitleManager::create(const Common::String &str) {
_subId++;
debugC(2, kDebugSubtitle, "Creating simple subtitle, subId=%d, message %s", _subId, str.c_str());
_subsList[_subId] = new Subtitle(_engine, str, _textArea);
_subsFocus.set(_subId);
return _subId;
}
void SubtitleManager::destroy(uint16 id) {
if (_subsList.contains(id)) {
debugC(2, kDebugSubtitle, "Marking subtitle %d for immediate deletion", id);
_subsList[id]->_toDelete = true;
}
}
void SubtitleManager::destroy(uint16 id, int16 delay) {
if (_subsList.contains(id)) {
debugC(2, kDebugSubtitle, "Marking subtitle %d for deletion in %dms", id, delay);
_subsList[id]->_timer = delay;
}
}
void SubtitleManager::timedMessage(const Common::String &str, uint16 milsecs) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating timed message: %s to subtitle id %d, time %d", str.c_str(), msgid, milsecs);
update(0, msgid);
process(0);
destroy(msgid, milsecs);
}
bool SubtitleManager::askQuestion(const Common::String &str, bool streaming, bool safeDefault) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating user question: %s to subtitle id %d", str.c_str(), msgid);
update(0, msgid);
process(0);
if(streaming)
_renderManager->renderSceneToScreen(true,true,true);
else
_renderManager->renderSceneToScreen(true);
_engine->stopClock();
int result = 0;
while (result == 0) {
Common::Event evnt;
while (_engine->getEventManager()->pollEvent(evnt)) {
switch (evnt.type) {
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if ((ZVisionAction)evnt.customType != kZVisionActionQuit)
break;
// fall through
case Common::EVENT_QUIT:
debugC(1, kDebugEvent, "Attempting to quit within quit dialog!");
_engine->quit(false);
return safeDefault;
break;
case Common::EVENT_KEYDOWN:
// English: yes/no
// German: ja/nein
// Spanish: si/no
// French Nemesis: F4/any other key _engine(engine),
// French ZGI: oui/non
// TODO: Handle this using the keymapper
switch (evnt.kbd.keycode) {
case Common::KEYCODE_y:
if (_engine->getLanguage() == Common::EN_ANY)
result = 2;
break;
case Common::KEYCODE_j:
if (_engine->getLanguage() == Common::DE_DEU)
result = 2;
break;
case Common::KEYCODE_s:
if (_engine->getLanguage() == Common::ES_ESP)
result = 2;
break;
case Common::KEYCODE_o:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_GRANDINQUISITOR)
result = 2;
break;
case Common::KEYCODE_F4:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
result = 2;
break;
case Common::KEYCODE_n:
result = 1;
break;
default:
if (_engine->getLanguage() == Common::FR_FRA && _engine->getGameId() == GID_NEMESIS)
result = 1;
break;
}
break;
default:
break;
}
}
if(streaming)
_renderManager->renderSceneToScreen(true,true,false);
else
_renderManager->renderSceneToScreen(true);
if (_doubleFPS)
_system->delayMillis(33);
else
_system->delayMillis(66);
}
destroy(msgid);
_engine->startClock();
return result == 2;
}
void SubtitleManager::delayedMessage(const Common::String &str, uint16 milsecs) {
uint16 msgid = create(str);
debugC(1, kDebugSubtitle, "initiating delayed message: %s to subtitle id %d, delay %dms", str.c_str(), msgid, milsecs);
update(0, msgid);
process(0);
_renderManager->renderSceneToScreen(true);
_engine->stopClock();
uint32 stopTime = _system->getMillis() + milsecs;
while (_system->getMillis() < stopTime) {
Common::Event evnt;
while (_engine->getEventManager()->pollEvent(evnt)) {
switch (evnt.type) {
case Common::EVENT_KEYDOWN:
switch (evnt.kbd.keycode) {
case Common::KEYCODE_SPACE:
case Common::KEYCODE_RETURN:
case Common::KEYCODE_ESCAPE:
goto skip_delayed_message;
break;
default:
break;
}
break;
case Common::EVENT_CUSTOM_ENGINE_ACTION_START:
if ((ZVisionAction)evnt.customType != kZVisionActionQuit)
break;
// fall through
case Common::EVENT_QUIT:
if (ConfMan.hasKey("confirm_exit") && ConfMan.getBool("confirm_exit"))
_engine->quit(true);
else
_engine->quit(false);
break;
default:
break;
}
}
skip_delayed_message:
_renderManager->renderSceneToScreen(true);
if (_doubleFPS)
_system->delayMillis(17);
else
_system->delayMillis(33);
}
destroy(msgid);
_engine->startClock();
}
void SubtitleManager::showDebugMsg(const Common::String &msg, int16 delay) {
uint16 msgid = create(msg);
debugC(1, kDebugSubtitle, "initiating in-game debug message: %s to subtitle id %d, delay %dms", msg.c_str(), msgid, delay);
update(0, msgid);
process(0);
destroy(msgid, delay);
}
Subtitle::Subtitle(ZVision *engine, const Common::Path &subname, bool vob) :
_engine(engine),
_lineId(-1),
_timer(-1),
_toDelete(false),
_redraw(false) {
Common::File subFile;
Common::Point _textOffset = _engine->getSubtitleManager()->getTextOffset();
if (!subFile.open(subname)) {
warning("Failed to open subtitle %s", subname.toString().c_str());
_toDelete = true;
return;
}
// Parse subtitle parameters from script
while (!subFile.eos()) {
Common::String str = subFile.readLine();
if (str.lastChar() == '~')
str.deleteLastChar();
if (str.matchString("*Initialization*", true)) {
// Not used
} else if (str.matchString("*Rectangle*", true)) {
int32 x1, y1, x2, y2;
if (sscanf(str.c_str(), "%*[^:]:%d %d %d %d", &x1, &y1, &x2, &y2) == 4) {
_textArea = Common::Rect(x1, y1, x2, y2);
debugC(1, kDebugSubtitle, "Original subtitle script rectangle coordinates: l%d, t%d, r%d, b%d", x1, y1, x2, y2);
// Original game subtitle scripts appear to define subtitle rectangles relative to origin of working area.
// To allow arbitrary aspect ratios, we need to instead place these relative to origin of text area.
// This will allow the managed text area to then be arbitrarily placed on the screen to suit different aspect ratios.
_textArea.translate(_textOffset.x, _textOffset.y); // Convert working area coordinates to text area coordinates
debugC(1, kDebugSubtitle, "Text area coordinates: l%d, t%d, r%d, b%d", _textArea.left, _textArea.top, _textArea.right, _textArea.bottom);
}
} else if (str.matchString("*TextFile*", true)) {
char filename[64];
if (sscanf(str.c_str(), "%*[^:]:%s", filename) == 1) {
Common::File txtFile;
if (txtFile.open(Common::Path(filename))) {
while (!txtFile.eos()) {
Common::String txtline = readWideLine(txtFile).encode();
Line curLine;
curLine.start = -1;
curLine.stop = -1;
curLine.subStr = txtline;
_lines.push_back(curLine);
}
txtFile.close();
}
}
} else {
int32 st; // Line start time
int32 en; // Line end time
int32 sb; // Line number
if (sscanf(str.c_str(), "%*[^:]:(%d,%d)=%d", &st, &en, &sb) == 3) {
if (sb <= (int32)_lines.size()) {
if (vob) {
// Convert frame number from 15FPS (AVI) to 29.97FPS (VOB) to synchronise with video
// st = st * 2997 / 1500;
// en = en * 2997 / 1500;
st = st * 2900 / 1500; // TODO: Subtitles only synchronise correctly at 29fps, but vob files should be 29.97fps; check if video codec is rounding this value down!
en = en * 2900 / 1500;
}
_lines[sb].start = st;
_lines[sb].stop = en;
}
}
}
}
subFile.close();
}
Subtitle::Subtitle(ZVision *engine, const Common::String &str, const Common::Rect &textArea) :
_engine(engine),
_lineId(-1),
_timer(-1),
_toDelete(false),
_redraw(false) {
_textArea = textArea;
debugC(1, kDebugSubtitle, "Text area coordinates: l%d, t%d, r%d, b%d", _textArea.left, _textArea.top, _textArea.right, _textArea.bottom);
Line curLine;
curLine.start = -1;
curLine.stop = 0;
curLine.subStr = str;
_lines.push_back(curLine);
}
Subtitle::~Subtitle() {
_lines.clear();
}
bool Subtitle::process(int32 deltatime) {
if (_timer != -1) {
_timer -= deltatime;
if (_timer <= 0)
_toDelete = true;
}
return _toDelete;
}
bool Subtitle::update(int32 count) {
int16 j = -1;
// Search all lines to find first line that encompasses current time/framecount, set j to this
for (uint16 i = (_lineId >= 0 ? _lineId : 0); i < _lines.size(); i++)
if (count >= _lines[i].start && count <= _lines[i].stop) {
j = i;
break;
}
if (j == -1) {
// No line exists for current time/framecount
if (_lineId != -1) {
// Line is set
_lineId = -1; // Unset line
_redraw = true;
}
} else {
// Line exists for current time/framecount
if (j != _lineId && _lines[j].subStr.size()) {
// Set line is not equal to current line & current line is not blank
_lineId = j; // Set line to current
_redraw = true;
}
}
return _redraw;
}
AutomaticSubtitle::AutomaticSubtitle(ZVision *engine, const Common::Path &subname, Audio::SoundHandle handle) :
Subtitle(engine, subname, false),
_handle(handle) {
}
bool AutomaticSubtitle::selfUpdate() {
if (_engine->_mixer->isSoundHandleActive(_handle) && _engine->getScriptManager()->getStateValue(StateKey_Subtitles) == 1)
return update(_engine->_mixer->getSoundElapsedTime(_handle) / 100);
else {
_toDelete = true;
return false;
}
}
bool AutomaticSubtitle::process(int32 deltatime) {
Subtitle::process(deltatime);
if (!_engine->_mixer->isSoundHandleActive(_handle))
_toDelete = true;
return _toDelete;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,128 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#ifndef ZVISION_SUBTITLES_H
#define ZVISION_SUBTITLES_H
#include "audio/mixer.h"
#include "zvision/zvision.h"
#include "zvision/common/focus_list.h"
namespace ZVision {
class ZVision;
class Subtitle {
friend class SubtitleManager;
public:
Subtitle(ZVision *engine, const Common::Path &subname, bool vob = false); // For scripted subtitles
Subtitle(ZVision *engine, const Common::String &str, const Common::Rect &textArea); // For other text messages
virtual ~Subtitle();
bool update(int32 count); // Return true if necessary to redraw
virtual bool selfUpdate() {
return false;
}
protected:
virtual bool process(int32 deltatime); // Return true if to be deleted
ZVision *_engine;
Common::Rect _textArea;
int16 _timer; // Always in milliseconds; countdown to deletion
bool _toDelete;
bool _redraw;
int16 _lineId;
struct Line {
int start;
int stop;
Common::String subStr;
};
// NB: start & stop do not always use the same units between different instances of this struct!
// Sound effect & music subtitles use milliseconds
// Video subtitle timings are specified in video frames at 15fps, i.e. in multiples of 66.6' milliseconds!
// AVI videos run at 15fps and can have frames counted directly
// DVD videos in VOB format run at 29.97 fps and must be converted to work with the subtitle files, which were made for AVI.
Common::Array<Line> _lines;
};
class AutomaticSubtitle : public Subtitle {
public:
AutomaticSubtitle(ZVision *engine, const Common::Path &subname, Audio::SoundHandle handle); // For scripted audio subtitles
~AutomaticSubtitle() {}
private:
bool process(int32 deltatime); // Return true if to be deleted
bool selfUpdate(); // Return true if necessary to redraw
Audio::SoundHandle _handle;
};
class SubtitleManager {
public:
SubtitleManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS);
~SubtitleManager();
private:
ZVision *_engine;
OSystem *_system;
RenderManager *_renderManager;
const Graphics::PixelFormat _pixelFormat;
const Common::Point _textOffset; // Position vector of text area origin relative to working window origin
const Common::Rect _textArea;
bool _redraw;
bool _doubleFPS;
// Internal subtitle ID counter
uint16 _subId;
typedef Common::HashMap<uint16, Subtitle *> SubtitleMap;
// Subtitle list
SubtitleMap _subsList;
// Subtitle focus history
FocusList<uint16> _subsFocus;
public:
// Update all subtitle objects' deletion timers, delete expired subtitles, & redraw most recent. Does NOT update any subtitle's count value or displayed string!
void process(int32 deltatime); // deltatime is always milliseconds
// Update counter value of referenced subtitle id & set current line to display, if any.
void update(int32 count, uint16 subid); // Count is milliseconds for sound & music; frames for video playback.
const Common::Point &getTextOffset() const {
return _textOffset;
}
// Create subtitle object and return ID
uint16 create(const Common::Path &subname, bool vob = false);
uint16 create(const Common::Path &subname, Audio::SoundHandle handle); // NB this creates an automatic subtitle
uint16 create(const Common::String &str);
// Delete subtitle object by ID
void destroy(uint16 id);
void destroy(uint16 id, int16 delay);
bool askQuestion(const Common::String &str, bool streaming = false, bool safeDefault = false);
void delayedMessage(const Common::String &str, uint16 milsecs);
void timedMessage(const Common::String &str, uint16 milsecs);
void showDebugMsg(const Common::String &msg, int16 delay = 3000);
};
}
#endif

View File

@@ -0,0 +1,539 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/debug.h"
#include "common/file.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/tokenizer.h"
#include "graphics/font.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
#include "graphics/fonts/ttf.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/scripting/script_manager.h"
#include "zvision/text/text.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
TextStyleState::TextStyleState() {
_fontname = "Arial";
_blue = 255;
_green = 255;
_red = 255;
_bold = false;
#if 0
_newline = false;
_escapement = 0;
#endif
_italic = false;
_justification = TEXT_JUSTIFY_LEFT;
_size = 12;
#if 0
_skipcolor = false;
#endif
_strikeout = false;
_underline = false;
_statebox = 0;
_sharp = false;
}
TextChange TextStyleState::parseStyle(const Common::String &str, int16 len) {
Common::String buf = Common::String(str.c_str(), len);
uint retval = TEXT_CHANGE_NONE;
Common::StringTokenizer tokenizer(buf, " ");
Common::String token;
while (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("font", true)) {
token = tokenizer.nextToken();
if (token[0] == '"') {
Common::String _tmp = Common::String(token.c_str() + 1);
while (token.lastChar() != '"' && !tokenizer.empty()) {
token = tokenizer.nextToken();
_tmp += " " + token;
}
if (_tmp.lastChar() == '"')
_tmp.deleteLastChar();
_fontname = _tmp;
} else {
if (!tokenizer.empty())
_fontname = token;
}
retval |= TEXT_CHANGE_FONT_TYPE;
} else if (token.matchString("blue", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_blue != tmp) {
_blue = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("red", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_red != tmp) {
_red = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("green", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_green != tmp) {
_green = tmp;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
} else if (token.matchString("newline", true)) {
#if 0
if ((retval & TXT_RET_NEWLN) == 0)
_newline = 0;
_newline++;
#endif
retval |= TEXT_CHANGE_NEWLINE;
} else if (token.matchString("point", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
int32 tmp = atoi(token.c_str());
if (_size != tmp) {
_size = tmp;
retval |= TEXT_CHANGE_FONT_TYPE;
}
}
} else if (token.matchString("escapement", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
#if 0
int32 tmp = atoi(token.c_str());
_escapement = tmp;
#endif
}
} else if (token.matchString("italic", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_italic != true) {
_italic = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_italic != false) {
_italic = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("underline", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_underline != true) {
_underline = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_underline != false) {
_underline = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("strikeout", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_strikeout != true) {
_strikeout = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_strikeout != false) {
_strikeout = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("bold", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("on", true)) {
if (_bold != true) {
_bold = true;
retval |= TEXT_CHANGE_FONT_STYLE;
}
} else if (token.matchString("off", true)) {
if (_bold != false) {
_bold = false;
retval |= TEXT_CHANGE_FONT_STYLE;
}
}
}
} else if (token.matchString("skipcolor", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
#if 0
if (token.matchString("on", true)) {
_skipcolor = true;
} else if (token.matchString("off", true)) {
_skipcolor = false;
}
#endif
}
} else if (token.matchString("image", true)) {
// Not used
} else if (token.matchString("statebox", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
_statebox = atoi(token.c_str());
retval |= TEXT_CHANGE_HAS_STATE_BOX;
}
} else if (token.matchString("justify", true)) {
if (!tokenizer.empty()) {
token = tokenizer.nextToken();
if (token.matchString("center", true))
_justification = TEXT_JUSTIFY_CENTER;
else if (token.matchString("left", true))
_justification = TEXT_JUSTIFY_LEFT;
else if (token.matchString("right", true))
_justification = TEXT_JUSTIFY_RIGHT;
}
}
}
return (TextChange)retval;
}
void TextStyleState::readAllStyles(const Common::String &txt) {
int16 startTextPosition = -1;
int16 endTextPosition = -1;
for (uint16 i = 0; i < txt.size(); i++) {
if (txt[i] == '<')
startTextPosition = i;
else if (txt[i] == '>') {
endTextPosition = i;
if (startTextPosition != -1) {
if ((endTextPosition - startTextPosition - 1) > 0) {
parseStyle(Common::String(txt.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
}
}
}
}
}
void TextStyleState::updateFontWithTextState(StyledTTFont &font) {
uint tempStyle = 0;
if (_bold) {
tempStyle |= StyledTTFont::TTF_STYLE_BOLD;
}
if (_italic) {
tempStyle |= StyledTTFont::TTF_STYLE_ITALIC;
}
if (_underline) {
tempStyle |= StyledTTFont::TTF_STYLE_UNDERLINE;
}
if (_strikeout) {
tempStyle |= StyledTTFont::TTF_STYLE_STRIKETHROUGH;
}
if (_sharp) {
tempStyle |= StyledTTFont::TTF_STYLE_SHARP;
}
font.loadFont(_fontname, _size, tempStyle);
}
void TextRenderer::drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification justify) {
switch (justify) {
case TEXT_JUSTIFY_LEFT :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignLeft);
break;
case TEXT_JUSTIFY_CENTER :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignCenter);
break;
case TEXT_JUSTIFY_RIGHT :
font.drawString(&dest, text, 0, lineY, dest.w, color, Graphics::kTextAlignRight);
break;
}
}
int32 TextRenderer::drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest) {
StyledTTFont font(_engine);
state.updateFontWithTextState(font);
uint32 color = _engine->_resourcePixelFormat.RGBToColor(state._red, state._green, state._blue);
drawTextWithJustification(text, font, color, dest, 0, state._justification);
return font.getStringWidth(text);
}
struct TextSurface {
TextSurface(Graphics::Surface *surface, Common::Point surfaceOffset, uint lineNumber)
: _surface(surface),
_surfaceOffset(surfaceOffset),
_lineNumber(lineNumber) {
}
Graphics::Surface *_surface;
Common::Point _surfaceOffset;
uint _lineNumber;
};
void TextRenderer::drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest, bool blackFrame) {
Common::Array<TextSurface> textSurfaces;
Common::Array<uint> lineWidths;
Common::Array<TextJustification> lineJustifications;
// Create the initial text state
TextStyleState currentState;
// Create an empty font and bind it to the state
StyledTTFont font(_engine);
currentState.updateFontWithTextState(font);
Common::String currentSentence; // Not a true 'grammatical' sentence. Rather, it's just a collection of words
Common::String currentWord;
int sentenceWidth = 0;
int wordWidth = 0;
int lineWidth = 0;
int lineHeight = font.getFontHeight();
uint currentLineNumber = 0u;
uint numSpaces = 0u;
int spaceWidth = 0;
// The pixel offset to the currentSentence
Common::Point sentencePixelOffset;
uint i = 0u;
uint stringlen = text.size();
// Parse entirety of supplied text
while (i < stringlen) {
// Style tag encountered?
if (text[i] == '<') {
// Flush the currentWord to the currentSentence
currentSentence += currentWord;
sentenceWidth += wordWidth;
// Reset the word variables
currentWord.clear();
wordWidth = 0;
// Parse the style tag
uint startTextPosition = i;
while (i < stringlen && text[i] != '>') {
++i;
}
uint endTextPosition = i;
uint32 textColor = currentState.getTextColor(_engine);
uint stateChanges = 0u;
if ((endTextPosition - startTextPosition - 1) > 0) {
stateChanges = currentState.parseStyle(Common::String(text.c_str() + startTextPosition + 1), endTextPosition - startTextPosition - 1);
}
if (stateChanges & (TEXT_CHANGE_FONT_TYPE | TEXT_CHANGE_FONT_STYLE)) {
// Use the last state to render out the current sentence
// Styles apply to the text 'after' them
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
lineWidth += sentenceWidth;
sentencePixelOffset.x += sentenceWidth;
// Reset the sentence variables
currentSentence.clear();
sentenceWidth = 0;
}
// Update the current state with the style information
currentState.updateFontWithTextState(font);
lineHeight = MAX(lineHeight, font.getFontHeight());
spaceWidth = font.getCharWidth(' ');
}
if (stateChanges & TEXT_CHANGE_NEWLINE) {
// If the current sentence has content, render it out
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
}
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
if (stateChanges & TEXT_CHANGE_HAS_STATE_BOX) {
Common::String temp = Common::String::format("%d", _engine->getScriptManager()->getStateValue(currentState._statebox));
wordWidth += font.getStringWidth(temp);
// If the word causes the line to overflow, render the sentence and start a new line
if (lineWidth + sentenceWidth + wordWidth > dest.w) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, textColor), sentencePixelOffset, currentLineNumber));
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
}
} else {
currentWord += text[i];
wordWidth += font.getCharWidth(text[i]);
if (text[i] == ' ') {
// When we hit the first space, flush the current word to the sentence
if (!currentWord.empty()) {
currentSentence += currentWord;
sentenceWidth += wordWidth;
currentWord.clear();
wordWidth = 0;
}
// We track the number of spaces so we can disregard their width in lineWidth calculations
++numSpaces;
} else {
// If the word causes the line to overflow, render the sentence and start a new line
if (lineWidth + sentenceWidth + wordWidth > dest.w) {
// Only render out content
if (!currentSentence.empty()) {
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
}
// Set line width
lineWidths.push_back(lineWidth + sentenceWidth - (numSpaces * spaceWidth));
currentSentence.clear();
sentenceWidth = 0;
// Update the offsets
sentencePixelOffset.x = 0u;
sentencePixelOffset.y += lineHeight;
// Reset the line variables
lineHeight = font.getFontHeight();
lineWidth = 0;
++currentLineNumber;
lineJustifications.push_back(currentState._justification);
}
numSpaces = 0u;
}
}
i++;
}
// Render out any remaining words/sentences
if (!currentWord.empty() || !currentSentence.empty()) {
currentSentence += currentWord;
sentenceWidth += wordWidth;
textSurfaces.push_back(TextSurface(font.renderSolidText(currentSentence, currentState.getTextColor(_engine)), sentencePixelOffset, currentLineNumber));
}
lineWidths.push_back(lineWidth + sentenceWidth);
lineJustifications.push_back(currentState._justification);
for (Common::Array<TextSurface>::iterator iter = textSurfaces.begin(); iter != textSurfaces.end(); ++iter) {
Common::Rect empty;
int16 Xpos = iter->_surfaceOffset.x;
switch (lineJustifications[iter->_lineNumber]) {
case TEXT_JUSTIFY_LEFT :
break;
case TEXT_JUSTIFY_CENTER :
Xpos += ((dest.w - lineWidths[iter->_lineNumber]) / 2);
break;
case TEXT_JUSTIFY_RIGHT :
Xpos += dest.w - lineWidths[iter->_lineNumber];
break;
}
if (blackFrame)
_engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, Xpos, iter->_surfaceOffset.y);
else
_engine->getRenderManager()->blitSurfaceToSurface(*iter->_surface, empty, dest, Xpos, iter->_surfaceOffset.y, 0);
// Release memory
iter->_surface->free();
delete iter->_surface;
}
}
Common::U32String readWideLine(Common::SeekableReadStream &stream) {
Common::U32String asciiString;
while (true) {
uint32 value = stream.readUint16LE();
if (stream.eos())
break;
// Check for CRLF
if (value == 0x0A0D) {
// Read in the extra NULL char
stream.readByte(); // \0
// End of the line. Break
break;
}
asciiString += value;
}
return asciiString;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,89 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*
*/
#ifndef ZVISION_TEXT_H
#define ZVISION_TEXT_H
#include "zvision/zvision.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
class ZVision;
enum TextJustification {
TEXT_JUSTIFY_CENTER = 0,
TEXT_JUSTIFY_LEFT = 1,
TEXT_JUSTIFY_RIGHT = 2
};
enum TextChange {
TEXT_CHANGE_NONE = 0x0,
TEXT_CHANGE_FONT_TYPE = 0x1,
TEXT_CHANGE_FONT_STYLE = 0x2,
TEXT_CHANGE_NEWLINE = 0x4,
TEXT_CHANGE_HAS_STATE_BOX = 0x8
};
class TextStyleState {
public:
TextStyleState();
TextChange parseStyle(const Common::String &str, int16 len);
void readAllStyles(const Common::String &txt);
void updateFontWithTextState(StyledTTFont &font);
uint32 getTextColor(ZVision *engine) {
return engine->_resourcePixelFormat.RGBToColor(_red, _green, _blue);
}
public:
Common::String _fontname;
TextJustification _justification;
int16 _size;
uint8 _red; // 0-255
uint8 _green; // 0-255
uint8 _blue; // 0-255
bool _italic;
bool _bold;
bool _underline;
bool _strikeout;
int32 _statebox;
bool _sharp;
};
class TextRenderer {
public:
TextRenderer(ZVision *engine): _engine(engine) {};
void drawTextWithJustification(const Common::String &text, StyledTTFont &font, uint32 color, Graphics::Surface &dest, int lineY, TextJustification jusification);
int32 drawText(const Common::String &text, TextStyleState &state, Graphics::Surface &dest);
void drawTextWithWordWrapping(const Common::String &text, Graphics::Surface &dest, bool blackFrame = false);
private:
ZVision *_engine;
};
Common::U32String readWideLine(Common::SeekableReadStream &stream);
} // End of namespace ZVision
#endif

View File

@@ -0,0 +1,229 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "common/config-manager.h"
#include "common/debug.h"
#include "common/file.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/unicode-bidi.h"
#include "common/ustr.h"
#include "common/compression/unzip.h"
#include "graphics/font.h"
#include "graphics/surface.h"
#include "graphics/fonts/ttf.h"
#include "zvision/zvision.h"
#include "zvision/graphics/render_manager.h"
#include "zvision/text/truetype_font.h"
namespace ZVision {
const FontStyle systemFonts[] = {
{ "*times new roman*", "times", "LiberationSerif" },
{ "*times*", "times", "LiberationSerif" },
{ "*century schoolbook*", "censcbk", "LiberationSerif" },
{ "*garamond*", "gara", "LiberationSerif" },
{ "*courier new*", "cour", "LiberationMono" },
{ "*courier*", "cour", "LiberationMono" },
{ "*ZorkDeath*", "cour", "LiberationMono" },
{ "*arial*", "arial", "LiberationSans" },
{ "*ZorkNormal*", "arial", "LiberationSans" }
};
const FontStyle getSystemFont(int fontIndex) {
return systemFonts[fontIndex];
}
StyledTTFont::StyledTTFont(ZVision *engine) {
_engine = engine;
_style = 0;
_font = nullptr;
_lineHeight = 0;
}
StyledTTFont::~StyledTTFont() {
delete _font;
}
bool StyledTTFont::loadFont(const Common::String &fontName, int32 point, uint style) {
// Don't re-load the font if we've already loaded it
// We have to check for empty so we can default to Arial
if (!fontName.empty() && _fontName.equalsIgnoreCase(fontName) && _lineHeight == point && _style == style) {
return true;
}
_style = style;
Common::String newFontName;
Common::String liberationFontName;
for (int i = 0; i < FONT_COUNT; i++) {
FontStyle curFont = getSystemFont(i);
if (fontName.matchString(curFont.zorkFont, true)) {
newFontName = curFont.fontBase;
liberationFontName = curFont.liberationFontBase;
if ((_style & TTF_STYLE_BOLD) && (_style & TTF_STYLE_ITALIC)) {
newFontName += "bi";
liberationFontName += "-BoldItalic";
} else if (_style & TTF_STYLE_BOLD) {
newFontName += "bd";
liberationFontName += "-Bold";
} else if (_style & TTF_STYLE_ITALIC) {
newFontName += "i";
liberationFontName += "-Italic";
} else {
liberationFontName += "-Regular";
}
newFontName += ".ttf";
liberationFontName += ".ttf";
break;
}
}
if (newFontName.empty()) {
warning("Could not identify font: %s. Reverting to Arial", fontName.c_str());
newFontName = "arial.ttf";
liberationFontName = "LiberationSans-Regular.ttf";
}
bool sharp = (_style & TTF_STYLE_SHARP) == TTF_STYLE_SHARP;
Common::File *file = new Common::File();
Graphics::Font *newFont;
if (!file->open(Common::Path(newFontName)) &&
!file->open(Common::Path(liberationFontName))) {
newFont = Graphics::loadTTFFontFromArchive(liberationFontName, point, Graphics::kTTFSizeModeCell, 0, 0, (sharp ? Graphics::kTTFRenderModeMonochrome : Graphics::kTTFRenderModeNormal));
delete file;
} else {
newFont = Graphics::loadTTFFont(file, DisposeAfterUse::YES, point, Graphics::kTTFSizeModeCell, 0, 0, (sharp ? Graphics::kTTFRenderModeMonochrome : Graphics::kTTFRenderModeNormal));
}
if (newFont == nullptr) {
return false;
}
delete _font;
_font = newFont;
_fontName = fontName;
_lineHeight = point;
return true;
}
int StyledTTFont::getFontHeight() {
if (_font)
return _font->getFontHeight();
return 0;
}
int StyledTTFont::getMaxCharWidth() {
if (_font)
return _font->getMaxCharWidth();
return 0;
}
int StyledTTFont::getCharWidth(uint16 chr) {
if (_font)
return _font->getCharWidth(chr);
return 0;
}
int StyledTTFont::getKerningOffset(byte left, byte right) {
if (_font)
return _font->getKerningOffset(left, right);
return 0;
}
void StyledTTFont::drawChar(Graphics::Surface *dst, uint16 chr, int x, int y, uint32 color) {
if (_font) {
_font->drawChar(dst, chr, x, y, color);
if (_style & TTF_STYLE_UNDERLINE) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.87);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color);
}
if (_style & TTF_STYLE_STRIKETHROUGH) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.60);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(x, y + pos, x + _font->getCharWidth(chr), y + pos + thk), color);
}
}
}
void StyledTTFont::drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align) {
if (_font) {
Common::U32String u32str = Common::convertUtf8ToUtf32(str);
_font->drawString(dst, Common::convertBiDiU32String(u32str).visual, x, y, w, color, align);
if (_style & TTF_STYLE_UNDERLINE) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.87);
int16 wd = MIN(_font->getStringWidth(u32str), w);
int16 stX = x;
if (align == Graphics::kTextAlignCenter)
stX += (w - wd) / 2;
else if (align == Graphics::kTextAlignRight)
stX += (w - wd);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color);
}
if (_style & TTF_STYLE_STRIKETHROUGH) {
int16 pos = (int16)floor(_font->getFontHeight() * 0.60);
int16 wd = MIN(_font->getStringWidth(u32str), w);
int16 stX = x;
if (align == Graphics::kTextAlignCenter)
stX += (w - wd) / 2;
else if (align == Graphics::kTextAlignRight)
stX += (w - wd);
int thk = MAX((int)(_font->getFontHeight() * 0.05), 1);
dst->fillRect(Common::Rect(stX, y + pos, stX + wd, y + pos + thk), color);
}
}
}
int StyledTTFont::getStringWidth(const Common::String &str) {
if (_font)
return _font->getStringWidth(str);
return 0;
}
Graphics::Surface *StyledTTFont::renderSolidText(const Common::String &str, uint32 color) {
Graphics::Surface *tmp = new Graphics::Surface;
if (_font) {
int16 w = _font->getStringWidth(str);
if (w && w < 1024) {
tmp->create(w, _font->getFontHeight(), _engine->_resourcePixelFormat);
drawString(tmp, str, 0, 0, w, color);
}
}
return tmp;
}
} // End of namespace ZVision

View File

@@ -0,0 +1,89 @@
/* 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 <http://www.gnu.org/licenses/>.
*
*/
// This file is based on engines/wintermute/base/fonts/base_font_truetype.h/.cpp
#ifndef ZVISION_TRUETYPE_FONT_H
#define ZVISION_TRUETYPE_FONT_H
#include "graphics/font.h"
#include "graphics/pixelformat.h"
namespace Graphics {
struct Surface;
}
namespace ZVision {
struct FontStyle {
const char *zorkFont;
const char *fontBase;
const char *liberationFontBase;
};
#define FONT_COUNT 9
class ZVision;
// Styled TTF
class StyledTTFont {
public:
StyledTTFont(ZVision *engine);
~StyledTTFont();
enum {
TTF_STYLE_BOLD = 0x01,
TTF_STYLE_ITALIC = 0x02,
TTF_STYLE_UNDERLINE = 0x04,
TTF_STYLE_STRIKETHROUGH = 0x08,
TTF_STYLE_SHARP = 0x10
};
private:
ZVision *_engine;
Graphics::Font *_font;
int _lineHeight;
uint _style;
Common::String _fontName;
public:
bool loadFont(const Common::String &fontName, int32 point, uint style);
int getFontHeight();
int getMaxCharWidth();
int getCharWidth(uint16 chr);
int getKerningOffset(byte left, byte right);
void drawChar(Graphics::Surface *dst, uint16 chr, int x, int y, uint32 color);
void drawString(Graphics::Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, Graphics::TextAlign align = Graphics::kTextAlignLeft);
int getStringWidth(const Common::String &str);
Graphics::Surface *renderSolidText(const Common::String &str, uint32 color);
bool isLoaded() {
return _font != NULL;
};
};
} // End of namespace ZVision
#endif