1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-09-15 06:52:34 +02:00

Merge pull request #571 from madebr/kerning

Add font-dependent kerning
This commit is contained in:
Daniel Evans 2018-08-07 23:06:12 +01:00 committed by GitHub
commit 31f32b2173
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 459 additions and 79 deletions

View File

@ -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

View File

@ -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")

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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
View File

@ -0,0 +1 @@
add_subdirectory(rwfont)

View 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
)

View 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;
}

View File

@ -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();
}