Initial commit
This commit is contained in:
65
engines/zvision/text/string_manager.cpp
Normal file
65
engines/zvision/text/string_manager.cpp
Normal 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
|
||||
66
engines/zvision/text/string_manager.h
Normal file
66
engines/zvision/text/string_manager.h
Normal 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
|
||||
430
engines/zvision/text/subtitle_manager.cpp
Normal file
430
engines/zvision/text/subtitle_manager.cpp
Normal 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
|
||||
128
engines/zvision/text/subtitle_manager.h
Normal file
128
engines/zvision/text/subtitle_manager.h
Normal 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
|
||||
539
engines/zvision/text/text.cpp
Normal file
539
engines/zvision/text/text.cpp
Normal 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
|
||||
89
engines/zvision/text/text.h
Normal file
89
engines/zvision/text/text.h
Normal 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
|
||||
229
engines/zvision/text/truetype_font.cpp
Normal file
229
engines/zvision/text/truetype_font.cpp
Normal 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
|
||||
89
engines/zvision/text/truetype_font.h
Normal file
89
engines/zvision/text/truetype_font.h
Normal 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
|
||||
Reference in New Issue
Block a user