mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-22 10:22:52 +01:00
Merge pull request #528 from madebr/fonts
Convert utf8 string to GameStrings using current font mapping + add string viewer to rwviewer
This commit is contained in:
commit
0743ac9681
@ -37,8 +37,6 @@ set(RWENGINE_SOURCES
|
||||
src/data/Chase.hpp
|
||||
src/data/CollisionModel.hpp
|
||||
src/data/CutsceneData.hpp
|
||||
src/data/GameTexts.cpp
|
||||
src/data/GameTexts.hpp
|
||||
src/data/InstanceData.hpp
|
||||
src/data/ModelData.cpp
|
||||
src/data/ModelData.hpp
|
||||
|
@ -1,9 +0,0 @@
|
||||
#include "GameTexts.hpp"
|
||||
|
||||
GameString GameStringUtil::fromString(const std::string& str) {
|
||||
GameString s;
|
||||
for (const char &i : str) {
|
||||
s += i;
|
||||
}
|
||||
return s;
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
#ifndef _RWENGINE_GAMETEXTS_HPP_
|
||||
#define _RWENGINE_GAMETEXTS_HPP_
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
/**
|
||||
* Each GXT char is just a 16-bit index into the font map.
|
||||
*/
|
||||
using GameStringChar = uint16_t;
|
||||
/**
|
||||
* The game stores strings as 16-bit indexes into the font
|
||||
* texture, which is something simllar to ASCII.
|
||||
*/
|
||||
using GameString = std::basic_string<GameStringChar>;
|
||||
/**
|
||||
* GXT keys are just 8 single byte chars.
|
||||
* Keys are small so should be subject to SSO
|
||||
*/
|
||||
using GameStringKey = std::string;
|
||||
|
||||
namespace GameStringUtil {
|
||||
/**
|
||||
* @brief fromString Converts a string to a GameString
|
||||
*
|
||||
* Encoding of GameStrings depends on the font, only simple ASCII chars will map
|
||||
* well
|
||||
*/
|
||||
GameString fromString(const std::string& str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the encoding of symbols is arbitrary, these constants should be used in
|
||||
* hard-coded strings containing symbols outside of the ASCII-subset supported
|
||||
* by
|
||||
* all fonts
|
||||
*/
|
||||
namespace GameSymbols {
|
||||
static constexpr GameStringChar Money = '$';
|
||||
static constexpr GameStringChar Heart = '{';
|
||||
static constexpr GameStringChar Armour = '[';
|
||||
static constexpr GameStringChar Star = ']';
|
||||
}
|
||||
|
||||
class GameTexts {
|
||||
using StringTable = std::unordered_map<GameStringKey, GameString>;
|
||||
StringTable m_strings;
|
||||
|
||||
public:
|
||||
void addText(const GameStringKey& id, GameString&& text) {
|
||||
m_strings.emplace(id, text);
|
||||
}
|
||||
|
||||
GameString text(const GameStringKey& id) const {
|
||||
auto a = m_strings.find(id);
|
||||
if (a != m_strings.end()) {
|
||||
return a->second;
|
||||
}
|
||||
return GameStringUtil::fromString("MISSING: " + id);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
@ -18,11 +18,11 @@
|
||||
#include <rw/forward.hpp>
|
||||
|
||||
#include <data/AnimGroup.hpp>
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <data/ModelData.hpp>
|
||||
#include <data/PedData.hpp>
|
||||
#include <data/Weather.hpp>
|
||||
#include <data/ZoneData.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <loaders/LoaderDFF.hpp>
|
||||
#include <loaders/LoaderIMG.hpp>
|
||||
#include <loaders/LoaderTXD.hpp>
|
||||
|
@ -9,15 +9,13 @@
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <data/VehicleGenerator.hpp>
|
||||
#include <engine/GameData.hpp>
|
||||
#include <engine/GameInputState.hpp>
|
||||
|
||||
#include <engine/GameWorld.hpp>
|
||||
#include <engine/ScreenText.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <objects/ObjectTypes.hpp>
|
||||
|
||||
#include <script/ScriptTypes.hpp>
|
||||
|
||||
class GameWorld;
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <rw/debug.hpp>
|
||||
|
||||
#include "fonts/FontMapGta3.hpp"
|
||||
|
||||
void ScreenText::tick(float dt) {
|
||||
int millis = dt * 1000;
|
||||
|
||||
@ -35,7 +37,7 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
case 1:
|
||||
return {str,
|
||||
{320.f, 252.f},
|
||||
1,
|
||||
FONT_PRICEDOWN,
|
||||
50,
|
||||
{2, 0, 0, 0},
|
||||
{58, 119, 133},
|
||||
@ -54,7 +56,7 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
case 2:
|
||||
return {str,
|
||||
{620.f, 380.f},
|
||||
1,
|
||||
FONT_PRICEDOWN,
|
||||
30,
|
||||
{2, 3, 0, 0},
|
||||
{214, 171, 9},
|
||||
@ -73,7 +75,7 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
case 3:
|
||||
return {str,
|
||||
{320.f, 400.f},
|
||||
1,
|
||||
FONT_PRICEDOWN,
|
||||
50,
|
||||
{5, 5, 0, 0},
|
||||
{169, 123, 88}, /// @todo verify
|
||||
@ -93,7 +95,7 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
case 5:
|
||||
return {str,
|
||||
{320.f, 176.f},
|
||||
2,
|
||||
FONT_ARIAL,
|
||||
50,
|
||||
((style == 4) ? glm::u8vec4({2, 2, 0, 0})
|
||||
: glm::u8vec4({-2, -2, 0, 0})),
|
||||
@ -113,7 +115,7 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
case 6:
|
||||
return {str,
|
||||
{320.f, 240.f},
|
||||
2,
|
||||
FONT_ARIAL,
|
||||
50,
|
||||
{2, 2, 0, 0},
|
||||
{152, 89, 39},
|
||||
@ -128,9 +130,9 @@ ScreenTextEntry ScreenTextEntry::makeBig(const GameStringKey& id,
|
||||
break;
|
||||
}
|
||||
|
||||
return {GameStringUtil::fromString("Error, style " + std::to_string(style)),
|
||||
return {GameStringUtil::fromString("Error, style " + std::to_string(style), FONT_PRICEDOWN),
|
||||
{320.f, 400.f},
|
||||
2,
|
||||
FONT_ARIAL,
|
||||
50,
|
||||
{20, 20, 0, 0},
|
||||
{20, 20, 200},
|
||||
@ -152,7 +154,7 @@ ScreenTextEntry ScreenTextEntry::makeHighPriority(const GameStringKey& id,
|
||||
// @todo verify: Size: 15 Pixel high letters ('S', 'l')
|
||||
return {str,
|
||||
{320.f, 420.f},
|
||||
2,
|
||||
FONT_ARIAL,
|
||||
18,
|
||||
{1, 0, 0, 0},
|
||||
{255, 255, 255},
|
||||
@ -165,7 +167,7 @@ ScreenTextEntry ScreenTextEntry::makeHighPriority(const GameStringKey& id,
|
||||
|
||||
ScreenTextEntry ScreenTextEntry::makeHelp(const GameStringKey& id,
|
||||
const GameString& str) {
|
||||
return {str, {20.f, 20.f}, 2, 18, {0, 0, 0, 255}, {255, 255, 255}, 0, 5000,
|
||||
return {str, {20.f, 20.f}, FONT_ARIAL, 18, {0, 0, 0, 255}, {255, 255, 255}, 0, 5000,
|
||||
0, 35, id};
|
||||
}
|
||||
|
||||
@ -173,7 +175,7 @@ ScreenTextEntry ScreenTextEntry::makeHiddenPackageText(const GameStringKey& id,
|
||||
const GameString& str) {
|
||||
return {str,
|
||||
{318.f, 138.f},
|
||||
2,
|
||||
FONT_ARIAL,
|
||||
33,
|
||||
{2, 2, 0, 0},
|
||||
{0x59, 0x73, 0x96},
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
|
||||
enum class ScreenTextType {
|
||||
/// Big text will be rendered according to the proscribed style.
|
||||
@ -144,7 +144,7 @@ public:
|
||||
|
||||
template <class... Args>
|
||||
static GameString format(GameString format, Args&&... args) {
|
||||
static auto kReplacementMarker = GameStringUtil::fromString("~1~");
|
||||
static auto kReplacementMarker = GameStringUtil::fromStringCommon("~1~");
|
||||
const std::array<GameString, sizeof...(args)> vals = {{args...}};
|
||||
size_t x = 0, val = 0;
|
||||
// We're only looking for numerical replacement markers
|
||||
|
@ -5,10 +5,9 @@
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <platform/FileHandle.hpp>
|
||||
|
||||
#include "data/GameTexts.hpp"
|
||||
|
||||
void LoaderGXT::load(GameTexts &texts, const FileHandle &file) {
|
||||
auto data = file->data;
|
||||
|
||||
|
@ -405,9 +405,9 @@ bool CollectablePickup::onPlayerTouch() {
|
||||
auto text = ScreenText::format(
|
||||
engine->data->texts.text(gxtEntry),
|
||||
GameStringUtil::fromString(
|
||||
std::to_string(state->playerInfo.hiddenPackagesCollected)),
|
||||
std::to_string(state->playerInfo.hiddenPackagesCollected), FONT_PRICEDOWN),
|
||||
GameStringUtil::fromString(
|
||||
std::to_string(state->playerInfo.hiddenPackageCount)));
|
||||
std::to_string(state->playerInfo.hiddenPackageCount), FONT_PRICEDOWN));
|
||||
|
||||
state->text.addText<ScreenTextType::HiddenPackageText>(
|
||||
ScreenTextEntry::makeHiddenPackageText(gxtEntry, text));
|
||||
|
@ -76,7 +76,7 @@ public:
|
||||
* should be controlled via a different mechanism.
|
||||
*/
|
||||
struct DrawParameters {
|
||||
/// Number of indicies
|
||||
/// Number of indices
|
||||
size_t count{};
|
||||
/// Start index.
|
||||
unsigned int start{};
|
||||
|
@ -18,10 +18,10 @@ int charToIndex(uint16_t g) {
|
||||
return g - 32;
|
||||
}
|
||||
|
||||
glm::vec4 indexToCoord(int font, int index) {
|
||||
glm::vec4 indexToCoord(font_t font, int index) {
|
||||
float x = static_cast<int>(index % 16);
|
||||
float y = static_cast<int>(index / 16) + 0.01f;
|
||||
float fontHeight = ((font == 0) ? 16.f : 13.f);
|
||||
float fontHeight = ((font == FONT_PAGER) ? 16.f : 13.f);
|
||||
glm::vec2 gsize(1.f / 16.f, 1.f / fontHeight);
|
||||
return glm::vec4(x, y, x + 1, y + 0.98f) * glm::vec4(gsize, gsize);
|
||||
}
|
||||
@ -100,31 +100,25 @@ TextRenderer::TextRenderer(GameRenderer* renderer) : renderer(renderer) {
|
||||
glyphData[charToIndex(g)].widthFrac = 0.65f;
|
||||
}
|
||||
|
||||
// Assumes contigious a-z character encoding
|
||||
// Assumes contiguous a-z character encoding
|
||||
for (char g = 0; g <= ('z' - 'a'); g++) {
|
||||
switch (('a' + g)) {
|
||||
case 'i':
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 0.4f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 0.4f;
|
||||
break;
|
||||
case 'l':
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 0.5f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 0.5f;
|
||||
break;
|
||||
case 'm':
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 1.0f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 1.0f;
|
||||
break;
|
||||
case 'w':
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 1.0f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 1.0f;
|
||||
break;
|
||||
default:
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 0.7f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 0.7f;
|
||||
break;
|
||||
}
|
||||
glyphData[charToIndex('a' + g)].widthFrac = 0.7f;
|
||||
glyphData[charToIndex('A' + g)].widthFrac = 0.7f;
|
||||
}
|
||||
// case 'i':
|
||||
glyphData[charToIndex('i')].widthFrac = 0.4f;
|
||||
glyphData[charToIndex('I')].widthFrac = 0.4f;
|
||||
// case 'l':
|
||||
glyphData[charToIndex('l')].widthFrac = 0.5f;
|
||||
glyphData[charToIndex('L')].widthFrac = 0.5f;
|
||||
// case 'm':
|
||||
glyphData[charToIndex('m')].widthFrac = 1.0f;
|
||||
glyphData[charToIndex('M')].widthFrac = 1.0f;
|
||||
// case 'w':
|
||||
glyphData[charToIndex('w')].widthFrac = 1.0f;
|
||||
glyphData[charToIndex('W')].widthFrac = 1.0f;
|
||||
// case 'accent aigu'
|
||||
glyphData[0x91].widthFrac = 0.6f;
|
||||
}
|
||||
|
||||
void TextRenderer::setFontTexture(int index, const std::string& texture) {
|
||||
@ -265,9 +259,9 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
geo.emplace_back(glm::vec2{p.x + ss.x, p.y + ss.y}, glm::vec2{tex.z, tex.w}, colour);
|
||||
}
|
||||
|
||||
if (ti.align == TextInfo::Right) {
|
||||
if (ti.align == TextInfo::TextAlignment::Right) {
|
||||
alignment.x -= maxWidth;
|
||||
} else if (ti.align == TextInfo::Center) {
|
||||
} else if (ti.align == TextInfo::TextAlignment::Center) {
|
||||
alignment.x -= (maxWidth / 2.f);
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include <gl/DrawBuffer.hpp>
|
||||
#include <gl/GeometryBuffer.hpp>
|
||||
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <render/OpenGLRenderer.hpp>
|
||||
|
||||
#define GAME_FONTS 3
|
||||
@ -30,10 +30,10 @@ public:
|
||||
* @todo Can this be merged with the gamestate text entries?
|
||||
*/
|
||||
struct TextInfo {
|
||||
enum TextAlignemnt { Left = 0, Right = 1, Center = 2 };
|
||||
enum class TextAlignment { Left = 0, Right = 1, Center = 2 };
|
||||
|
||||
/// Font index @see TextRenderer::setFontTexture
|
||||
int font{0};
|
||||
font_t font{FONT_PAGER};
|
||||
/// Message to be displayed (including markup)
|
||||
GameString text;
|
||||
/// On screen position
|
||||
@ -45,7 +45,7 @@ public:
|
||||
/// Background colour
|
||||
glm::u8vec4 backgroundColour{};
|
||||
/// Horizontal Alignment
|
||||
TextAlignemnt align = Left;
|
||||
TextAlignment align = TextAlignment::Left;
|
||||
/// Wrap width
|
||||
int wrapX{0};
|
||||
|
||||
|
@ -4,11 +4,11 @@
|
||||
#include <rw/debug.hpp>
|
||||
|
||||
#include <ai/AIGraphNode.hpp>
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <data/ModelData.hpp>
|
||||
#include <engine/GameData.hpp>
|
||||
#include <engine/GameState.hpp>
|
||||
#include <engine/GameWorld.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <objects/GameObject.hpp>
|
||||
#include <objects/CharacterObject.hpp>
|
||||
#include <objects/VehicleObject.hpp>
|
||||
|
@ -5376,13 +5376,12 @@ void opcode_01e2(const ScriptArguments& args, const ScriptInt arg1, const Script
|
||||
*/
|
||||
void opcode_01e3(const ScriptArguments& args, const ScriptString gxtEntry, const ScriptInt arg2, const ScriptInt time, const ScriptInt style) {
|
||||
auto str =
|
||||
ScreenText::format(
|
||||
script::gxt(args, gxtEntry),
|
||||
GameStringUtil::fromString(std::to_string(arg2)));
|
||||
ScreenText::format(
|
||||
script::gxt(args, gxtEntry),
|
||||
GameStringUtil::fromString(std::to_string(arg2), FONT_PRICEDOWN));
|
||||
args.getState()->text.addText<ScreenTextType::Big>(
|
||||
ScreenTextEntry::makeBig(
|
||||
gxtEntry, str, style, time
|
||||
));
|
||||
ScreenTextEntry::makeBig(
|
||||
gxtEntry, str, style, time));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -9763,8 +9762,8 @@ void opcode_036d(const ScriptArguments& args, const ScriptString gxtEntry, const
|
||||
|
||||
auto str =
|
||||
ScreenText::format(script::gxt(args, gxtEntry),
|
||||
GameStringUtil::fromString(std::to_string(arg2)),
|
||||
GameStringUtil::fromString(std::to_string(arg3)));
|
||||
GameStringUtil::fromString(std::to_string(arg2), FONT_PRICEDOWN),
|
||||
GameStringUtil::fromString(std::to_string(arg3), FONT_PRICEDOWN));
|
||||
|
||||
auto textEntry = ScreenTextEntry::makeBig(gxtEntry, str, style, time);
|
||||
world->state->text.addText<ScreenTextType::Big>(textEntry);
|
||||
|
@ -42,9 +42,9 @@ void drawScriptTimer(GameWorld* world, GameRenderer* render) {
|
||||
float scriptTimerTextY = ui_scriptTimerHeight;
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = 1;
|
||||
ti.font = FONT_PRICEDOWN;
|
||||
ti.size = ui_textSize;
|
||||
ti.align = TextRenderer::TextInfo::Right;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Right;
|
||||
|
||||
{
|
||||
int32_t seconds = *world->state->scriptTimerVariable / 1000;
|
||||
@ -52,7 +52,7 @@ void drawScriptTimer(GameWorld* world, GameRenderer* render) {
|
||||
ss << std::setw(2) << std::setfill('0') << seconds / 60
|
||||
<< std::setw(0) << ":" << std::setw(2) << seconds % 60;
|
||||
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameStringUtil::fromString(ss.str(), ti.font);
|
||||
}
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
@ -107,16 +107,16 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
float wantedY = ui_wantedLevelHeight;
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = 1;
|
||||
ti.font = FONT_PRICEDOWN;
|
||||
ti.size = ui_textSize;
|
||||
ti.align = TextRenderer::TextInfo::Right;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Right;
|
||||
|
||||
{
|
||||
std::stringstream ss;
|
||||
ss << std::setw(2) << std::setfill('0') << world->getHour()
|
||||
<< std::setw(0) << ":" << std::setw(2) << world->getMinute();
|
||||
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameStringUtil::fromString(ss.str(), ti.font);
|
||||
}
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
@ -135,7 +135,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
ss << std::setw(8) << std::setfill('0')
|
||||
<< world->state->playerInfo.displayedMoney;
|
||||
|
||||
ti.text = GameSymbols::Money + GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameSymbols::Money + GameStringUtil::fromString(ss.str(), ti.font);
|
||||
}
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
@ -157,7 +157,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
ss << std::setw(3) << std::setfill('0')
|
||||
<< static_cast<int>(
|
||||
player->getCharacter()->getCurrentState().health);
|
||||
ti.text = GameSymbols::Heart + GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameSymbols::Heart + GameStringUtil::fromString(ss.str(), ti.font);
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
ti.screenPosition = glm::vec2(infoTextX + 1.f, infoTextY + 1.f);
|
||||
@ -174,7 +174,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
ss << std::setw(3) << std::setfill('0')
|
||||
<< static_cast<int>(
|
||||
player->getCharacter()->getCurrentState().armour);
|
||||
ti.text = GameSymbols::Armour + GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameSymbols::Armour + GameStringUtil::fromString(ss.str(), ti.font);
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
ti.screenPosition =
|
||||
@ -249,7 +249,7 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
displayBulletsTotal += slotInfo.bulletsClip;
|
||||
|
||||
ti.text =
|
||||
GameStringUtil::fromString(std::to_string(displayBulletsTotal));
|
||||
GameStringUtil::fromString(std::to_string(displayBulletsTotal), ti.font);
|
||||
} else {
|
||||
// Limit the maximal displayed length for the total bullet count
|
||||
if (slotInfo.bulletsTotal > 9999) {
|
||||
@ -258,13 +258,13 @@ void drawPlayerInfo(PlayerController* player, GameWorld* world,
|
||||
|
||||
ti.text = GameStringUtil::fromString(
|
||||
std::to_string(displayBulletsTotal) + "-" +
|
||||
std::to_string(slotInfo.bulletsClip));
|
||||
std::to_string(slotInfo.bulletsClip), ti.font);
|
||||
}
|
||||
|
||||
ti.baseColour = ui_shadowColour;
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.size = ui_ammoSize;
|
||||
ti.align = TextRenderer::TextInfo::Center;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Center;
|
||||
ti.screenPosition = glm::vec2(iconX + ui_weaponSize / 2.f,
|
||||
iconY + ui_weaponSize - ui_ammoHeight);
|
||||
render->text.renderText(ti);
|
||||
@ -284,7 +284,7 @@ void drawOnScreenText(GameWorld* world, GameRenderer* renderer) {
|
||||
const auto vp = glm::vec2(renderer->getRenderer()->getViewport());
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.screenPosition = glm::vec2(10.f, 10.f);
|
||||
ti.size = 20.f;
|
||||
|
||||
@ -299,13 +299,13 @@ void drawOnScreenText(GameWorld* world, GameRenderer* renderer) {
|
||||
ti.screenPosition = (t.position / glm::vec2(640.f, 480.f)) * vp;
|
||||
switch (t.alignment) {
|
||||
case 0:
|
||||
ti.align = TextRenderer::TextInfo::Left;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Left;
|
||||
break;
|
||||
case 1:
|
||||
ti.align = TextRenderer::TextInfo::Center;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Center;
|
||||
break;
|
||||
case 2:
|
||||
ti.align = TextRenderer::TextInfo::Right;
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Right;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -40,13 +40,13 @@ public:
|
||||
|
||||
public:
|
||||
MenuEntry(const std::string& n, const std::function<void(void)>& cb)
|
||||
: text(GameStringUtil::fromString(n)), callback(cb) {
|
||||
: text(GameStringUtil::fromString(n, FONT_PRICEDOWN)), callback(cb) {
|
||||
}
|
||||
MenuEntry(const GameString& n, const std::function<void(void)>& cb)
|
||||
: text(n), callback(cb) {
|
||||
}
|
||||
|
||||
void draw(int font, float size, bool active, GameRenderer* r,
|
||||
void draw(font_t font, float size, bool active, GameRenderer* r,
|
||||
glm::vec2& basis) {
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = font;
|
||||
@ -90,7 +90,7 @@ public:
|
||||
}
|
||||
|
||||
Menu& lambda(const std::string& n, std::function<void(void)> callback) {
|
||||
entries.emplace_back(GameStringUtil::fromString(n), callback);
|
||||
entries.emplace_back(GameStringUtil::fromString(n, FONT_PRICEDOWN), callback);
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
@ -68,9 +68,9 @@ RWGame::RWGame(Logger& log, int argc, char* argv[])
|
||||
}
|
||||
|
||||
// Set up text renderer
|
||||
renderer.text.setFontTexture(0, "pager");
|
||||
renderer.text.setFontTexture(1, "font1");
|
||||
renderer.text.setFontTexture(2, "font2");
|
||||
renderer.text.setFontTexture(FONT_PAGER, "pager");
|
||||
renderer.text.setFontTexture(FONT_PRICEDOWN, "font1");
|
||||
renderer.text.setFontTexture(FONT_ARIAL, "font2");
|
||||
|
||||
debug.setDebugMode(btIDebugDraw::DBG_DrawWireframe |
|
||||
btIDebugDraw::DBG_DrawConstraints |
|
||||
@ -663,8 +663,8 @@ void RWGame::renderDebugStats(float time) {
|
||||
<< "Timescale: " << world->state->basic.timeScale;
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL);
|
||||
ti.screenPosition = glm::vec2(10.f, 10.f);
|
||||
ti.size = 15.f;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
@ -786,8 +786,8 @@ void RWGame::renderDebugObjects(float time, ViewCamera& camera) {
|
||||
<< " Peds: " << world->pedestrianPool.objects.size() << "\n";
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL);
|
||||
ti.screenPosition = glm::vec2(10.f, 10.f);
|
||||
ti.size = 15.f;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
@ -809,7 +809,7 @@ void RWGame::renderDebugObjects(float time, ViewCamera& camera) {
|
||||
if (screen.z >= 1.f) {
|
||||
return;
|
||||
}
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL);
|
||||
screen.y = viewport.w - screen.y;
|
||||
ti.screenPosition = glm::vec2(screen);
|
||||
ti.size = 10.f;
|
||||
@ -863,7 +863,7 @@ void RWGame::renderProfile() {
|
||||
float xscale = renderer.getRenderer()->getViewport().x / upperlimit;
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.align = TextRenderer::TextInfo::Left;
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.size = lineHeight - 2.f;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
std::function<void(const perf::ProfileEntry&, int)> renderEntry =
|
||||
@ -880,14 +880,14 @@ void RWGame::renderProfile() {
|
||||
ti.screenPosition.x = xscale * (event.start);
|
||||
ti.screenPosition.y = y + 2.f;
|
||||
ti.text = GameStringUtil::fromString(
|
||||
event.label + " " + std::to_string(duration) + " us ");
|
||||
event.label + " " + std::to_string(duration) + " us ", ti.font);
|
||||
renderer.text.renderText(ti);
|
||||
renderEntry(event, depth + 1);
|
||||
}
|
||||
};
|
||||
renderEntry(frame, 0);
|
||||
ti.screenPosition = glm::vec2(xscale * (16000), 40.f);
|
||||
ti.text = GameStringUtil::fromString(".16 ms");
|
||||
ti.text = GameStringUtil::fromString(".16 ms", ti.font);
|
||||
renderer.text.renderText(ti);
|
||||
#endif
|
||||
}
|
||||
|
@ -375,8 +375,8 @@ void DebugState::draw(GameRenderer* r) {
|
||||
ss << (zone ? zone->name : "No Zone") << "\n";
|
||||
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.text = GameStringUtil::fromString(ss.str());
|
||||
ti.font = 2;
|
||||
ti.font = FONT_ARIAL;
|
||||
ti.text = GameStringUtil::fromString(ss.str(), ti.font);
|
||||
ti.screenPosition = glm::vec2(10.f, 10.f);
|
||||
ti.size = 15.f;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
|
@ -28,14 +28,14 @@ void LoadingState::handleEvent(const SDL_Event& e) {
|
||||
}
|
||||
|
||||
void LoadingState::draw(GameRenderer* r) {
|
||||
static auto kLoadingString = GameStringUtil::fromString("Loading...");
|
||||
static auto kLoadingString = GameStringUtil::fromString("Loading...", FONT_ARIAL);
|
||||
// Display some manner of loading screen.
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.text = kLoadingString;
|
||||
auto size = r->getRenderer()->getViewport();
|
||||
ti.size = 25.f;
|
||||
ti.screenPosition = glm::vec2(50.f, size.y - ti.size - 50.f);
|
||||
ti.font = 2;
|
||||
ti.font = FONT_PRICEDOWN;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
r->text.renderText(ti);
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ void MenuState::enterLoadMenu() {
|
||||
<< save.basicState.saveTime.day << " "
|
||||
<< save.basicState.saveTime.hour << ":"
|
||||
<< save.basicState.saveTime.minute << " ";
|
||||
auto name = GameStringUtil::fromString(ss.str());
|
||||
auto name = GameStringUtil::fromString(ss.str(), FONT_ARIAL);
|
||||
name += save.basicState.saveName;
|
||||
auto loadsave = [=] {
|
||||
StateManager::get().enter<IngameState>(game, false);
|
||||
|
@ -27,6 +27,15 @@ SET(RWLIB_SOURCES
|
||||
source/data/Clump.hpp
|
||||
source/data/Clump.cpp
|
||||
|
||||
source/fonts/FontMap.cpp
|
||||
source/fonts/FontMap.hpp
|
||||
source/fonts/FontMapGta3.cpp
|
||||
source/fonts/FontMapGta3.hpp
|
||||
source/fonts/GameTexts.cpp
|
||||
source/fonts/GameTexts.hpp
|
||||
source/fonts/Unicode.cpp
|
||||
source/fonts/Unicode.hpp
|
||||
|
||||
source/loaders/LoaderIMG.hpp
|
||||
source/loaders/LoaderIMG.cpp
|
||||
source/loaders/RWBinaryStream.hpp
|
||||
|
79
rwlib/source/fonts/FontMap.cpp
Normal file
79
rwlib/source/fonts/FontMap.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include "FontMap.hpp"
|
||||
|
||||
#include <rw/debug.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
/**
|
||||
* Use output operations to create GameStrings (only allows write operations)
|
||||
*/
|
||||
using OGameStringStream = std::basic_ostringstream<GameStringChar>;
|
||||
|
||||
FontMap::FontMap(std::initializer_list<std::reference_wrapper<const gschar_unicode_map_t>> maps) {
|
||||
for (const auto &map : maps) {
|
||||
m_to_unicode.insert(map.get().cbegin(), map.get().cend());
|
||||
for (const auto &m : map.get()) {
|
||||
m_from_unicode[m.second] = m.first;
|
||||
}
|
||||
}
|
||||
const auto &q = m_from_unicode.find(UnicodeValue::UNICODE_QUESTION_MARK);
|
||||
if (q == m_from_unicode.end()) {
|
||||
RW_ERROR("Font does not have a question mark");
|
||||
m_unknown_gschar = ' ';
|
||||
} else {
|
||||
m_unknown_gschar = q->second;
|
||||
}
|
||||
}
|
||||
|
||||
GameStringChar FontMap::to_GameStringChar(unicode_t u) const {
|
||||
if (u < 0x20) {
|
||||
/* Passthrough control characters */
|
||||
return u;
|
||||
}
|
||||
const auto &p = m_from_unicode.find(u);
|
||||
if (p == m_from_unicode.end()) {
|
||||
return m_unknown_gschar;
|
||||
}
|
||||
return p->second;
|
||||
}
|
||||
|
||||
unicode_t FontMap::to_unicode(GameStringChar c) const {
|
||||
if (c < 0x20) {
|
||||
/* Passthrough control characters */
|
||||
return c;
|
||||
}
|
||||
const auto &p = m_to_unicode.find(c);
|
||||
if (p == m_to_unicode.end()) {
|
||||
return UnicodeValue::UNICODE_REPLACEMENT_CHARACTER;
|
||||
}
|
||||
return p->second;
|
||||
}
|
||||
|
||||
std::string FontMap::to_string(const GameString &s) const {
|
||||
std::ostringstream oss;
|
||||
for (GameStringChar c: s) {
|
||||
char buffer[4];
|
||||
unicode_t u = to_unicode(c);
|
||||
auto nb = unicode_to_utf8(u, buffer);
|
||||
oss.write(buffer, nb);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
GameString FontMap::to_GameString(const std::string &utf8) const {
|
||||
OGameStringStream oss;
|
||||
std::istringstream iss(utf8);
|
||||
for (Utf8UnicodeIterator it(iss); it.good(); ++it) {
|
||||
GameStringChar c = to_GameStringChar(it.unicode());
|
||||
oss.write(&c, 1);
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
FontMap::gschar_unicode_iterator FontMap::to_unicode_begin() const {
|
||||
return m_to_unicode.cbegin();
|
||||
}
|
||||
|
||||
FontMap::gschar_unicode_iterator FontMap::to_unicode_end() const {
|
||||
return m_to_unicode.cend();
|
||||
}
|
95
rwlib/source/fonts/FontMap.hpp
Normal file
95
rwlib/source/fonts/FontMap.hpp
Normal file
@ -0,0 +1,95 @@
|
||||
#ifndef _RWLIB_FONTS_FONTMAP_HPP_
|
||||
#define _RWLIB_FONTS_FONTMAP_HPP_
|
||||
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <map>
|
||||
|
||||
#include "GameTexts.hpp"
|
||||
#include "Unicode.hpp"
|
||||
|
||||
/**
|
||||
* @brief Class providing mapping from unicode chars to game strings and vice versa.
|
||||
* The conversions of an object of this class depend on the actual font used.
|
||||
* @param maps
|
||||
*/
|
||||
class FontMap {
|
||||
public:
|
||||
/**
|
||||
* Mapping from GameStringChar to unicode data point.
|
||||
*/
|
||||
using gschar_unicode_map_t = std::map<GameStringChar, unicode_t>;
|
||||
|
||||
/**
|
||||
* Mapping from unicode data point to GameStringChar.
|
||||
*/
|
||||
using unicode_gschar_map_t = std::map<unicode_t, GameStringChar>;
|
||||
|
||||
/**
|
||||
* Iterator type over all GameStringChar to unicode.
|
||||
*/
|
||||
using gschar_unicode_iterator = gschar_unicode_map_t::const_iterator;
|
||||
|
||||
/**
|
||||
* @brief FontMap Create a new Fontmapping using the maps provided.
|
||||
* @param maps List of mappings used as source for this font mapping.
|
||||
*/
|
||||
FontMap(std::initializer_list<std::reference_wrapper<const gschar_unicode_map_t>> maps);
|
||||
|
||||
/**
|
||||
* @brief to_GameStringChar Convert a unicode data point to a GameStringChar.
|
||||
* @param u The unicode character.
|
||||
* @return A GameStringChar
|
||||
*/
|
||||
GameStringChar to_GameStringChar(unicode_t u) const;
|
||||
|
||||
/**
|
||||
* @brief to_unicoe Convert a GameStringChar to a unicode data point.
|
||||
* @param c The GameStringChar
|
||||
* @return A unicode character.
|
||||
*/
|
||||
unicode_t to_unicode(GameStringChar c) const;
|
||||
|
||||
/**
|
||||
* @brief to_string Convert a GameString to a utf-8 encoded string.
|
||||
* @param s The GameString.
|
||||
* @return A utf-8 encoded string.
|
||||
*/
|
||||
std::string to_string(const GameString &s) const;
|
||||
|
||||
/**
|
||||
* @brief to_GameString Convert a utf-8 encoded string to a GameString.
|
||||
* @param utf8 The utf-8 encoded string.
|
||||
* @return A GameString.
|
||||
*/
|
||||
GameString to_GameString(const std::string &utf8) const;
|
||||
|
||||
/**
|
||||
* @brief to_unicode_begin Iterate over the GameStringChar to unicode begin.
|
||||
* @return Iterator to begin.
|
||||
*/
|
||||
gschar_unicode_iterator to_unicode_begin() const;
|
||||
|
||||
/**
|
||||
* @brief to_unicode_begin Iterate over the GameStringChar to unicode end.
|
||||
* @return Iterator Iterator to end.
|
||||
*/
|
||||
gschar_unicode_iterator to_unicode_end() const;
|
||||
private:
|
||||
/**
|
||||
* Mapping from a unicode point to a GameStringChar.
|
||||
*/
|
||||
unicode_gschar_map_t m_from_unicode;
|
||||
|
||||
/**
|
||||
* Mapping from a GameStringChar to a unicode point.
|
||||
*/
|
||||
gschar_unicode_map_t m_to_unicode;
|
||||
|
||||
/**
|
||||
* GameStringChar used if a unicode point has no corresponding GameStringChar.
|
||||
*/
|
||||
GameStringChar m_unknown_gschar;
|
||||
};
|
||||
|
||||
#endif
|
206
rwlib/source/fonts/FontMapGta3.cpp
Normal file
206
rwlib/source/fonts/FontMapGta3.cpp
Normal file
@ -0,0 +1,206 @@
|
||||
#include "FontMapGta3.hpp"
|
||||
|
||||
static const FontMap::gschar_unicode_map_t map_gta3_font_common = {
|
||||
{0x20, UnicodeValue::UNICODE_SPACE},
|
||||
{0x21, UnicodeValue::UNICODE_EXCLAMATION_MARK},
|
||||
{0x24, UnicodeValue::UNICODE_DOLLAR_SIGN},
|
||||
{0x25, UnicodeValue::UNICODE_PROCENT_SIGN},
|
||||
{0x26, UnicodeValue::UNICODE_AMPERSAND},
|
||||
{0x27, UnicodeValue::UNICODE_APOSTROPHE},
|
||||
{0x28, UnicodeValue::UNICODE_LEFT_PARENTHESIS},
|
||||
{0x29, UnicodeValue::UNICODE_RIGHT_PARENTHESIS},
|
||||
{0x2c, UnicodeValue::UNICODE_COMMA},
|
||||
{0x2d, UnicodeValue::UNICODE_HYPHEN_MINUS},
|
||||
{0x2e, UnicodeValue::UNICODE_FULL_STOP},
|
||||
{0x30, UnicodeValue::UNICODE_DIGIT_0},
|
||||
{0x31, UnicodeValue::UNICODE_DIGIT_1},
|
||||
{0x32, UnicodeValue::UNICODE_DIGIT_2},
|
||||
{0x33, UnicodeValue::UNICODE_DIGIT_3},
|
||||
{0x34, UnicodeValue::UNICODE_DIGIT_4},
|
||||
{0x35, UnicodeValue::UNICODE_DIGIT_5},
|
||||
{0x36, UnicodeValue::UNICODE_DIGIT_6},
|
||||
{0x37, UnicodeValue::UNICODE_DIGIT_7},
|
||||
{0x38, UnicodeValue::UNICODE_DIGIT_8},
|
||||
{0x39, UnicodeValue::UNICODE_DIGIT_9},
|
||||
{0x3a, UnicodeValue::UNICODE_COLON},
|
||||
{0x3f, UnicodeValue::UNICODE_QUESTION_MARK},
|
||||
{0x41, UnicodeValue::UNICODE_CAPITAL_A},
|
||||
{0x42, UnicodeValue::UNICODE_CAPITAL_B},
|
||||
{0x43, UnicodeValue::UNICODE_CAPITAL_C},
|
||||
{0x44, UnicodeValue::UNICODE_CAPITAL_D},
|
||||
{0x45, UnicodeValue::UNICODE_CAPITAL_E},
|
||||
{0x46, UnicodeValue::UNICODE_CAPITAL_F},
|
||||
{0x47, UnicodeValue::UNICODE_CAPITAL_G},
|
||||
{0x48, UnicodeValue::UNICODE_CAPITAL_H},
|
||||
{0x49, UnicodeValue::UNICODE_CAPITAL_I},
|
||||
{0x4a, UnicodeValue::UNICODE_CAPITAL_J},
|
||||
{0x4b, UnicodeValue::UNICODE_CAPITAL_K},
|
||||
{0x4c, UnicodeValue::UNICODE_CAPITAL_L},
|
||||
{0x4d, UnicodeValue::UNICODE_CAPITAL_M},
|
||||
{0x4e, UnicodeValue::UNICODE_CAPITAL_N},
|
||||
{0x4f, UnicodeValue::UNICODE_CAPITAL_O},
|
||||
{0x50, UnicodeValue::UNICODE_CAPITAL_P},
|
||||
{0x51, UnicodeValue::UNICODE_CAPITAL_Q},
|
||||
{0x52, UnicodeValue::UNICODE_CAPITAL_R},
|
||||
{0x53, UnicodeValue::UNICODE_CAPITAL_S},
|
||||
{0x54, UnicodeValue::UNICODE_CAPITAL_T},
|
||||
{0x55, UnicodeValue::UNICODE_CAPITAL_U},
|
||||
{0x56, UnicodeValue::UNICODE_CAPITAL_V},
|
||||
{0x57, UnicodeValue::UNICODE_CAPITAL_W},
|
||||
{0x58, UnicodeValue::UNICODE_CAPITAL_X},
|
||||
{0x59, UnicodeValue::UNICODE_CAPITAL_Y},
|
||||
{0x5a, UnicodeValue::UNICODE_CAPITAL_Z},
|
||||
{0x61, UnicodeValue::UNICODE_SMALL_A},
|
||||
{0x62, UnicodeValue::UNICODE_SMALL_B},
|
||||
{0x63, UnicodeValue::UNICODE_SMALL_C},
|
||||
{0x64, UnicodeValue::UNICODE_SMALL_D},
|
||||
{0x65, UnicodeValue::UNICODE_SMALL_E},
|
||||
{0x66, UnicodeValue::UNICODE_SMALL_F},
|
||||
{0x67, UnicodeValue::UNICODE_SMALL_G},
|
||||
{0x68, UnicodeValue::UNICODE_SMALL_H},
|
||||
{0x69, UnicodeValue::UNICODE_SMALL_I},
|
||||
{0x6a, UnicodeValue::UNICODE_SMALL_J},
|
||||
{0x6b, UnicodeValue::UNICODE_SMALL_K},
|
||||
{0x6c, UnicodeValue::UNICODE_SMALL_L},
|
||||
{0x6d, UnicodeValue::UNICODE_SMALL_M},
|
||||
{0x6e, UnicodeValue::UNICODE_SMALL_N},
|
||||
{0x6f, UnicodeValue::UNICODE_SMALL_O},
|
||||
{0x70, UnicodeValue::UNICODE_SMALL_P},
|
||||
{0x71, UnicodeValue::UNICODE_SMALL_Q},
|
||||
{0x72, UnicodeValue::UNICODE_SMALL_R},
|
||||
{0x73, UnicodeValue::UNICODE_SMALL_S},
|
||||
{0x74, UnicodeValue::UNICODE_SMALL_T},
|
||||
{0x75, UnicodeValue::UNICODE_SMALL_U},
|
||||
{0x76, UnicodeValue::UNICODE_SMALL_V},
|
||||
{0x77, UnicodeValue::UNICODE_SMALL_W},
|
||||
{0x78, UnicodeValue::UNICODE_SMALL_X},
|
||||
{0x79, UnicodeValue::UNICODE_SMALL_Y},
|
||||
{0x7a, UnicodeValue::UNICODE_SMALL_Z},
|
||||
{0x80, UnicodeValue::UNICODE_CAPITAL_A_GRAVE},
|
||||
{0x81, UnicodeValue::UNICODE_CAPITAL_A_ACUTE},
|
||||
{0x82, UnicodeValue::UNICODE_CAPITAL_A_CIRCUMFLEX},
|
||||
{0x83, UnicodeValue::UNICODE_CAPITAL_A_DIARESIS},
|
||||
{0x84, UnicodeValue::UNICODE_CAPITAL_AE},
|
||||
{0x85, UnicodeValue::UNICODE_CAPITAL_C_CEDILLA},
|
||||
{0x86, UnicodeValue::UNICODE_CAPITAL_E_GRAVE},
|
||||
{0x87, UnicodeValue::UNICODE_CAPITAL_E_ACUTE},
|
||||
{0x88, UnicodeValue::UNICODE_CAPITAL_E_CIRCUMFLEX},
|
||||
{0x89, UnicodeValue::UNICODE_CAPITAL_E_DIARESIS},
|
||||
{0x8a, UnicodeValue::UNICODE_CAPITAL_I_GRAVE},
|
||||
{0x8b, UnicodeValue::UNICODE_CAPITAL_I_ACUTE},
|
||||
{0x8c, UnicodeValue::UNICODE_CAPITAL_I_CIRCUMFLEX},
|
||||
{0x8d, UnicodeValue::UNICODE_CAPITAL_I_DIARESIS},
|
||||
{0x8e, UnicodeValue::UNICODE_CAPITAL_O_GRAVE},
|
||||
{0x8f, UnicodeValue::UNICODE_CAPITAL_O_ACUTE},
|
||||
{0x90, UnicodeValue::UNICODE_CAPITAL_O_CIRCUMFLEX},
|
||||
{0x91, UnicodeValue::UNICODE_CAPITAL_O_DIARESIS},
|
||||
{0x92, UnicodeValue::UNICODE_CAPITAL_U_GRAVE},
|
||||
{0x93, UnicodeValue::UNICODE_CAPITAL_U_ACUTE},
|
||||
{0x94, UnicodeValue::UNICODE_CAPITAL_U_CIRCUMFLEX},
|
||||
{0x95, UnicodeValue::UNICODE_CAPITAL_U_DIARESIS},
|
||||
{0x96, UnicodeValue::UNICODE_SMALL_SHARP_S},
|
||||
{0x97, UnicodeValue::UNICODE_SMALL_A_GRAVE},
|
||||
{0x98, UnicodeValue::UNICODE_SMALL_A_ACUTE},
|
||||
{0x99, UnicodeValue::UNICODE_SMALL_A_CIRCUMFLEX},
|
||||
{0x9a, UnicodeValue::UNICODE_SMALL_A_DIARESIS},
|
||||
{0x9b, UnicodeValue::UNICODE_SMALL_AE},
|
||||
{0x9c, UnicodeValue::UNICODE_SMALL_C_CEDILLA},
|
||||
{0x9d, UnicodeValue::UNICODE_SMALL_E_GRAVE},
|
||||
{0x9e, UnicodeValue::UNICODE_SMALL_E_ACUTE},
|
||||
{0x9f, UnicodeValue::UNICODE_SMALL_E_CIRCUMFLEX},
|
||||
{0xa0, UnicodeValue::UNICODE_SMALL_E_DIARESIS},
|
||||
{0xa1, UnicodeValue::UNICODE_SMALL_I_GRAVE},
|
||||
{0xa2, UnicodeValue::UNICODE_SMALL_I_ACUTE},
|
||||
{0xa3, UnicodeValue::UNICODE_SMALL_I_CIRCUMFLEX},
|
||||
{0xa4, UnicodeValue::UNICODE_SMALL_I_DIARESIS},
|
||||
{0xa5, UnicodeValue::UNICODE_SMALL_O_GRAVE},
|
||||
{0xa6, UnicodeValue::UNICODE_SMALL_O_ACUTE},
|
||||
{0xa7, UnicodeValue::UNICODE_SMALL_O_CIRCUMFLEX},
|
||||
{0xa8, UnicodeValue::UNICODE_SMALL_O_DIARESIS},
|
||||
{0xa9, UnicodeValue::UNICODE_SMALL_U_GRAVE},
|
||||
{0xaa, UnicodeValue::UNICODE_SMALL_U_ACUTE},
|
||||
{0xab, UnicodeValue::UNICODE_SMALL_U_CIRCUMFLEX},
|
||||
{0xac, UnicodeValue::UNICODE_SMALL_U_DIARESIS},
|
||||
{0xad, UnicodeValue::UNICODE_CAPITAL_N_TILDE},
|
||||
{0xae, UnicodeValue::UNICODE_SMALL_N_TILDE},
|
||||
{0xaf, UnicodeValue::UNICODE_INVERTED_QUESTION_MARK},
|
||||
{0xb0, UnicodeValue::UNICODE_INVERTED_EXCLAMATION_MARK},
|
||||
};
|
||||
|
||||
static const FontMap::gschar_unicode_map_t map_gta3_font_0_priv = {
|
||||
{0x22, UnicodeValue::UNICODE_QUOTATION_MARK},
|
||||
{0x23, UnicodeValue::UNICODE_NUMBER_SIGN},
|
||||
{0x2a, UnicodeValue::UNICODE_HYPHEN_MINUS},
|
||||
/*{0x2b, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x2f, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x3b, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x3c, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x3d, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x3e, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x40, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x5b, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x5c, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x5d, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x5e, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x5f, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x60, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x6b, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x7c, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x7d, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x7f, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
};
|
||||
|
||||
static const FontMap::gschar_unicode_map_t map_gta3_font_1_priv = {
|
||||
{0x22, UnicodeValue::UNICODE_INCREMENT},
|
||||
{0x23, UnicodeValue::UNICODE_REGISTERED_SIGN},
|
||||
/*{0x2a, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
{0x2b, UnicodeValue::UNICODE_PLUS_SIGN},
|
||||
{0x2f, UnicodeValue::UNICODE_MULTIPLICATION_SIGN},
|
||||
{0x3b, UnicodeValue::UNICODE_BLACK_UP_POINTING_TRIANGLE},
|
||||
{0x3c, UnicodeValue::UNICODE_BLACK_LEFT_POINTING_POINTER},
|
||||
{0x3d, UnicodeValue::UNICODE_BLACK_DOWN_POINTING_POINTER},
|
||||
{0x3e, UnicodeValue::UNICODE_BLACK_RIGHT_POINTING_POINTER},
|
||||
{0x40, UnicodeValue::UNICODE_TRADE_MARK},
|
||||
{0x5b, UnicodeValue::UNICODE_SHIELD},
|
||||
{0x5c, UnicodeValue::UNICODE_REVERSE_SOLIDUS},
|
||||
{0x5d, UnicodeValue::UNICODE_BLACK_STAR},
|
||||
{0x5e, UnicodeValue::UNICODE_NUMERO_SIGN},
|
||||
{0x5f, UnicodeValue::UNICODE_DEGREES},
|
||||
{0x60, UnicodeValue::UNICODE_COPYRIGHT_SIGN},
|
||||
{0x7b, UnicodeValue::UNICODE_BLACK_HEART_SUIT},
|
||||
{0x7c, UnicodeValue::UNICODE_WHITE_CIRCLE},
|
||||
/*{0x7d, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x7f, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
};
|
||||
|
||||
static const FontMap::gschar_unicode_map_t map_gta3_font_2_priv = {
|
||||
{0x22, UnicodeValue::UNICODE_INCREMENT},
|
||||
{0x23, UnicodeValue::UNICODE_NUMBER_SIGN},
|
||||
{0x2a, UnicodeValue::UNICODE_ASTERISK},
|
||||
{0x2b, UnicodeValue::UNICODE_PLUS_SIGN},
|
||||
{0x2f, UnicodeValue::UNICODE_SOLIDUS},
|
||||
{0x3b, UnicodeValue::UNICODE_SEMICOLON},
|
||||
{0x3c, UnicodeValue::UNICODE_BLACK_LEFT_POINTING_POINTER},
|
||||
{0x3d, UnicodeValue::UNICODE_EQUALS_SIGN},
|
||||
{0x3e, UnicodeValue::UNICODE_BLACK_RIGHT_POINTING_POINTER},
|
||||
{0x40, UnicodeValue::UNICODE_TRADE_MARK},
|
||||
{0x5b, UnicodeValue::UNICODE_LEFT_SQUARE_BRACKET},
|
||||
{0x5c, UnicodeValue::UNICODE_REVERSE_SOLIDUS},
|
||||
{0x5d, UnicodeValue::UNICODE_RIGHT_SQUARE_BRACKET},
|
||||
{0x5e, UnicodeValue::UNICODE_CIRCUMFLEX_ACCENT},
|
||||
{0x5f, UnicodeValue::UNICODE_DEGREES},
|
||||
{0x60, UnicodeValue::UNICODE_GRAVE_ACCENT},
|
||||
/*{0x7b, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
{0x7c, UnicodeValue::UNICODE_WHITE_CIRCLE},
|
||||
/*{0x7d, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
/*{0x7f, UnicodeValue::UNICODE_REPLACEMENT_CHARACTER},*/
|
||||
{0xb1, UnicodeValue::UNICODE_ACUTE_ACCENT},
|
||||
};
|
||||
|
||||
const FontMap fontmap_gta3_font_common({map_gta3_font_common});
|
||||
|
||||
const std::array<FontMap, 3> fontmaps_gta3_font = {
|
||||
FontMap({map_gta3_font_common, map_gta3_font_0_priv}),
|
||||
FontMap({map_gta3_font_common, map_gta3_font_1_priv}),
|
||||
FontMap({map_gta3_font_common, map_gta3_font_2_priv}),
|
||||
};
|
18
rwlib/source/fonts/FontMapGta3.hpp
Normal file
18
rwlib/source/fonts/FontMapGta3.hpp
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef _RWLIB_FONTS_FONTMAPGTA3_HPP_
|
||||
#define _RWLIB_FONTS_FONTMAPGTA3_HPP_
|
||||
|
||||
#include "FontMap.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
/**
|
||||
* Commong font mapping of all fonts.
|
||||
*/
|
||||
extern const FontMap fontmap_gta3_font_common;
|
||||
|
||||
/**
|
||||
* Array of all font mappings.
|
||||
*/
|
||||
extern const std::array<FontMap, 3> fontmaps_gta3_font;
|
||||
|
||||
#endif
|
19
rwlib/source/fonts/GameTexts.cpp
Normal file
19
rwlib/source/fonts/GameTexts.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
#include "GameTexts.hpp"
|
||||
|
||||
// FIXME: Update for GTA VC
|
||||
#include "FontMapGta3.hpp"
|
||||
|
||||
#include "rw/debug.hpp"
|
||||
|
||||
GameString GameStringUtil::fromString(const std::string& str, font_t font) {
|
||||
RW_ASSERT(font < FONTS_COUNT);
|
||||
return fontmaps_gta3_font[font].to_GameString(str);
|
||||
}
|
||||
|
||||
GameString GameStringUtil::fromStringCommon(const std::string& str) {
|
||||
return fontmap_gta3_font_common.to_GameString(str);
|
||||
}
|
||||
|
||||
std::string GameStringUtil::toString(const GameString& str, font_t font) {
|
||||
return fontmaps_gta3_font[font].to_string(str);
|
||||
}
|
94
rwlib/source/fonts/GameTexts.hpp
Normal file
94
rwlib/source/fonts/GameTexts.hpp
Normal file
@ -0,0 +1,94 @@
|
||||
#ifndef _RWLIB_FONTS_GAMETEXTS_HPP_
|
||||
#define _RWLIB_FONTS_GAMETEXTS_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* Each GXT char is just a 16-bit index into the font map.
|
||||
*/
|
||||
using GameStringChar = std::uint16_t;
|
||||
|
||||
/**
|
||||
* The game stores strings as 16-bit indexes into the font
|
||||
* texture, which is something similar to ASCII.
|
||||
*/
|
||||
using GameString = std::basic_string<GameStringChar>;
|
||||
|
||||
/**
|
||||
* GXT keys are just 8 single byte chars.
|
||||
* Keys are small so should be subject to SSO
|
||||
*/
|
||||
using GameStringKey = std::string;
|
||||
|
||||
/**
|
||||
* Index to used font.
|
||||
*/
|
||||
using font_t = size_t;
|
||||
|
||||
static constexpr font_t FONT_PAGER = 0;
|
||||
static constexpr font_t FONT_PRICEDOWN = 1;
|
||||
static constexpr font_t FONT_ARIAL = 2;
|
||||
static constexpr font_t FONTS_COUNT = 3;
|
||||
|
||||
namespace GameStringUtil {
|
||||
/**
|
||||
* @brief fromString Converts a std::string to a GameString, depending on the font
|
||||
*
|
||||
* Encoding of GameStrings depends on the font. Unknown chars are converted to a "unknown GameStringChar" (such as '?').
|
||||
*/
|
||||
GameString fromString(const std::string& str, font_t font);
|
||||
|
||||
/**
|
||||
* @brief fromString Converts a GameString to a std::string, depending on the font
|
||||
*
|
||||
* Encoding of GameStrings depends on the font. Unknown GameStringChar's are converted to a UNICODE_REPLACEMENT_CHARACTER utf-8 sequence.
|
||||
*/
|
||||
std::string toString(const GameString& str, font_t font);
|
||||
|
||||
/**
|
||||
* @brief fromString Converts a string to a GameString, independent on the font (only characthers known to all fonts are converted)
|
||||
*
|
||||
* Encoding of GameStrings does not depend on the font. Unknown chars are converted to a "unknown GameStringChar" (such as '?').
|
||||
*/
|
||||
GameString fromStringCommon(const std::string& str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the encoding of symbols is arbitrary, these constants should be used in
|
||||
* hard-coded strings containing symbols outside of the ASCII-subset supported
|
||||
* by all fonts
|
||||
*/
|
||||
namespace GameSymbols {
|
||||
static constexpr GameStringChar Money = '$';
|
||||
static constexpr GameStringChar Heart = '{';
|
||||
static constexpr GameStringChar Armour = '[';
|
||||
static constexpr GameStringChar Star = ']';
|
||||
}
|
||||
|
||||
class GameTexts {
|
||||
using StringTable = std::unordered_map<GameStringKey, GameString>;
|
||||
StringTable m_strings;
|
||||
|
||||
public:
|
||||
void addText(const GameStringKey& id, GameString&& text) {
|
||||
m_strings.emplace(id, text);
|
||||
}
|
||||
|
||||
GameString text(const GameStringKey& id) const {
|
||||
auto a = m_strings.find(id);
|
||||
if (a != m_strings.end()) {
|
||||
return a->second;
|
||||
}
|
||||
return GameStringUtil::fromString("MISSING: " + id, FONT_ARIAL);
|
||||
}
|
||||
|
||||
const StringTable &getStringTable() const {
|
||||
return m_strings;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
107
rwlib/source/fonts/Unicode.cpp
Normal file
107
rwlib/source/fonts/Unicode.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include "Unicode.hpp"
|
||||
|
||||
#include <istream>
|
||||
|
||||
size_t unicode_to_utf8(unicode_t unicode, char c[4]) {
|
||||
if (unicode < 0x80) { // 7 bits
|
||||
c[0] = unicode;
|
||||
return 1;
|
||||
} else if (unicode < 0x800) { // 11 bits
|
||||
c[0] = 0xc0 | (unicode >> 6);
|
||||
c[1] = 0x80 | (unicode & 0x3f);
|
||||
return 2;
|
||||
} else if (unicode < 0x10000) { // 16 bits
|
||||
c[0] = 0xe0 | (unicode >> 12);
|
||||
c[1] = 0x80 | ((unicode >> 6) & 0x3f);
|
||||
c[2] = 0x80 | (unicode & 0x3f);
|
||||
return 3;
|
||||
} else if (unicode < 0x110000) { // 21 bits
|
||||
c[0] = 0xf0 | (unicode >> 18);
|
||||
c[1] = 0x80 | ((unicode >> 12) & 0x3f);
|
||||
c[2] = 0x80 | ((unicode >> 6) & 0x3f);
|
||||
c[3] = 0x80 | (unicode & 0x3f);
|
||||
return 4;
|
||||
} else {
|
||||
return unicode_to_utf8(UnicodeValue::UNICODE_REPLACEMENT_CHARACTER, c);
|
||||
}
|
||||
}
|
||||
|
||||
Utf8UnicodeIterator::Utf8UnicodeIterator(std::istream &is) : m_is(&is), m_finished(false) {
|
||||
next_unicode();
|
||||
}
|
||||
|
||||
void Utf8UnicodeIterator::next_unicode() {
|
||||
int c = m_is->get();
|
||||
if (c == EOF) {
|
||||
m_finished = true;
|
||||
return;
|
||||
}
|
||||
char cc = static_cast<char>(c);
|
||||
unicode_t unicode;
|
||||
unsigned nb_bytes;
|
||||
if ((cc & 0x80) == 0x00) {
|
||||
unicode = cc;
|
||||
nb_bytes = 0;
|
||||
} else if ((c & 0xe0) == 0xc0) {
|
||||
unicode = c & 0x1f;
|
||||
nb_bytes = 1;
|
||||
} else if ((c & 0xf0) == 0xe0) {
|
||||
unicode = c & 0x0f;
|
||||
nb_bytes = 2;
|
||||
} else if ((c & 0xf8) == 0xf0) {
|
||||
unicode = c & 0x07;
|
||||
nb_bytes = 3;
|
||||
} else {
|
||||
unicode = UnicodeValue::UNICODE_REPLACEMENT_CHARACTER;
|
||||
nb_bytes = 0;
|
||||
}
|
||||
while (nb_bytes != 0) {
|
||||
c = m_is->get();
|
||||
if (c == EOF) {
|
||||
unicode = UnicodeValue::UNICODE_REPLACEMENT_CHARACTER;
|
||||
m_finished = true;
|
||||
break;
|
||||
}
|
||||
cc = static_cast<char>(c);
|
||||
if ((c & 0xc0) != 0x80) {
|
||||
unicode = UnicodeValue::UNICODE_REPLACEMENT_CHARACTER;
|
||||
break;
|
||||
}
|
||||
unicode = (unicode << 6) | (c & 0x3f);
|
||||
--nb_bytes;
|
||||
}
|
||||
m_unicode = unicode;
|
||||
}
|
||||
|
||||
Utf8UnicodeIterator &Utf8UnicodeIterator::operator ++() {
|
||||
next_unicode();
|
||||
return *this;
|
||||
}
|
||||
|
||||
unicode_t Utf8UnicodeIterator::unicode() const {
|
||||
return m_unicode;
|
||||
}
|
||||
|
||||
unicode_t Utf8UnicodeIterator::operator *() const {
|
||||
return m_unicode;
|
||||
}
|
||||
|
||||
bool Utf8UnicodeIterator::good() const {
|
||||
return !m_finished;
|
||||
}
|
||||
|
||||
Utf8UnicodeIteratorWrapper::Utf8UnicodeIteratorWrapper(const std::string &s)
|
||||
: iss(s) {
|
||||
}
|
||||
|
||||
Utf8UnicodeIterator Utf8UnicodeIteratorWrapper::begin() {
|
||||
return Utf8UnicodeIterator(iss);
|
||||
}
|
||||
|
||||
Utf8UnicodeIterator Utf8UnicodeIteratorWrapper::end() {
|
||||
return Utf8UnicodeIterator();
|
||||
}
|
||||
|
||||
bool Utf8UnicodeIterator::operator !=(const Utf8UnicodeIterator &) {
|
||||
return good();
|
||||
}
|
303
rwlib/source/fonts/Unicode.hpp
Normal file
303
rwlib/source/fonts/Unicode.hpp
Normal file
@ -0,0 +1,303 @@
|
||||
#ifndef _RWLIB_FONTS_UNICODE_HPP_
|
||||
#define _RWLIB_FONTS_UNICODE_HPP_
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <sstream>
|
||||
|
||||
/**
|
||||
* unicode_t represent a unicode data point. (UTF-32)
|
||||
*/
|
||||
using unicode_t = char32_t;
|
||||
|
||||
/**
|
||||
* @brief unicode_to_utf8 Encode a unicode data point to a (non-zero terminated) utf-8 string.
|
||||
* @param unicode The unicode data point to convert
|
||||
* @param c buffer to write the utf-8 data to
|
||||
* @return number of bytes written
|
||||
*/
|
||||
size_t unicode_to_utf8(unicode_t unicode, char c[4]);
|
||||
|
||||
/**
|
||||
* @brief Iterate over a utf8 string stream. Output unicode data points.
|
||||
*/
|
||||
class Utf8UnicodeIterator {
|
||||
private:
|
||||
/**
|
||||
* @brief m_is Pointer to the utf8 stream to iterate over.
|
||||
*/
|
||||
std::istream *m_is = nullptr;
|
||||
|
||||
/**
|
||||
* @brief m_finished true if the stream is finished/invalid.
|
||||
*/
|
||||
bool m_finished = true;
|
||||
|
||||
/**
|
||||
* @brief m_unicode Current unicode point.
|
||||
*/
|
||||
unicode_t m_unicode;
|
||||
|
||||
/**
|
||||
* @brief next_unicode Move to the next unicode point.
|
||||
*/
|
||||
void next_unicode();
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Create an empty unicode iterator. The iterator is not good.
|
||||
*/
|
||||
Utf8UnicodeIterator() = default;
|
||||
/**
|
||||
* @brief Create a unicode iterator that iterates over a utf8 stream.
|
||||
* @param is utf8 stream
|
||||
*/
|
||||
Utf8UnicodeIterator(std::istream &is);
|
||||
|
||||
/**
|
||||
* @brief operator ++ Move to the next unicode character.
|
||||
* Only call this function when the iterator is good.
|
||||
* @return this object
|
||||
*/
|
||||
Utf8UnicodeIterator &operator ++();
|
||||
/**
|
||||
* @brief operator != Returns true if this iterator is good
|
||||
* @return true if this iterator is good
|
||||
*/
|
||||
bool operator!=(const Utf8UnicodeIterator &);
|
||||
|
||||
/**
|
||||
* @brief operator * Returns the current unicode point
|
||||
* ONly call this function when the iterator is good.
|
||||
* @return unicode point
|
||||
*/
|
||||
unicode_t operator *() const;
|
||||
|
||||
/**
|
||||
* @brief unicode Returns the current unicode point
|
||||
* Only call this function when the iterator is good.
|
||||
* @return unicode point
|
||||
*/
|
||||
unicode_t unicode() const;
|
||||
|
||||
/**
|
||||
* @brief good Checks whether this stream is good.
|
||||
* A stream is good when a unicode point is available.
|
||||
* @return true if the stream is good, else false.
|
||||
*/
|
||||
bool good() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class lets you iterate over a utf8 string using for ranged loop
|
||||
*/
|
||||
class Utf8UnicodeIteratorWrapper {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a new wraper wrapper.
|
||||
* @param s The string that the utf8 to Unicode iterator should iterate over
|
||||
*/
|
||||
Utf8UnicodeIteratorWrapper(const std::string &s);
|
||||
|
||||
/**
|
||||
* @brief Return the iterator at the start. This one returns unicode points.
|
||||
* @return Utf8UnicodeIterator of which will read the unicode points.
|
||||
*/
|
||||
Utf8UnicodeIterator begin();
|
||||
/**
|
||||
* @brief Return the unicode iterator at the end. This one is not good.
|
||||
* No unicode points can be read from this object.
|
||||
* @return Utf8UnicodeIterator Unicode iterator that is not good.
|
||||
*/
|
||||
Utf8UnicodeIterator end();
|
||||
private:
|
||||
/**
|
||||
* @brief iss This utf8 stream holds the wrapped string and returns bytes to convert to unicode.
|
||||
*/
|
||||
std::istringstream iss;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unicode data points used by OpenRW.
|
||||
*/
|
||||
enum UnicodeValue : unicode_t {
|
||||
UNICODE_TAB = 0x09, /* '\t' */
|
||||
UNICODE_NEW_LINE = 0x0a, /* '\n' */
|
||||
UNICODE_CARRIAGE_RETURN = 0x0d, /* '\r' */
|
||||
|
||||
UNICODE_SPACE = 0x20, /* ' ' */
|
||||
UNICODE_EXCLAMATION_MARK = 0x21, /* '!' */
|
||||
UNICODE_QUOTATION_MARK = 0x22, /* '"' */
|
||||
UNICODE_NUMBER_SIGN = 0x23, /* '#' */
|
||||
UNICODE_DOLLAR_SIGN = 0x24, /* '$' */
|
||||
UNICODE_PROCENT_SIGN = 0x25, /* '%' */
|
||||
UNICODE_AMPERSAND = 0x26, /* '&' */
|
||||
UNICODE_APOSTROPHE = 0x27, /* ''' */
|
||||
UNICODE_LEFT_PARENTHESIS = 0x28, /* '(' */
|
||||
UNICODE_RIGHT_PARENTHESIS = 0x29, /* ')' */
|
||||
UNICODE_ASTERISK = 0x2a, /* '*' */
|
||||
UNICODE_PLUS_SIGN = 0x2b, /* '+' */
|
||||
UNICODE_COMMA = 0x2c, /* ',' */
|
||||
UNICODE_HYPHEN_MINUS = 0x2d, /* '-' */
|
||||
UNICODE_FULL_STOP = 0x2e, /* '.' */
|
||||
UNICODE_SOLIDUS = 0x2f, /* '/' */
|
||||
|
||||
UNICODE_DIGIT_0 = 0x30, /* '0' */
|
||||
UNICODE_DIGIT_1 = 0x31, /* '1' */
|
||||
UNICODE_DIGIT_2 = 0x32, /* '2' */
|
||||
UNICODE_DIGIT_3 = 0x33, /* '3' */
|
||||
UNICODE_DIGIT_4 = 0x34, /* '4' */
|
||||
UNICODE_DIGIT_5 = 0x35, /* '5' */
|
||||
UNICODE_DIGIT_6 = 0x36, /* '6' */
|
||||
UNICODE_DIGIT_7 = 0x37, /* '7' */
|
||||
UNICODE_DIGIT_8 = 0x38, /* '8' */
|
||||
UNICODE_DIGIT_9 = 0x39, /* '9' */
|
||||
|
||||
UNICODE_COLON = 0x3a, /* ':' */
|
||||
UNICODE_SEMICOLON = 0x3b, /* ';' */
|
||||
UNICODE_LESS_THAN_SIGN = 0x3c, /* '<' */
|
||||
UNICODE_EQUALS_SIGN = 0x3d, /* '=' */
|
||||
UNICODE_GREATER_THAN_SIGN = 0x3e, /* '>' */
|
||||
UNICODE_QUESTION_MARK = 0x3f, /* '?' */
|
||||
|
||||
UNICODE_COMMERCIAL_AT = 0x40, /* '@' */
|
||||
UNICODE_CAPITAL_A = 0x41, /* 'A' */
|
||||
UNICODE_CAPITAL_B = 0x42, /* 'B' */
|
||||
UNICODE_CAPITAL_C = 0x43, /* 'C' */
|
||||
UNICODE_CAPITAL_D = 0x44, /* 'D' */
|
||||
UNICODE_CAPITAL_E = 0x45, /* 'E' */
|
||||
UNICODE_CAPITAL_F = 0x46, /* 'F' */
|
||||
UNICODE_CAPITAL_G = 0x47, /* 'G' */
|
||||
UNICODE_CAPITAL_H = 0x48, /* 'H' */
|
||||
UNICODE_CAPITAL_I = 0x49, /* 'I' */
|
||||
UNICODE_CAPITAL_J = 0x4a, /* 'J' */
|
||||
UNICODE_CAPITAL_K = 0x4b, /* 'K' */
|
||||
UNICODE_CAPITAL_L = 0x4c, /* 'L' */
|
||||
UNICODE_CAPITAL_M = 0x4d, /* 'M' */
|
||||
UNICODE_CAPITAL_N = 0x4e, /* 'N' */
|
||||
UNICODE_CAPITAL_O = 0x4f, /* 'O' */
|
||||
UNICODE_CAPITAL_P = 0x50, /* 'P' */
|
||||
UNICODE_CAPITAL_Q = 0x51, /* 'Q' */
|
||||
UNICODE_CAPITAL_R = 0x52, /* 'R' */
|
||||
UNICODE_CAPITAL_S = 0x53, /* 'S' */
|
||||
UNICODE_CAPITAL_T = 0x54, /* 'T' */
|
||||
UNICODE_CAPITAL_U = 0x55, /* 'U' */
|
||||
UNICODE_CAPITAL_V = 0x56, /* 'V' */
|
||||
UNICODE_CAPITAL_W = 0x57, /* 'W' */
|
||||
UNICODE_CAPITAL_X = 0x58, /* 'X' */
|
||||
UNICODE_CAPITAL_Y = 0x59, /* 'Y' */
|
||||
UNICODE_CAPITAL_Z = 0x5a, /* 'Z' */
|
||||
|
||||
UNICODE_LEFT_SQUARE_BRACKET = 0x5b, /* '[' */
|
||||
UNICODE_REVERSE_SOLIDUS = 0x5c, /* '\' */
|
||||
UNICODE_RIGHT_SQUARE_BRACKET = 0x5d, /* ']' */
|
||||
UNICODE_CIRCUMFLEX_ACCENT = 0x5e, /* '^' */
|
||||
UNICODE_LOW_LINE = 0x5f, /* '_' */
|
||||
|
||||
UNICODE_GRAVE_ACCENT = 0x60, /* '`' */
|
||||
UNICODE_SMALL_A = 0x61, /* 'a' */
|
||||
UNICODE_SMALL_B = 0x62, /* 'b' */
|
||||
UNICODE_SMALL_C = 0x63, /* 'c' */
|
||||
UNICODE_SMALL_D = 0x64, /* 'd' */
|
||||
UNICODE_SMALL_E = 0x65, /* 'e' */
|
||||
UNICODE_SMALL_F = 0x66, /* 'f' */
|
||||
UNICODE_SMALL_G = 0x67, /* 'g' */
|
||||
UNICODE_SMALL_H = 0x68, /* 'h' */
|
||||
UNICODE_SMALL_I = 0x69, /* 'i' */
|
||||
UNICODE_SMALL_J = 0x6a, /* 'j' */
|
||||
UNICODE_SMALL_K = 0x6b, /* 'k' */
|
||||
UNICODE_SMALL_L = 0x6c, /* 'l' */
|
||||
UNICODE_SMALL_M = 0x6d, /* 'm' */
|
||||
UNICODE_SMALL_N = 0x6e, /* 'n' */
|
||||
UNICODE_SMALL_O = 0x6f, /* 'o' */
|
||||
UNICODE_SMALL_P = 0x70, /* 'p' */
|
||||
UNICODE_SMALL_Q = 0x71, /* 'q' */
|
||||
UNICODE_SMALL_R = 0x72, /* 'r' */
|
||||
UNICODE_SMALL_S = 0x73, /* 's' */
|
||||
UNICODE_SMALL_T = 0x74, /* 't' */
|
||||
UNICODE_SMALL_U = 0x75, /* 'u' */
|
||||
UNICODE_SMALL_V = 0x76, /* 'v' */
|
||||
UNICODE_SMALL_W = 0x77, /* 'w' */
|
||||
UNICODE_SMALL_X = 0x78, /* 'x' */
|
||||
UNICODE_SMALL_Y = 0x79, /* 'y' */
|
||||
UNICODE_SMALL_Z = 0x7a, /* 'z' */
|
||||
|
||||
UNICODE_LEFT_CURLY_BRACKET = 0x7b, /* '{' */
|
||||
UNICODE_VERTICAL_LINE = 0x7c, /* '|' */
|
||||
UNICODE_RIGHT_CURLY_BRACKET = 0x7d, /* '}' */
|
||||
UNICODE_TILDE = 0x7e, /* '~' */
|
||||
|
||||
UNICODE_INVERTED_EXCLAMATION_MARK = 0xa1, /* '¡' */
|
||||
UNICODE_COPYRIGHT_SIGN = 0xa9, /* '©' */
|
||||
UNICODE_REGISTERED_SIGN = 0xae, /* '®' */
|
||||
UNICODE_DEGREES = 0xb0, /* '°' */
|
||||
UNICODE_ACUTE_ACCENT = 0xb4, /* '´' */
|
||||
UNICODE_INVERTED_QUESTION_MARK = 0xbf, /* '¿' */
|
||||
|
||||
UNICODE_CAPITAL_A_GRAVE = 0xc0, /* 'À' */
|
||||
UNICODE_CAPITAL_A_ACUTE = 0xc1, /* 'Á' */
|
||||
UNICODE_CAPITAL_A_CIRCUMFLEX = 0xc2, /* 'Â' */
|
||||
UNICODE_CAPITAL_A_DIARESIS = 0xc4, /* 'Ä' */
|
||||
UNICODE_CAPITAL_AE = 0xc6, /* 'Æ' */
|
||||
UNICODE_CAPITAL_C_CEDILLA = 0xc7, /* 'Ç' */
|
||||
UNICODE_CAPITAL_E_GRAVE = 0xc8, /* 'È' */
|
||||
UNICODE_CAPITAL_E_ACUTE = 0xc9, /* 'É' */
|
||||
UNICODE_CAPITAL_E_CIRCUMFLEX = 0xca, /* 'Ê' */
|
||||
UNICODE_CAPITAL_E_DIARESIS = 0xcb, /* 'Ë' */
|
||||
UNICODE_CAPITAL_I_GRAVE = 0xcc, /* 'Ì' */
|
||||
UNICODE_CAPITAL_I_ACUTE = 0xcd, /* 'Í' */
|
||||
UNICODE_CAPITAL_I_CIRCUMFLEX = 0xce, /* 'Î' */
|
||||
UNICODE_CAPITAL_I_DIARESIS = 0xcf, /* 'Ï' */
|
||||
UNICODE_CAPITAL_N_TILDE = 0xd1, /* 'Ñ' */
|
||||
UNICODE_CAPITAL_O_GRAVE = 0xd2, /* 'Ò' */
|
||||
UNICODE_CAPITAL_O_ACUTE = 0xd3, /* 'Ó' */
|
||||
UNICODE_CAPITAL_O_CIRCUMFLEX = 0xd4, /* 'Ô' */
|
||||
UNICODE_CAPITAL_O_DIARESIS = 0xd6, /* 'Ö' */
|
||||
UNICODE_MULTIPLICATION_SIGN = 0xd7, /* '×' */
|
||||
UNICODE_CAPITAL_U_GRAVE = 0xd9, /* 'Ù' */
|
||||
UNICODE_CAPITAL_U_ACUTE = 0xda, /* 'Ú' */
|
||||
UNICODE_CAPITAL_U_CIRCUMFLEX = 0xdb, /* 'Û' */
|
||||
UNICODE_CAPITAL_U_DIARESIS = 0xdc, /* 'Ü' */
|
||||
UNICODE_SMALL_SHARP_S = 0xdf, /* 'ß' */
|
||||
|
||||
UNICODE_SMALL_A_GRAVE = 0xe0, /* 'à' */
|
||||
UNICODE_SMALL_A_ACUTE = 0xe1, /* 'á' */
|
||||
UNICODE_SMALL_A_CIRCUMFLEX = 0xe2, /* 'â' */
|
||||
UNICODE_SMALL_A_DIARESIS = 0xe4, /* 'ä' */
|
||||
UNICODE_SMALL_AE = 0xe6, /* 'æ' */
|
||||
UNICODE_SMALL_C_CEDILLA = 0xe7, /* 'ç' */
|
||||
UNICODE_SMALL_E_GRAVE = 0xe8, /* 'è' */
|
||||
UNICODE_SMALL_E_ACUTE = 0xe9, /* 'é' */
|
||||
UNICODE_SMALL_E_CIRCUMFLEX = 0xea, /* 'ê' */
|
||||
UNICODE_SMALL_E_DIARESIS = 0xeb, /* 'ë' */
|
||||
UNICODE_SMALL_I_GRAVE = 0xec, /* 'ì' */
|
||||
UNICODE_SMALL_I_ACUTE = 0xed, /* 'í' */
|
||||
UNICODE_SMALL_I_CIRCUMFLEX = 0xee, /* 'î' */
|
||||
UNICODE_SMALL_I_DIARESIS = 0xef, /* 'ï' */
|
||||
UNICODE_SMALL_N_TILDE = 0xf1, /* 'ñ' */
|
||||
UNICODE_SMALL_O_GRAVE = 0xf2, /* 'ò' */
|
||||
UNICODE_SMALL_O_ACUTE = 0xf3, /* 'ó' */
|
||||
UNICODE_SMALL_O_CIRCUMFLEX = 0xf4, /* 'ô' */
|
||||
UNICODE_SMALL_O_DIARESIS = 0xf6, /* 'ö' */
|
||||
UNICODE_SMALL_U_GRAVE = 0xf9, /* 'ù' */
|
||||
UNICODE_SMALL_U_ACUTE = 0xfa, /* 'ú' */
|
||||
UNICODE_SMALL_U_CIRCUMFLEX = 0xfb, /* 'û' */
|
||||
UNICODE_SMALL_U_DIARESIS = 0xfc, /* 'ü' */
|
||||
|
||||
UNICODE_NUMERO_SIGN = 0x2116, /* '№' */
|
||||
UNICODE_TRADE_MARK = 0x2122, /* '™' */
|
||||
UNICODE_INCREMENT = 0x2206, /* '∆' */
|
||||
UNICODE_BLACK_UP_POINTING_TRIANGLE = 0x25b2, /* '▲' */
|
||||
UNICODE_BLACK_RIGHT_POINTING_POINTER = 0x25ba, /* '►' */
|
||||
UNICODE_BLACK_DOWN_POINTING_POINTER = 0x25bc, /* '▼' */
|
||||
UNICODE_BLACK_LEFT_POINTING_POINTER = 0x25c4, /* '◄' */
|
||||
UNICODE_WHITE_CIRCLE = 0x25cb, /* '○' */
|
||||
UNICODE_BLACK_STAR = 0x2605, /* '★' */
|
||||
UNICODE_BLACK_HEART_SUIT = 0x2665, /* '♥' */
|
||||
UNICODE_CROSS_MARK = 0x274c, /* '❌' */
|
||||
UNICODE_REPLACEMENT_CHARACTER = 0xfffd, /* '<27>' */
|
||||
|
||||
UNICODE_SHIELD = 0x1f6e1, /* '🛡' */
|
||||
};
|
||||
|
||||
#endif
|
@ -6,23 +6,43 @@ find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
add_executable(rwviewer WIN32
|
||||
main.cpp
|
||||
OpenGLCompat.h
|
||||
|
||||
ViewerWindow.hpp
|
||||
ViewerWindow.cpp
|
||||
|
||||
models/ObjectListModel.hpp
|
||||
models/ObjectListModel.cpp
|
||||
models/DFFFramesTreeModel.hpp
|
||||
models/DFFFramesTreeModel.cpp
|
||||
models/TextModel.hpp
|
||||
models/TextModel.cpp
|
||||
|
||||
views/ViewerInterface.hpp
|
||||
views/ObjectViewer.hpp
|
||||
views/ObjectViewer.cpp
|
||||
views/ModelViewer.hpp
|
||||
views/ModelViewer.cpp
|
||||
views/TextViewer.hpp
|
||||
views/TextViewer.cpp
|
||||
views/WorldViewer.hpp
|
||||
views/WorldViewer.cpp
|
||||
views/ViewerInterface.hpp
|
||||
views/ViewerInterface.cpp
|
||||
|
||||
ViewerWidget.cpp
|
||||
ViewerWidget.hpp
|
||||
ItemListModel.hpp
|
||||
ItemListModel.cpp
|
||||
ItemListWidget.hpp
|
||||
ItemListWidget.cpp
|
||||
IMGArchiveModel.hpp
|
||||
IMGArchiveModel.cpp
|
||||
widgets/ModelFramesWidget.hpp
|
||||
widgets/ModelFramesWidget.cpp
|
||||
AnimationListModel.hpp
|
||||
AnimationListModel.cpp
|
||||
AnimationListWidget.hpp
|
||||
AnimationListWidget.cpp
|
||||
)
|
||||
|
||||
|
@ -2,12 +2,15 @@
|
||||
#include <QFileDialog>
|
||||
#include <QMouseEvent>
|
||||
#include <engine/Animator.hpp>
|
||||
#include <engine/GameData.hpp>
|
||||
#include <engine/GameWorld.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <objects/CharacterObject.hpp>
|
||||
#include <objects/InstanceObject.hpp>
|
||||
#include <objects/VehicleObject.hpp>
|
||||
#include <render/GameRenderer.hpp>
|
||||
#include <render/ObjectRenderer.hpp>
|
||||
#include <render/TextRenderer.hpp>
|
||||
|
||||
constexpr float kViewFov = glm::radians(90.0f);
|
||||
|
||||
@ -31,13 +34,7 @@ ViewCamera OrbitCamera (const glm::vec2& viewPort, const glm::vec2& viewAngles,
|
||||
|
||||
ViewerWidget::ViewerWidget(QOpenGLContext* context, QWindow* parent)
|
||||
: QWindow(parent)
|
||||
, context(context)
|
||||
, selectedFrame(nullptr)
|
||||
, viewDistance(1.f)
|
||||
, dragging(false)
|
||||
, moveFast(false)
|
||||
, _frameWidgetDraw(nullptr)
|
||||
, _frameWidgetGeom(nullptr) {
|
||||
, context(context) {
|
||||
setSurfaceType(OpenGLSurface);
|
||||
}
|
||||
|
||||
@ -127,6 +124,13 @@ void ViewerWidget::drawWorld(GameRenderer& r) {
|
||||
r.renderWorld(world(), vc, 0.f);
|
||||
}
|
||||
|
||||
void ViewerWidget::drawText(GameRenderer& r) {
|
||||
for(auto &textInfo : textInfos) {
|
||||
_renderer->text.renderText(textInfo, false);
|
||||
}
|
||||
r.renderPostProcess();
|
||||
}
|
||||
|
||||
void ViewerWidget::paintGL() {
|
||||
glViewport(0, 0, width() * devicePixelRatio(), height() * devicePixelRatio());
|
||||
glClearColor(0.3f, 0.3f, 0.3f, 1.f);
|
||||
@ -141,7 +145,6 @@ void ViewerWidget::paintGL() {
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
|
||||
r.getRenderer()->invalidate();
|
||||
r.setupRender();
|
||||
|
||||
@ -155,6 +158,9 @@ void ViewerWidget::paintGL() {
|
||||
case Mode::World:
|
||||
drawWorld(r);
|
||||
break;
|
||||
case Mode::Text:
|
||||
drawText(r);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -228,9 +234,18 @@ void ViewerWidget::showObject(quint16 item) {
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWidget::clearText() {
|
||||
textInfos.clear();
|
||||
}
|
||||
|
||||
void ViewerWidget::showText(const TextRenderer::TextInfo &ti) {
|
||||
textInfos.push_back(ti);
|
||||
}
|
||||
|
||||
void ViewerWidget::showModel(ClumpPtr model) {
|
||||
_viewMode = Mode::Model;
|
||||
_model = model;
|
||||
textInfos.clear();
|
||||
}
|
||||
|
||||
void ViewerWidget::selectFrame(ModelFrame* frame) {
|
||||
@ -248,7 +263,7 @@ void ViewerWidget::exportModel() {
|
||||
if( it != world()->objectTypes.end() ) {
|
||||
for( auto& archive : world()->data.archives ) {
|
||||
for(size_t i = 0; i < archive.second.getAssetCount(); ++i) {
|
||||
auto& assetI = archive.second.getAssetInfoByIndex(i);
|
||||
auto& assetI = archive.second.getAssetInfoByIndex(i);;
|
||||
std::string q(assetI.name);
|
||||
std::transform(q.begin(), q.end(), q.begin(), ::tolower);
|
||||
if( q.find(it->second->modelName) != q.npos ) {
|
||||
|
@ -6,8 +6,9 @@
|
||||
#include <engine/GameWorld.hpp>
|
||||
#include <gl/DrawBuffer.hpp>
|
||||
#include <gl/GeometryBuffer.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
#include <loaders/LoaderIFP.hpp>
|
||||
#include <render/TextRenderer.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
// Prevent Qt from conflicting with glLoadGen on macOS
|
||||
#include "OpenGLCompat.h"
|
||||
@ -16,6 +17,7 @@
|
||||
|
||||
class GameRenderer;
|
||||
class Clump;
|
||||
|
||||
class ViewerWidget : public QWindow {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@ -26,6 +28,8 @@ public:
|
||||
Model,
|
||||
//! View loaded instances, \see showWorld();
|
||||
World,
|
||||
//! View text strings, \see showText
|
||||
Text,
|
||||
};
|
||||
|
||||
ViewerWidget(QOpenGLContext* context, QWindow* parent);
|
||||
@ -54,6 +58,8 @@ public:
|
||||
public slots:
|
||||
void showObject(quint16 item);
|
||||
void showModel(ClumpPtr model);
|
||||
void clearText();
|
||||
void showText(const TextRenderer::TextInfo &ti);
|
||||
void selectFrame(ModelFrame* frame);
|
||||
void exportModel();
|
||||
|
||||
@ -78,23 +84,24 @@ protected:
|
||||
GameWorld* _world = nullptr;
|
||||
GameRenderer* _renderer = nullptr;
|
||||
|
||||
std::vector<TextRenderer::TextInfo> textInfos;
|
||||
ClumpPtr _model;
|
||||
ModelFrame* selectedFrame = nullptr;
|
||||
GameObject* _object = nullptr;
|
||||
quint16 _objectID = 0;
|
||||
|
||||
|
||||
float viewDistance;
|
||||
float viewDistance = 1.f;
|
||||
glm::vec2 viewAngles{};
|
||||
glm::vec3 viewPosition{};
|
||||
|
||||
bool dragging;
|
||||
bool dragging = false;
|
||||
QPointF dstart;
|
||||
glm::vec2 dastart{};
|
||||
bool moveFast;
|
||||
bool moveFast = false;
|
||||
|
||||
DrawBuffer* _frameWidgetDraw;
|
||||
GeometryBuffer* _frameWidgetGeom;
|
||||
DrawBuffer* _frameWidgetDraw = nullptr;
|
||||
GeometryBuffer* _frameWidgetGeom = nullptr;
|
||||
GLuint whiteTex;
|
||||
|
||||
void drawFrameWidget(ModelFrame* f, const glm::mat4& = glm::mat4(1.f));
|
||||
@ -103,6 +110,7 @@ protected:
|
||||
void drawModel(GameRenderer& r, ClumpPtr& model);
|
||||
void drawObject(GameRenderer& r, GameObject* object);
|
||||
void drawWorld(GameRenderer& r);
|
||||
void drawText(GameRenderer& r);
|
||||
|
||||
};
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "views/ModelViewer.hpp"
|
||||
#include "views/ObjectViewer.hpp"
|
||||
#include "views/WorldViewer.hpp"
|
||||
#include "views/TextViewer.hpp"
|
||||
|
||||
#include <engine/GameState.hpp>
|
||||
#include <engine/GameWorld.hpp>
|
||||
@ -42,14 +43,17 @@ void ViewerWindow::createMenus() {
|
||||
for (int i = 0; i < MaxRecentGames; ++i) {
|
||||
QAction* r = file->addAction("");
|
||||
recentGames.append(r);
|
||||
connect(r, SIGNAL(triggered()), SLOT(openRecent()));
|
||||
connect(r, &QAction::triggered, this, [r, this]() {
|
||||
QString recentGame = r->data().toString();
|
||||
loadGame(recentGame);
|
||||
});
|
||||
}
|
||||
|
||||
recentSep = file->addSeparator();
|
||||
auto ex = file->addAction("E&xit");
|
||||
ex->setShortcut(QKeySequence::Quit);
|
||||
connect(ex, SIGNAL(triggered()), QApplication::instance(),
|
||||
SLOT(closeAllWindows()));
|
||||
connect(ex, &QAction::triggered,
|
||||
QApplication::instance(), &QApplication::closeAllWindows);
|
||||
|
||||
mb->addMenu("&Data");
|
||||
|
||||
@ -94,6 +98,10 @@ void ViewerWindow::createDefaultViews() {
|
||||
views->addTab(worldView, "World");
|
||||
connect(this, &ViewerWindow::gameLoaded, worldView, &WorldViewer::showData);
|
||||
|
||||
auto textView = new TextViewer(this);
|
||||
views->addTab(textView, "Texts");
|
||||
connect(this, &ViewerWindow::gameLoaded, textView, &TextViewer::showData);
|
||||
|
||||
setCentralWidget(views);
|
||||
}
|
||||
|
||||
@ -143,6 +151,10 @@ void ViewerWindow::loadGame(const QString& path) {
|
||||
|
||||
gameWorld->data->load();
|
||||
|
||||
renderer->text.setFontTexture(FONT_PAGER, "pager");
|
||||
renderer->text.setFontTexture(FONT_PRICEDOWN, "font1");
|
||||
renderer->text.setFontTexture(FONT_ARIAL, "font2");
|
||||
|
||||
gameLoaded(gameWorld.get(), renderer.get());
|
||||
|
||||
QSettings settings("OpenRW", "rwviewer");
|
||||
@ -155,13 +167,6 @@ void ViewerWindow::loadGame(const QString& path) {
|
||||
updateRecentGames();
|
||||
}
|
||||
|
||||
void ViewerWindow::openRecent() {
|
||||
QAction* r = qobject_cast<QAction*>(sender());
|
||||
if (r) {
|
||||
loadGame(r->data().toString());
|
||||
}
|
||||
}
|
||||
|
||||
void ViewerWindow::showObjectModel(uint16_t) {
|
||||
#pragma message("implement me")
|
||||
}
|
||||
|
@ -49,7 +49,6 @@ signals:
|
||||
void gameLoaded(GameWorld*, GameRenderer*);
|
||||
|
||||
private slots:
|
||||
void openRecent();
|
||||
void showObjectModel(uint16_t object);
|
||||
|
||||
private:
|
||||
|
76
rwviewer/models/TextModel.cpp
Normal file
76
rwviewer/models/TextModel.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include "TextModel.hpp"
|
||||
#include <iostream>
|
||||
|
||||
#include <QBrush>
|
||||
|
||||
TextModel::TextModel(QObject *parent)
|
||||
: QAbstractTableModel(parent) {
|
||||
|
||||
}
|
||||
|
||||
void TextModel::setData(const TextMapType &textMap) {
|
||||
beginResetModel();
|
||||
m_textMap = textMap;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
int TextModel::rowCount(const QModelIndex &) const {
|
||||
return m_textMap.keys.size();
|
||||
}
|
||||
|
||||
int TextModel::columnCount(const QModelIndex &) const {
|
||||
return m_textMap.languages.size();
|
||||
}
|
||||
|
||||
const GameString &TextModel::lookupIndex(const QModelIndex &index) const {
|
||||
const auto &language = m_textMap.languages.at(index.column());
|
||||
const auto &key = m_textMap.keys.at(index.row());
|
||||
return m_textMap.map_lang_key_tran.at(language).at(key);
|
||||
}
|
||||
|
||||
QVariant TextModel::data(const QModelIndex &index, int role) const {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
try {
|
||||
const auto &gameText = this->lookupIndex(index);
|
||||
auto gameString = GameStringUtil::toString(gameText, m_font);
|
||||
return QString::fromStdString(gameString);
|
||||
} catch (const std::out_of_range &) {
|
||||
return QVariant::Invalid;
|
||||
} catch (...) {
|
||||
throw;
|
||||
}
|
||||
case Qt::BackgroundRole:
|
||||
try {
|
||||
this->lookupIndex(index);
|
||||
return QVariant::Invalid;
|
||||
} catch (const std::out_of_range &) {
|
||||
return QBrush(Qt::red);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return QVariant::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
QVariant TextModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
switch (orientation) {
|
||||
case Qt::Horizontal:
|
||||
return QString::fromStdString(m_textMap.languages[section]);
|
||||
case Qt::Vertical:
|
||||
return QString::fromStdString(m_textMap.keys[section]);
|
||||
default:
|
||||
return QVariant::Invalid;
|
||||
}
|
||||
default:
|
||||
return QVariant::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
void TextModel::fontChanged(font_t font) {
|
||||
beginResetModel();
|
||||
m_font = font;
|
||||
endResetModel();
|
||||
}
|
35
rwviewer/models/TextModel.hpp
Normal file
35
rwviewer/models/TextModel.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
#ifndef _TEXTMODEL_HPP_
|
||||
#define _TEXTMODEL_HPP_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
|
||||
#include <fonts/GameTexts.hpp>
|
||||
|
||||
struct TextMapType {
|
||||
std::vector<std::string> languages;
|
||||
std::vector<std::string> keys;
|
||||
std::map<std::string, std::map<std::string, GameString>> map_lang_key_tran;
|
||||
};
|
||||
|
||||
class TextModel : public QAbstractTableModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TextModel(QObject *parent = nullptr);
|
||||
void setData(const TextMapType &textMap);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
const GameString &lookupIndex(const QModelIndex &index) const;
|
||||
public slots:
|
||||
void fontChanged(font_t font);
|
||||
private:
|
||||
font_t m_font = FONT_PAGER;
|
||||
TextMapType m_textMap;
|
||||
};
|
||||
|
||||
#endif
|
@ -120,6 +120,7 @@ ObjectViewer::ObjectViewer(QWidget* parent, Qt::WindowFlags f)
|
||||
objectList->setColumnWidth(1, 150);
|
||||
objectList->setColumnWidth(2, 200);
|
||||
objectList->setSortingEnabled(true);
|
||||
objectList->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
|
||||
connect(objectList->selectionModel(),
|
||||
SIGNAL(currentChanged(QModelIndex, QModelIndex)), this,
|
||||
SLOT(showItem(QModelIndex)));
|
||||
|
251
rwviewer/views/TextViewer.cpp
Normal file
251
rwviewer/views/TextViewer.cpp
Normal file
@ -0,0 +1,251 @@
|
||||
#include "TextViewer.hpp"
|
||||
|
||||
#include <render/TextRenderer.hpp>
|
||||
#include <rw/filesystem.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <loaders/LoaderGXT.hpp>
|
||||
#include <models/TextModel.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <render/GameRenderer.hpp>
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QFormLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QVBoxLayout>
|
||||
#include <QItemSelection>
|
||||
#include <QLineEdit>
|
||||
#include <QModelIndex>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QRegExp>
|
||||
#include <QRegExpValidator>
|
||||
#include <QSpinBox>
|
||||
#include <QSplitter>
|
||||
#include <QTableView>
|
||||
#include <QTextEdit>
|
||||
|
||||
void TextTableView::selectionChanged(const QItemSelection &selected, const QItemSelection &) {
|
||||
if (!selected.size())
|
||||
return;
|
||||
auto index = selected.indexes()[0];
|
||||
if (!index.isValid())
|
||||
return;
|
||||
|
||||
auto *textModel = dynamic_cast<TextModel *>(this->model());
|
||||
if (!textModel) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto gameString = textModel->lookupIndex(index);
|
||||
emit gameStringChanged(gameString);
|
||||
} catch (std::out_of_range &) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
TextViewer::TextViewer(QWidget* parent, Qt::WindowFlags f)
|
||||
: ViewerInterface(parent, f) {
|
||||
|
||||
auto dataLayout = new QVBoxLayout;
|
||||
|
||||
auto splitter = new QSplitter;
|
||||
splitter->setChildrenCollapsible(false);
|
||||
splitter->setOrientation(Qt::Horizontal);
|
||||
|
||||
viewerWidget = createViewer();
|
||||
|
||||
textModel = new TextModel;
|
||||
textTable = new TextTableView;
|
||||
textTable->setModel(textModel);
|
||||
textTable->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
connect(textTable, &TextTableView::gameStringChanged, this, &TextViewer::onGameStringChange);
|
||||
dataLayout->addWidget(textTable);
|
||||
|
||||
auto propLayout = new QHBoxLayout;
|
||||
|
||||
connect(this, &TextViewer::fontChanged, this, &TextViewer::onFontChange);
|
||||
auto groupBox = new QGroupBox;
|
||||
auto *groupBoxLayout = new QHBoxLayout;
|
||||
auto *radioFont1 = new QRadioButton(tr("Pager"));
|
||||
connect(radioFont1, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_PAGER);});
|
||||
groupBoxLayout->addWidget(radioFont1);
|
||||
auto *radioFont2 = new QRadioButton(tr("Pricedown"));
|
||||
connect(radioFont2, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_PRICEDOWN);});
|
||||
groupBoxLayout->addWidget(radioFont2);
|
||||
auto *radioFont3 = new QRadioButton(tr("Arial"));
|
||||
connect(radioFont3, &QRadioButton::clicked, [this]() {emit fontChanged(FONT_ARIAL);});
|
||||
groupBoxLayout->addWidget(radioFont3);
|
||||
groupBox->setLayout(groupBoxLayout);
|
||||
groupBox->setProperty("border", "2px solid gray");
|
||||
propLayout->addWidget(groupBox);
|
||||
|
||||
radioFont1->click();
|
||||
|
||||
auto textSizeLayout = new QFormLayout;
|
||||
auto textSizeSpinBox = new QSpinBox;
|
||||
textSizeSpinBox->setMinimum(1);
|
||||
textSizeSpinBox->setMaximum(50);
|
||||
connect(textSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TextViewer::onFontSizeChange);
|
||||
textSizeSpinBox->setValue(20);
|
||||
textSizeLayout->addRow(tr("Font size"), textSizeSpinBox);
|
||||
propLayout->addLayout(textSizeLayout);
|
||||
|
||||
propLayout->addStretch();
|
||||
dataLayout->addLayout(propLayout);
|
||||
|
||||
hexLineEdit = new QLineEdit;
|
||||
hexLineEdit->setReadOnly(true);
|
||||
hexLineEdit->setValidator(new QRegExpValidator(QRegExp("[0-9A-F]*")));
|
||||
dataLayout->addWidget(hexLineEdit);
|
||||
|
||||
textEdit = new QTextEdit;
|
||||
dataLayout->addWidget(textEdit);
|
||||
|
||||
auto dataWidget = new QWidget;
|
||||
dataWidget->setLayout(dataLayout);
|
||||
splitter->addWidget(dataWidget);
|
||||
|
||||
viewerWidget->setMode(ViewerWidget::Mode::Text);
|
||||
splitter->addWidget(QWidget::createWindowContainer(viewerWidget));
|
||||
|
||||
auto mainLayout = new QHBoxLayout;
|
||||
mainLayout->addWidget(splitter);
|
||||
setLayout(mainLayout);
|
||||
|
||||
connect(textSizeSpinBox, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &TextViewer::onFontSizeChange);
|
||||
connect(textEdit, &QTextEdit::textChanged, this, &TextViewer::onStringChange);
|
||||
}
|
||||
|
||||
void TextViewer::onFontChange(font_t font) {
|
||||
currentFont = font;
|
||||
emit textModel->fontChanged(font);
|
||||
updateRender();
|
||||
}
|
||||
|
||||
void TextViewer::onFontSizeChange(int size) {
|
||||
currentFontSize = size;
|
||||
updateRender();
|
||||
}
|
||||
|
||||
void TextViewer::onStringChange() {
|
||||
auto string = textEdit->toPlainText();
|
||||
auto newGameString = GameStringUtil::fromString(string.toStdString(), currentFont);
|
||||
onGameStringChange(newGameString);
|
||||
}
|
||||
|
||||
void TextViewer::onGameStringChange(const GameString &gameString) {
|
||||
if (!currentGameString.compare(gameString)) {
|
||||
return;
|
||||
}
|
||||
currentGameString = gameString;
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << std::hex;
|
||||
for (auto c : gameString) {
|
||||
oss << std::setw(sizeof(gameString[0])) << std::setfill('0')
|
||||
<< int(c) << " ";
|
||||
}
|
||||
auto newHexText = QString::fromStdString(oss.str());
|
||||
if (hexLineEdit->text().compare(newHexText)) {
|
||||
hexLineEdit->setText(newHexText);
|
||||
}
|
||||
auto newText = QString::fromStdString(GameStringUtil::toString(gameString, currentFont));
|
||||
if (textEdit->toPlainText().compare(newText)) {
|
||||
textEdit->setText(newText);
|
||||
}
|
||||
|
||||
updateRender();
|
||||
}
|
||||
|
||||
void TextViewer::updateRender() {
|
||||
viewerWidget->clearText();
|
||||
const int ROW_STRIDE = currentFontSize * 1.2;
|
||||
const int COL_STRIDE = currentFontSize * 1.2;
|
||||
{
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = currentFont;
|
||||
ti.size = currentFontSize;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
ti.backgroundColour = glm::u8vec4(0, 0, 0, 0);
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Left;
|
||||
ti.wrapX = 0;
|
||||
|
||||
ti.text = currentGameString;
|
||||
ti.screenPosition = glm::vec2(10, 10);
|
||||
viewerWidget->showText(ti);
|
||||
}
|
||||
|
||||
{
|
||||
TextRenderer::TextInfo ti;
|
||||
ti.font = currentFont;
|
||||
ti.size = currentFontSize;
|
||||
ti.baseColour = glm::u8vec3(255);
|
||||
ti.backgroundColour = glm::u8vec4(0, 0, 0, 0);
|
||||
ti.align = TextRenderer::TextInfo::TextAlignment::Left;
|
||||
ti.wrapX = 0;
|
||||
|
||||
for(GameStringChar c=0x20; c<0xb2; ++c) {
|
||||
unsigned column = c % 0x10;
|
||||
unsigned row = (c / 0x10) - 2 + 3; /* +3 to offset first line*/
|
||||
ti.text = c;
|
||||
ti.screenPosition = glm::vec2(10 + (column * COL_STRIDE), 10 + (row * ROW_STRIDE));
|
||||
viewerWidget->showText(ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TextViewer::worldChanged() {
|
||||
auto textNames = getFontTextureNames();
|
||||
TextMapType textMap;
|
||||
LoaderGXT loader;
|
||||
std::set<std::string> keys;
|
||||
for(const auto &textName : textNames) {
|
||||
GameTexts texts;
|
||||
auto handle = world()->data->index.openFile(textName);
|
||||
loader.load(texts, handle);
|
||||
const auto &language = textName;
|
||||
textMap.languages.push_back(language);
|
||||
const auto &stringTable = texts.getStringTable();
|
||||
for (const auto &tableItem : stringTable) {
|
||||
keys.insert(tableItem.first);
|
||||
textMap.map_lang_key_tran[language][tableItem.first] = tableItem.second;
|
||||
}
|
||||
}
|
||||
textMap.keys.resize(keys.size());
|
||||
std::move(keys.begin(), keys.end(), textMap.keys.begin());
|
||||
|
||||
textModel->setData(textMap);
|
||||
}
|
||||
|
||||
std::vector<std::string> TextViewer::getFontTextureNames() {
|
||||
const auto &gameDataPath = rwfs::path(world()->data->getDataPath());
|
||||
rwfs::path textPath;
|
||||
for (const rwfs::path &p : rwfs::directory_iterator(gameDataPath)) {
|
||||
if (!rwfs::is_directory(p)) {
|
||||
continue;
|
||||
}
|
||||
std::string filename = p.filename().string();
|
||||
std::transform(filename.begin(), filename.end(), filename.begin(), ::tolower);
|
||||
if (!filename.compare("text")) {
|
||||
textPath = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!textPath.string().length()) {
|
||||
throw std::runtime_error("text directory not found in gamedata path");
|
||||
}
|
||||
std::vector<std::string> names;
|
||||
for (const rwfs::path &p : rwfs::directory_iterator(textPath)) {
|
||||
// auto langName = p.lexically_relative(gameDataPath).string();
|
||||
auto langName = p.filename().string();
|
||||
std::transform(langName.begin(), langName.end(), langName.begin(), ::tolower);
|
||||
names.push_back(langName);
|
||||
}
|
||||
return names;
|
||||
}
|
64
rwviewer/views/TextViewer.hpp
Normal file
64
rwviewer/views/TextViewer.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#ifndef _TEXTVIEWER_HPP_
|
||||
#define _TEXTVIEWER_HPP_
|
||||
|
||||
#include "ViewerInterface.hpp"
|
||||
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <rw/filesystem.hpp>
|
||||
|
||||
#include <QTableView>
|
||||
|
||||
#include <vector>
|
||||
|
||||
class TextModel;
|
||||
class ViewerWidget;
|
||||
|
||||
class QLayout;
|
||||
class QItemSelection;
|
||||
class QLineEdit;
|
||||
class QTextEdit;
|
||||
class QModelIndex;
|
||||
class QString;
|
||||
class QWidget;
|
||||
|
||||
class TextTableView : public QTableView {
|
||||
Q_OBJECT
|
||||
protected slots:
|
||||
virtual void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) override;
|
||||
signals:
|
||||
void gameStringChanged(const GameString &string);
|
||||
};
|
||||
|
||||
class TextViewer : public ViewerInterface {
|
||||
Q_OBJECT
|
||||
|
||||
TextModel *textModel;
|
||||
TextTableView *textTable;
|
||||
ViewerWidget *viewerWidget;
|
||||
|
||||
QLineEdit *hexLineEdit;
|
||||
QTextEdit *textEdit;
|
||||
|
||||
virtual void worldChanged() override;
|
||||
|
||||
GameString currentGameString;
|
||||
font_t currentFont;
|
||||
int currentFontSize;
|
||||
|
||||
void updateRender();
|
||||
|
||||
void setGameString(const GameString &gameString);
|
||||
std::vector<std::string> getFontTextureNames();
|
||||
public:
|
||||
TextViewer(QWidget* parent = nullptr, Qt::WindowFlags f = nullptr);
|
||||
|
||||
signals:
|
||||
void fontChanged(font_t font);
|
||||
private slots:
|
||||
void onGameStringChange(const GameString &gameString);
|
||||
void onStringChange();
|
||||
void onFontChange(size_t font);
|
||||
void onFontSizeChange(int font);
|
||||
};
|
||||
|
||||
#endif
|
@ -31,6 +31,7 @@ set(TESTS
|
||||
SaveGame
|
||||
ScriptMachine
|
||||
State
|
||||
StringEncoding
|
||||
Text
|
||||
TrafficDirector
|
||||
Vehicle
|
||||
|
200
tests/test_StringEncoding.cpp
Normal file
200
tests/test_StringEncoding.cpp
Normal file
@ -0,0 +1,200 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <fonts/FontMapGta3.hpp>
|
||||
#include <fonts/Unicode.hpp>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
BOOST_TEST_DONT_PRINT_LOG_VALUE(GameString)
|
||||
|
||||
/**
|
||||
* All tests about changing string encodings.
|
||||
*/
|
||||
BOOST_AUTO_TEST_SUITE(StringEncodingTests)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unicode_to_char_1char) {
|
||||
char val[4];
|
||||
unicode_t u = 0x3f; /* QUESTION MARK */
|
||||
auto nb = unicode_to_utf8(u, val);
|
||||
|
||||
BOOST_CHECK_EQUAL(nb, 1);
|
||||
|
||||
const char ref[1] = {0x3f};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(val, val + nb, ref, ref + nb);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unicode_to_char_2char) {
|
||||
char val[4];
|
||||
unicode_t u = 0x00e6; /* LATIN SMALL LETTER AE */
|
||||
auto nb = unicode_to_utf8(u, val);
|
||||
|
||||
BOOST_CHECK_EQUAL(nb, 2);
|
||||
|
||||
const char ref[2] = {char(0xc3), char(0xa6)};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(val, val + nb, ref, ref + nb);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unicode_to_char_3char) {
|
||||
char val[4];
|
||||
unicode_t u = 0x0f45; /* TIBETAN LETTER CA */
|
||||
auto nb = unicode_to_utf8(u, val);
|
||||
|
||||
BOOST_CHECK_EQUAL(nb, 3);
|
||||
|
||||
const char ref[3] = {char(0xe0), char(0xbd), char(0x85)};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(val, val + nb, ref, ref + nb);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unicode_to_char_4char) {
|
||||
char val[4];
|
||||
unicode_t u = 0x10454; /* SHAVIAN LETTER THIGH */
|
||||
auto nb = unicode_to_utf8(u, val);
|
||||
|
||||
BOOST_CHECK_EQUAL(nb, 4);
|
||||
|
||||
const char ref[4] = {char(0xf0), char(0x90), char(0x91), char(0x94)};
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(val, val + nb, ref, ref + nb);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(unicode_to_char_illegal) {
|
||||
char val[4];
|
||||
unicode_t u = 0x124544; /* Illegal unicode */
|
||||
auto nb = unicode_to_utf8(u, val);
|
||||
|
||||
BOOST_CHECK_EQUAL(nb, 3);
|
||||
|
||||
const char ref[4] = {char(0xef), char(0xbf), char(0xbd)}; // utf-8 encoding of replacement character
|
||||
BOOST_CHECK_EQUAL_COLLECTIONS(val, val + nb, ref, ref + nb);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(utf8_iterator_simple) {
|
||||
std::string s("Hello World", 12);
|
||||
std::istringstream iss(s);
|
||||
Utf8UnicodeIterator it(iss);
|
||||
|
||||
BOOST_CHECK_EQUAL(s.size(), 12);
|
||||
|
||||
for (size_t i=0; i < s.size(); ++i) {
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL(it.unicode(), s[i]);
|
||||
++it;
|
||||
}
|
||||
BOOST_CHECK(!it.good());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(utf8_iterator_invalid) {
|
||||
const unsigned char s[] = {'a', 0xff, 'b', 0xff, 'c', 0x00};
|
||||
std::istringstream iss(reinterpret_cast<const char *>(s));
|
||||
Utf8UnicodeIterator it(iss);
|
||||
|
||||
BOOST_CHECK_EQUAL(sizeof(s), 6);
|
||||
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL('a', it.unicode());
|
||||
|
||||
++it;
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL(UNICODE_REPLACEMENT_CHARACTER, it.unicode());
|
||||
|
||||
++it;
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL('b', it.unicode());
|
||||
|
||||
++it;
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL(UNICODE_REPLACEMENT_CHARACTER, it.unicode());
|
||||
|
||||
++it;
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL('c', it.unicode());
|
||||
|
||||
++it;
|
||||
BOOST_CHECK(!it.good());
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *utf8;
|
||||
unicode_t unicode;
|
||||
} utf8_unicode_t;
|
||||
|
||||
const utf8_unicode_t utf_unicode_data[] = {
|
||||
{
|
||||
"\x2e", 0x2e, /* full stop*/
|
||||
}, {
|
||||
"\x77", 0x77, /* w */
|
||||
}, {
|
||||
"\xc3\x97", 0xd7, /* multiplication sign */
|
||||
}, {
|
||||
"\xd8\x8c", 0x060c, /* Arabic comma */
|
||||
}, {
|
||||
"\xe2\x9b\xb0", 0x26f0, /* mountain */
|
||||
}, {
|
||||
"\xe2\x8a\xa8", 0x22a8, /* true */
|
||||
}, {
|
||||
"\xf0\x9f\xa7\x9b", 0x1f9db, /* vampire */
|
||||
}, {
|
||||
"\xf0\x9f\xa4\x9f", 0x1f91f, /* I love you hand sign */
|
||||
}, {
|
||||
"", 0, /* sentinel */
|
||||
}
|
||||
};
|
||||
|
||||
std::string createUtf8String() {
|
||||
std::ostringstream oss;
|
||||
for (const utf8_unicode_t *uu = utf_unicode_data; uu->unicode; ++uu) {
|
||||
oss << uu->utf8;
|
||||
}
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(utf8_iterator_mixed) {
|
||||
std::string str = createUtf8String();
|
||||
std::istringstream iss(str);
|
||||
Utf8UnicodeIterator it(iss);
|
||||
|
||||
size_t nb = 0;
|
||||
for (const utf8_unicode_t *uu = utf_unicode_data; uu->unicode; ++uu) {
|
||||
BOOST_CHECK(it.good());
|
||||
BOOST_CHECK_EQUAL(it.unicode(), uu->unicode);
|
||||
++it;
|
||||
++nb;
|
||||
}
|
||||
BOOST_CHECK(!it.good());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(utf8_iterator_ranged_for_loop) {
|
||||
std::string str = createUtf8String();
|
||||
std::istringstream iss(str);
|
||||
Utf8UnicodeIterator it(iss);
|
||||
|
||||
size_t nb = 0;
|
||||
const utf8_unicode_t *uu = utf_unicode_data;
|
||||
for (unicode_t u : Utf8UnicodeIteratorWrapper(str)) {
|
||||
BOOST_CHECK_EQUAL(u, uu->unicode);
|
||||
++it;
|
||||
++nb;
|
||||
++uu;
|
||||
}
|
||||
BOOST_CHECK(!it.good());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(GameStringChar_simple) {
|
||||
for (const auto &fontmap : fontmaps_gta3_font) {
|
||||
auto c = fontmap.to_GameStringChar('x');
|
||||
BOOST_CHECK_EQUAL(c, GameStringChar('x'));
|
||||
auto u = fontmap.to_unicode('x');
|
||||
BOOST_CHECK_EQUAL(u, unicode_t('x'));
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(GameString_simple) {
|
||||
std::string s = "Hello world";
|
||||
for (const auto &fontmap : fontmaps_gta3_font) {
|
||||
auto gs = fontmap.to_GameString(s);
|
||||
BOOST_CHECK_EQUAL(s.size(), gs.length());
|
||||
for (size_t i = 0; i < s.size(); ++i) {
|
||||
BOOST_CHECK_EQUAL(gs[i], GameStringChar(s[i]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
@ -1,10 +1,10 @@
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <data/GameTexts.hpp>
|
||||
#include <engine/ScreenText.hpp>
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <loaders/LoaderGXT.hpp>
|
||||
#include "test_Globals.hpp"
|
||||
|
||||
#define T(x) GameStringUtil::fromString(x)
|
||||
#define T(x) GameStringUtil::fromString(x, FONT_PRICEDOWN)
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(TextTests)
|
||||
|
||||
@ -22,6 +22,15 @@ BOOST_AUTO_TEST_CASE(load_test) {
|
||||
BOOST_CHECK_EQUAL(texts.text("1008"), T("BUSTED"));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_CASE(special_chars) {
|
||||
{
|
||||
auto newline = T("\n");
|
||||
BOOST_CHECK_EQUAL(newline.size(), 1);
|
||||
BOOST_CHECK_EQUAL(newline[0], '\n');
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(big_test) {
|
||||
// Check that makeBig creates a text in the right place
|
||||
@ -132,6 +141,5 @@ BOOST_AUTO_TEST_CASE(format_remove) {
|
||||
|
||||
BOOST_CHECK_EQUAL(1, st.getText<ScreenTextType::Big>().size());
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
Reference in New Issue
Block a user