mirror of
https://github.com/rwengine/openrw.git
synced 2024-11-07 03:12:36 +01:00
commit
31f32b2173
@ -78,6 +78,9 @@ if(BUILD_TESTS)
|
||||
include(CTest)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
if(BUILD_TOOLS)
|
||||
add_subdirectory(rwtools)
|
||||
endif()
|
||||
|
||||
# Copy the license to the install directory
|
||||
install(FILES COPYING
|
||||
|
@ -1,5 +1,6 @@
|
||||
option(RW_VERBOSE_DEBUG_MESSAGES "Print verbose debugging messages" ON)
|
||||
|
||||
option(BUILD_TOOLS "Build tools")
|
||||
option(BUILD_TESTS "Build test suite")
|
||||
option(BUILD_VIEWER "Build GUI data viewer")
|
||||
|
||||
|
11
conanfile.py
11
conanfile.py
@ -15,11 +15,13 @@ class OpenrwConan(ConanFile):
|
||||
options = {
|
||||
'test_data': [True, False],
|
||||
'viewer': [True, False],
|
||||
'tools': [True, False],
|
||||
}
|
||||
|
||||
default_options = (
|
||||
'test_data=False',
|
||||
'viewer=False',
|
||||
'tools=False',
|
||||
'bullet:shared=False',
|
||||
'ffmpeg:iconv=False',
|
||||
'libalsa:disable_python=True', # https://github.com/conan-community/community/issues/3
|
||||
@ -41,7 +43,10 @@ class OpenrwConan(ConanFile):
|
||||
),
|
||||
'viewer': (
|
||||
'Qt/5.11@bincrafters/stable',
|
||||
)
|
||||
),
|
||||
'tools': (
|
||||
'freetype/2.9.0@bincrafters/stable',
|
||||
),
|
||||
}
|
||||
|
||||
def requirements(self):
|
||||
@ -50,6 +55,9 @@ class OpenrwConan(ConanFile):
|
||||
if self.options.viewer:
|
||||
for dep in self._rw_dependencies['viewer']:
|
||||
self.requires(dep)
|
||||
if self.options.tools:
|
||||
for dep in self._rw_dependencies['tools']:
|
||||
self.requires(dep)
|
||||
|
||||
def _configure_cmake(self):
|
||||
cmake = CMake(self)
|
||||
@ -58,6 +66,7 @@ class OpenrwConan(ConanFile):
|
||||
'CMAKE_BUILD_TYPE': self.settings.build_type,
|
||||
'BUILD_TESTS': True,
|
||||
'BUILD_VIEWER': self.options.viewer,
|
||||
'BUILD_TOOLS': self.options.tools,
|
||||
'TESTS_NODATA': not self.options.test_data,
|
||||
'USE_CONAN': True,
|
||||
'BOOST_STATIC': not self.options['boost'].shared,
|
||||
|
@ -12,18 +12,24 @@
|
||||
#include "engine/GameData.hpp"
|
||||
#include "render/GameRenderer.hpp"
|
||||
|
||||
int charToIndex(uint16_t g) {
|
||||
namespace {
|
||||
|
||||
unsigned charToIndex(std::uint16_t g) {
|
||||
// Correct for the default font maps
|
||||
/// @todo confirm for JA / RU font maps
|
||||
return g - 32;
|
||||
}
|
||||
|
||||
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 == 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);
|
||||
glm::vec4 indexToTexCoord(int index, const glm::u32vec2 &textureSize, const glm::u8vec2 &glyphOffset) {
|
||||
constexpr unsigned TEXTURE_COLUMNS = 16;
|
||||
const float x = index % TEXTURE_COLUMNS;
|
||||
const float y = index / TEXTURE_COLUMNS;
|
||||
// Add offset to avoid 'leakage' between adjacent glyphs
|
||||
float s = (x * glyphOffset.x + 0.5f) / textureSize.x;
|
||||
float t = (y * glyphOffset.y + 0.5f) / textureSize.y;
|
||||
float p = ((x + 1) * glyphOffset.x - 1.5f) / textureSize.x;
|
||||
float q = ((y + 1) * glyphOffset.y - 1.5f) / textureSize.y;
|
||||
return glm::vec4(s, t, p, q);
|
||||
}
|
||||
|
||||
const char* TextVertexShader = R"(
|
||||
@ -40,9 +46,9 @@ uniform vec2 alignment;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = proj * vec4(alignment + position, 0.0, 1.0);
|
||||
TexCoord = texcoord;
|
||||
Colour = colour;
|
||||
gl_Position = proj * vec4(alignment + position, 0.0, 1.0);
|
||||
TexCoord = texcoord;
|
||||
Colour = colour;
|
||||
})";
|
||||
|
||||
const char* TextFragmentShader = R"(
|
||||
@ -56,10 +62,64 @@ out vec4 outColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
float a = texture(fontTexture, TexCoord).a;
|
||||
outColour = vec4(Colour, a);
|
||||
float a = texture(fontTexture, TexCoord).a;
|
||||
outColour = vec4(Colour, a);
|
||||
})";
|
||||
|
||||
|
||||
constexpr size_t GLYPHS_NB = 193;
|
||||
using FontWidthLut = std::array<std::uint8_t, GLYPHS_NB>;
|
||||
|
||||
constexpr std::array<std::uint8_t, 193> fontWidthsPager = {
|
||||
3, 3, 6, 8, 6, 10, 8, 3, 5, 5, 7, 0, 3, 7, 3, 0, // 1
|
||||
6, 4, 6, 6, 7, 6, 6, 6, 6, 6, 3, 0, 0, 0, 0, 6, // 2
|
||||
0, 6, 6, 6, 6, 6, 6, 6, 6, 3, 6, 6, 5, 8, 7, 6, // 3
|
||||
6, 7, 6, 6, 5, 6, 6, 8, 6, 7, 7, 0, 0, 0, 0, 0, // 4
|
||||
0, 6, 6, 6, 6, 6, 5, 6, 6, 3, 4, 6, 3, 9, 6, 6, // 5
|
||||
6, 6, 5, 6, 5, 6, 6, 8, 6, 6, 5, 0, 0, 0, 0, 0, // 6
|
||||
6, 6, 6, 6, 8, 6, 6, 6, 6, 6, 5, 5, 6, 6, 6, 6, // 7
|
||||
6, 6, 6, 6, 6, 6, 7, 6, 6, 6, 6, 9, 6, 6, 6, 6, // 8
|
||||
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 6, 6, // 9
|
||||
3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 10
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 11
|
||||
8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 12
|
||||
8,
|
||||
};
|
||||
|
||||
constexpr std::array<std::uint8_t, 193> fontWidthsPriceDown = {
|
||||
11, 13, 30, 27, 20, 24, 22, 12, 14, 14, 0, 26, 9, 14, 9, 26, // 1
|
||||
20, 19, 20, 20, 22, 20, 20, 19, 20, 20, 13, 29, 24, 29, 24, 20, // 2
|
||||
27, 20, 20, 20, 20, 20, 17, 20, 20, 10, 20, 20, 15, 30, 20, 20, // 3
|
||||
20, 20, 20, 20, 22, 20, 22, 32, 20, 20, 19, 27, 20, 32, 23, 13, // 4
|
||||
27, 21, 21, 21, 21, 21, 18, 22, 21, 12, 20, 22, 17, 30, 22, 21, // 5
|
||||
21, 21, 21, 22, 21, 21, 21, 29, 19, 23, 21, 28, 25, 0, 0, 0, // 6
|
||||
20, 20, 20, 20, 30, 20, 20, 20, 20, 20, 10, 10, 10, 10, 21, 21, // 7
|
||||
21, 21, 20, 20, 20, 20, 21, 21, 21, 21, 21, 32, 23, 21, 21, 21, // 8
|
||||
21, 12, 12, 12, 12, 21, 21, 21, 21, 21, 21, 21, 21, 20, 20, 20, // 9
|
||||
13, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // 10
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // 11
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // 12
|
||||
16,
|
||||
};
|
||||
|
||||
constexpr std::array<std::uint8_t, 193> fontWidthsArial = {
|
||||
27, 25, 55, 43, 47, 65, 53, 19, 29, 31, 21, 45, 23, 35, 27, 29, // 1
|
||||
47, 33, 45, 43, 49, 47, 47, 41, 47, 45, 25, 23, 53, 43, 53, 39, // 2
|
||||
61, 53, 51, 47, 49, 45, 43, 49, 53, 23, 41, 53, 45, 59, 53, 51, // 3
|
||||
47, 51, 49, 49, 45, 51, 49, 59, 59, 47, 51, 31, 27, 31, 29, 27, // 4
|
||||
19, 43, 45, 43, 43, 45, 27, 45, 43, 21, 33, 45, 23, 65, 43, 43, // 5
|
||||
47, 45, 33, 41, 29, 43, 41, 61, 51, 43, 43, 67, 53, 67, 67, 71, // 6
|
||||
53, 53, 53, 53, 65, 49, 45, 45, 45, 45, 23, 23, 23, 23, 51, 51, // 7
|
||||
51, 51, 51, 51, 51, 51, 51, 43, 43, 43, 43, 65, 43, 45, 45, 45, // 8
|
||||
45, 21, 21, 21, 21, 43, 43, 43, 43, 43, 43, 43, 43, 53, 43, 39, // 9
|
||||
25, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // 10
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 11, 19, 19, // 11
|
||||
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, // 12
|
||||
19,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
struct TextVertex {
|
||||
glm::vec2 position;
|
||||
glm::vec2 texcoord;
|
||||
@ -85,46 +145,43 @@ struct TextVertex {
|
||||
TextRenderer::TextRenderer(GameRenderer* renderer) : renderer(renderer) {
|
||||
textShader = renderer->getRenderer()->createShader(TextVertexShader,
|
||||
TextFragmentShader);
|
||||
|
||||
std::fill(glyphData.begin(), glyphData.end(), GlyphInfo{.9f});
|
||||
|
||||
glyphData[charToIndex(' ')].widthFrac = 0.4f;
|
||||
glyphData[charToIndex('-')].widthFrac = 0.5f;
|
||||
glyphData[charToIndex('\'')].widthFrac = 0.5f;
|
||||
glyphData[charToIndex('(')].widthFrac = 0.45f;
|
||||
glyphData[charToIndex(')')].widthFrac = 0.45f;
|
||||
glyphData[charToIndex(':')].widthFrac = 0.65f;
|
||||
glyphData[charToIndex('$')].widthFrac = 0.65f;
|
||||
|
||||
for (char g = '0'; g <= '9'; ++g) {
|
||||
glyphData[charToIndex(g)].widthFrac = 0.65f;
|
||||
}
|
||||
|
||||
// Assumes contiguous a-z character encoding
|
||||
for (char g = 0; g <= ('z' - 'a'); g++) {
|
||||
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) {
|
||||
if (index < GAME_FONTS) {
|
||||
fonts[index] = texture;
|
||||
void TextRenderer::setFontTexture(font_t font, const std::string& textureName) {
|
||||
if (font >= FONTS_COUNT) {
|
||||
RW_ERROR("Illegal font: " << font);
|
||||
return;
|
||||
}
|
||||
auto ftexture = renderer->getData()->findSlotTexture("fonts", textureName);
|
||||
const glm::u32vec2 textureSize = ftexture->getSize();
|
||||
glm::u8vec2 glyphOffset{textureSize.x / 16, textureSize.x / 16};
|
||||
if (font != FONT_PAGER) {
|
||||
glyphOffset.y += glyphOffset.y / 4;
|
||||
}
|
||||
const FontWidthLut *glyphWidths;
|
||||
switch (font) {
|
||||
case FONT_PAGER:
|
||||
glyphWidths = &fontWidthsPager;
|
||||
break;
|
||||
case FONT_PRICEDOWN:
|
||||
glyphWidths = &fontWidthsPriceDown;
|
||||
break;
|
||||
case FONT_ARIAL:
|
||||
glyphWidths = &fontWidthsArial;
|
||||
break;
|
||||
}
|
||||
std::uint8_t monoWidth = 0;
|
||||
if (font == FONT_PAGER) {
|
||||
monoWidth = 1 + *std::max_element(fontWidthsPager.cbegin(),
|
||||
fontWidthsPager.cend());
|
||||
}
|
||||
fonts[font] = FontMetaData{
|
||||
textureName,
|
||||
*glyphWidths,
|
||||
textureSize,
|
||||
glyphOffset,
|
||||
monoWidth
|
||||
};
|
||||
}
|
||||
|
||||
void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
@ -151,11 +208,13 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
|
||||
auto text = ti.text;
|
||||
|
||||
const auto &fontMetaData = fonts[ti.font];
|
||||
|
||||
for (size_t i = 0; i < text.length(); ++i) {
|
||||
char16_t c = text[i];
|
||||
|
||||
// Handle any markup changes.
|
||||
if (c == '~' && text.length() > i + 1) {
|
||||
if (c == '~' && text.length() > i + 2) {
|
||||
switch (text[i + 1]) {
|
||||
case 'b': // Blue
|
||||
text.erase(text.begin() + i, text.begin() + i + 3);
|
||||
@ -209,8 +268,17 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
colour = glm::vec3(ti.baseColour) * (1 / 255.f);
|
||||
}
|
||||
|
||||
int glyph = charToIndex(c);
|
||||
if (glyph >= GAME_GLYPHS) {
|
||||
// Handle special chars.
|
||||
if (c == '\n') {
|
||||
coord.x = 0.f;
|
||||
coord.y += ss.y;
|
||||
maxHeight = coord.y + ss.y;
|
||||
lineLength = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto glyph = charToIndex(c);
|
||||
if (glyph >= fontMetaData.glyphWidths.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -230,24 +298,20 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
}
|
||||
}
|
||||
|
||||
auto& data = glyphData[glyph];
|
||||
auto tex = indexToCoord(ti.font, glyph);
|
||||
|
||||
ss.x = ti.size * data.widthFrac;
|
||||
tex.z = tex.x + (tex.z - tex.x) * data.widthFrac;
|
||||
|
||||
// Handle special chars.
|
||||
if (c == '\n') {
|
||||
coord.x = 0.f;
|
||||
coord.y += ss.y;
|
||||
maxHeight = coord.y + ss.y;
|
||||
lineLength = 0;
|
||||
continue;
|
||||
}
|
||||
auto tex = indexToTexCoord(glyph, fontMetaData.textureSize, fontMetaData.glyphOffset);
|
||||
ss.x = ti.size * static_cast<float>(fontMetaData.glyphOffset.x) / fontMetaData.glyphOffset.y;
|
||||
lineLength++;
|
||||
|
||||
glm::vec2 p = coord;
|
||||
coord.x += ss.x;
|
||||
float factor = ti.size / static_cast<float>(fontMetaData.glyphOffset.y);
|
||||
float glyphWidth = factor * static_cast<float>(fontMetaData.glyphWidths[glyph]);
|
||||
if (fontMetaData.monoWidth != 0) {
|
||||
float monoWidth = factor * fontMetaData.monoWidth;
|
||||
p.x += static_cast<float>(monoWidth - glyphWidth) / 2;
|
||||
coord.x += monoWidth;
|
||||
} else {
|
||||
coord.x += glyphWidth;
|
||||
}
|
||||
maxWidth = std::max(coord.x, maxWidth);
|
||||
|
||||
geo.emplace_back(glm::vec2{p.x, p.y + ss.y}, glm::vec2{tex.x, tex.w}, colour);
|
||||
@ -287,7 +351,7 @@ void TextRenderer::renderText(const TextRenderer::TextInfo& ti,
|
||||
dp.start = 0;
|
||||
dp.blendMode = BlendMode::BLEND_ALPHA;
|
||||
dp.count = gb.getCount();
|
||||
auto ftexture = renderer->getData()->findSlotTexture("fonts", fonts[ti.font]);
|
||||
auto ftexture = renderer->getData()->findSlotTexture("fonts", fontMetaData.textureName);
|
||||
dp.textures = {ftexture->getName()};
|
||||
dp.depthWrite = false;
|
||||
|
||||
|
@ -14,9 +14,6 @@
|
||||
#include <fonts/GameTexts.hpp>
|
||||
#include <render/OpenGLRenderer.hpp>
|
||||
|
||||
#define GAME_FONTS 3
|
||||
#define GAME_GLYPHS 192
|
||||
|
||||
class GameRenderer;
|
||||
/**
|
||||
* @brief Handles rendering of bitmap font textures.
|
||||
@ -62,13 +59,35 @@ public:
|
||||
TextRenderer(GameRenderer* renderer);
|
||||
~TextRenderer() = default;
|
||||
|
||||
void setFontTexture(int index, const std::string& font);
|
||||
void setFontTexture(font_t font, const std::string& textureName);
|
||||
|
||||
void renderText(const TextInfo& ti, bool forceColour = false);
|
||||
|
||||
private:
|
||||
std::string fonts[GAME_FONTS];
|
||||
std::array<GlyphInfo, GAME_GLYPHS> glyphData;
|
||||
class FontMetaData {
|
||||
public:
|
||||
FontMetaData() = default;
|
||||
template<size_t N>
|
||||
FontMetaData(const std::string &textureName,
|
||||
const std::array<std::uint8_t, N> &glyphWidths,
|
||||
const glm::u32vec2 &textureSize,
|
||||
const glm::u8vec2 &glyphOffset,
|
||||
const std::uint8_t monoWidth)
|
||||
: textureName(textureName)
|
||||
, glyphWidths(glyphWidths.cbegin(), glyphWidths.cend())
|
||||
, textureSize(textureSize)
|
||||
, glyphOffset(glyphOffset)
|
||||
, monoWidth(monoWidth)
|
||||
{
|
||||
}
|
||||
std::string textureName;
|
||||
std::vector<std::uint8_t> glyphWidths;
|
||||
glm::u32vec2 textureSize;
|
||||
glm::u8vec2 glyphOffset;
|
||||
std::uint8_t monoWidth;
|
||||
};
|
||||
|
||||
std::array<FontMetaData, FONTS_COUNT> fonts;
|
||||
|
||||
GameRenderer* renderer;
|
||||
std::unique_ptr<Renderer::ShaderProgram> textShader;
|
||||
|
@ -76,6 +76,7 @@ static const FontMap::gschar_unicode_map_t map_gta3_font_common = {
|
||||
{0x78, UnicodeValue::UNICODE_SMALL_X},
|
||||
{0x79, UnicodeValue::UNICODE_SMALL_Y},
|
||||
{0x7a, UnicodeValue::UNICODE_SMALL_Z},
|
||||
{0x7e, UnicodeValue::UNICODE_TILDE},
|
||||
{0x80, UnicodeValue::UNICODE_CAPITAL_A_GRAVE},
|
||||
{0x81, UnicodeValue::UNICODE_CAPITAL_A_ACUTE},
|
||||
{0x82, UnicodeValue::UNICODE_CAPITAL_A_CIRCUMFLEX},
|
||||
|
1
rwtools/CMakeLists.txt
Normal file
1
rwtools/CMakeLists.txt
Normal file
@ -0,0 +1 @@
|
||||
add_subdirectory(rwfont)
|
14
rwtools/rwfont/CMakeLists.txt
Normal file
14
rwtools/rwfont/CMakeLists.txt
Normal file
@ -0,0 +1,14 @@
|
||||
# fixme: conan support
|
||||
find_package(Freetype REQUIRED)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Gui)
|
||||
add_executable(rwfontmap
|
||||
rwfontmap.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(rwfontmap
|
||||
PUBLIC
|
||||
rwlib
|
||||
Freetype::Freetype
|
||||
Qt5::Gui
|
||||
Boost::program_options
|
||||
)
|
272
rwtools/rwfont/rwfontmap.cpp
Normal file
272
rwtools/rwfont/rwfontmap.cpp
Normal file
@ -0,0 +1,272 @@
|
||||
#include <fonts/FontMapGta3.hpp>
|
||||
#include <fonts/Unicode.hpp>
|
||||
#include <rw/filesystem.hpp>
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <QImage>
|
||||
#include <QImageWriter>
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
const char *ft_error_to_string(int error) {
|
||||
switch (error) {
|
||||
#define FT_NOERRORDEF_(_DC, CODE, STR) case CODE: return STR;
|
||||
#define FT_ERRORDEF_(_DC, CODE, STR) case CODE: return STR;
|
||||
#include FT_ERROR_DEFINITIONS_H
|
||||
#undef FT_ERRORDEF_
|
||||
#undef FT_NOERRORDEF_
|
||||
}
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
[[noreturn]]
|
||||
void ft_error(int code)
|
||||
{
|
||||
std::cerr << ft_error_to_string(code) << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
class FontTextureBuffer {
|
||||
public:
|
||||
FontTextureBuffer(unsigned fontsize, unsigned width, unsigned height, float height_width_ratio)
|
||||
: m_texture{static_cast<int>(width), static_cast<int>(height), QImage::Format_ARGB32} {
|
||||
m_aspectratio = height_width_ratio;
|
||||
m_fontsize = fontsize;
|
||||
m_glyph_width = width / 16;
|
||||
m_glyph_height = height_width_ratio * m_glyph_width;
|
||||
|
||||
int error = FT_Init_FreeType(&m_library);
|
||||
if (error) ft_error(error);
|
||||
}
|
||||
|
||||
~FontTextureBuffer() {
|
||||
for (auto face : m_faces) {
|
||||
int error = FT_Done_Face(face);
|
||||
if (error) ft_error(error);
|
||||
}
|
||||
int error = FT_Done_FreeType(m_library);
|
||||
if (error) ft_error(error);
|
||||
}
|
||||
|
||||
void clear() {
|
||||
m_texture.fill(QColor{0, 0, 0, 0});
|
||||
m_advances.clear();
|
||||
}
|
||||
|
||||
void add_face(const char *path) {
|
||||
m_faces.emplace_back();
|
||||
FT_Face &face = m_faces.back();
|
||||
int error = FT_New_Face(m_library, path, 0, &face);
|
||||
if (error) ft_error(error);
|
||||
error = FT_Select_Charmap(face, FT_ENCODING_UNICODE);
|
||||
if (error) ft_error(error);
|
||||
error = FT_Set_Pixel_Sizes(face, 0, m_fontsize);
|
||||
if (error) ft_error(error);
|
||||
}
|
||||
|
||||
void draw_glyph_mono(int index, FT_GlyphSlot glyph) {
|
||||
int x = index % 16;
|
||||
int y = index / 16 - 2;
|
||||
QPoint topleft = {x * static_cast<int>(m_glyph_width), y * static_cast<int>(m_glyph_height)};
|
||||
QPoint baselineleft = topleft + QPoint{0, static_cast<int>(m_glyph_width)};
|
||||
|
||||
if (baselineleft.x() + glyph->metrics.horiBearingX / 64 + glyph->metrics.width / 64 >= topleft.x() + m_glyph_width) {
|
||||
std::cerr << "index " << index << " crosses right border\n";
|
||||
}
|
||||
if (baselineleft.y() - glyph->metrics.horiBearingY / 64 < topleft.y()) {
|
||||
std::cerr << "index " << index << " crosses top border\n";
|
||||
}
|
||||
if (baselineleft.y() - glyph->metrics.horiBearingY / 64 + glyph->metrics.height / 64 >= topleft.y() + m_glyph_height) {
|
||||
std::cerr << "index " << index << " crosses bottom border\n";
|
||||
}
|
||||
|
||||
for (unsigned row = 0; row < glyph->bitmap.rows; ++row) {
|
||||
const unsigned char *buffer = glyph->bitmap.buffer + row * glyph->bitmap.pitch;
|
||||
for (unsigned i = 0; i < glyph->bitmap.width; ++i) {
|
||||
bool pixel = (buffer[i/8] << (i % 8)) & 0x80;
|
||||
QPoint point = baselineleft + QPoint{static_cast<int>(i), static_cast<int>(row - (glyph->metrics.horiBearingY / 64))};
|
||||
QColor color{255, 255, 255, pixel ? 255 : 0};
|
||||
m_texture.setPixelColor(point, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void draw_glyph_normal(int index, FT_GlyphSlot glyph) {
|
||||
int x = index % 16;
|
||||
int y = index / 16 - 2;
|
||||
QPoint topleft = {x * static_cast<int>(m_glyph_width), y * static_cast<int>(m_glyph_height)};
|
||||
QPoint baselineleft = topleft + QPoint{0, 4 * static_cast<int>(m_glyph_height) / 5};
|
||||
|
||||
if (baselineleft.x() + glyph->metrics.horiBearingX / 64 + glyph->metrics.width / 64 >= topleft.x() + m_glyph_width) {
|
||||
std::cerr << "index " << index << " crosses right border\n";
|
||||
}
|
||||
if (baselineleft.y() - glyph->metrics.horiBearingY / 64 < topleft.y()) {
|
||||
std::cerr << "index " << index << " crosses top border\n";
|
||||
}
|
||||
if (baselineleft.y() - glyph->metrics.horiBearingY / 64 + glyph->metrics.height / 64 >= topleft.y() + m_glyph_height) {
|
||||
std::cerr << "index " << index << " crosses bottom border\n";
|
||||
}
|
||||
|
||||
for (unsigned row = 0; row < glyph->bitmap.rows; ++row) {
|
||||
const unsigned char *buffer = glyph->bitmap.buffer + row * glyph->bitmap.pitch;
|
||||
for (unsigned i = 0; i < glyph->bitmap.width; ++i) {
|
||||
QPoint point = baselineleft + QPoint{static_cast<int>(i), static_cast<int>(row - (glyph->metrics.horiBearingY / 64))};
|
||||
QColor color{255, 255, 255, buffer[i]};
|
||||
m_texture.setPixelColor(point, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RenderMode {
|
||||
MONO,
|
||||
NORMAL
|
||||
};
|
||||
|
||||
void create_font_map(const FontMap &fontmap, RenderMode mode) {
|
||||
GameStringChar next = 0x20;
|
||||
FT_Render_Mode render_mode;
|
||||
switch (mode) {
|
||||
case RenderMode::MONO:
|
||||
render_mode = FT_RENDER_MODE_MONO;
|
||||
break;
|
||||
default:
|
||||
case RenderMode::NORMAL:
|
||||
render_mode = FT_RENDER_MODE_NORMAL;
|
||||
break;
|
||||
}
|
||||
for (auto it = fontmap.to_unicode_begin(); it != fontmap.to_unicode_end(); ++it) {
|
||||
while (it->first != next) {
|
||||
m_advances.push_back(0);
|
||||
++next;
|
||||
}
|
||||
for (auto face : m_faces) {
|
||||
auto glyph_index = FT_Get_Char_Index(face, it->second);
|
||||
if (glyph_index == 0) {
|
||||
continue;
|
||||
}
|
||||
int error = FT_Load_Glyph(face, glyph_index, FT_LOAD_DEFAULT);
|
||||
if (error) ft_error(error);
|
||||
error = FT_Render_Glyph(face->glyph, render_mode);
|
||||
if (error) ft_error(error);
|
||||
|
||||
switch (mode) {
|
||||
case RenderMode::MONO:
|
||||
draw_glyph_mono(it->first, face->glyph);
|
||||
break;
|
||||
case RenderMode::NORMAL:
|
||||
draw_glyph_normal(it->first, face->glyph);
|
||||
break;
|
||||
}
|
||||
|
||||
int advance = face->glyph->metrics.horiAdvance / 64;// - face->glyph->metrics.horiBearingX / 64;
|
||||
if (advance < 0 || advance > 255) {
|
||||
std::cerr << "advance out of range\n";
|
||||
}
|
||||
m_advances.push_back(advance);
|
||||
++next;
|
||||
break;
|
||||
}
|
||||
if (it->first + 1 != next) {
|
||||
std::cerr << "unknown character.\n"; exit(1);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void write(const rwfs::path &bitmap_path, const rwfs::path &advance_path) {
|
||||
QImageWriter writer(bitmap_path.c_str());
|
||||
writer.write(m_texture);
|
||||
std::ofstream ofs(advance_path.c_str(), std::ios_base::out);
|
||||
ofs << m_aspectratio << '\n';
|
||||
for (auto adv : m_advances) {
|
||||
ofs << int(adv) << '\n';
|
||||
}
|
||||
// ofs.write(reinterpret_cast<const char *>(m_advances.data()), m_advances.size());
|
||||
}
|
||||
|
||||
private:
|
||||
QImage m_texture;
|
||||
|
||||
float m_aspectratio;
|
||||
unsigned m_fontsize;
|
||||
unsigned m_glyph_width;
|
||||
unsigned m_glyph_height;
|
||||
FT_Library m_library;
|
||||
|
||||
std::vector<FT_Face> m_faces;
|
||||
|
||||
std::vector<std::uint8_t> m_advances;
|
||||
};
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
namespace po = boost::program_options;
|
||||
po::options_description desc("Options");
|
||||
desc.add_options()
|
||||
("help", "Show this help message")
|
||||
("fontsize,s", po::value<unsigned>()->value_name("FONTSIZE")->required(), "Fontsize")
|
||||
("width,w", po::value<unsigned>()->value_name("WIDTH")->required(), "Width of the texture")
|
||||
("height,h", po::value<unsigned>()->value_name("HEIGHT")->required(), "Height of the texture")
|
||||
("ratio,r", po::value<float>()->value_name("ASPECTRATIO"), "Aspect ratio")
|
||||
("map,m", po::value<unsigned>()->value_name("MAP")->required(), "Font map to use")
|
||||
("font,f", po::value<std::vector<std::string>>()->value_name("PATH")->required(), "Path to fonts")
|
||||
("texture,t", po::value<rwfs::path>()->value_name("PATH")->required(), "Output texture")
|
||||
("advance,a", po::value<rwfs::path>()->value_name("PATH")->required(), "Output advances")
|
||||
;
|
||||
|
||||
po::variables_map vm;
|
||||
try {
|
||||
po::store(po::parse_command_line(argc, argv, desc), vm);
|
||||
if (vm.count("help")) {
|
||||
std::cout << desc;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
po::notify(vm);
|
||||
} catch (po::error &ex) {
|
||||
std::cerr << "Error parsing arguments: " << ex.what() << std::endl;
|
||||
std::cerr << desc;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const auto fontmap_index = vm["map"].as<unsigned>();
|
||||
const auto width = vm["width"].as<unsigned>();
|
||||
const auto height = vm["height"].as<unsigned>();
|
||||
const auto fontsize = vm["fontsize"].as<unsigned>();
|
||||
|
||||
float aspect;
|
||||
if (vm.count("aspect")) {
|
||||
aspect = vm["aspect"].as<float>();
|
||||
} else {
|
||||
if (fontmap_index == 0) {
|
||||
aspect = 1.0f;
|
||||
} else {
|
||||
aspect = 1.25f;
|
||||
}
|
||||
}
|
||||
|
||||
if (fontmap_index >= fontmaps_gta3_font.size()) {
|
||||
std::cerr << "Illegal map: range: [0, " << fontmaps_gta3_font.size() << ")\n";
|
||||
std::cerr << desc;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
FontTextureBuffer texBuffer{fontsize, width, height, aspect};
|
||||
|
||||
for (const auto &fontpath : vm["font"].as<std::vector<std::string>>()) {
|
||||
texBuffer.add_face(fontpath.c_str());
|
||||
}
|
||||
|
||||
texBuffer.create_font_map(fontmaps_gta3_font[fontmap_index], FontTextureBuffer::RenderMode::NORMAL);
|
||||
|
||||
texBuffer.write(vm["texture"].as<rwfs::path>(), vm["advance"].as<rwfs::path>());
|
||||
return 0;
|
||||
}
|
@ -156,10 +156,6 @@ void TextViewer::onGameStringChange(const GameString &gameString) {
|
||||
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();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user