Skip to content

Commit

Permalink
💥 [Fonts]: Font rendering changes
Browse files Browse the repository at this point in the history
Fonts were rendering at larger than normal sizes. This change
ensures that all glpyhs are rendered at their correct size. This
was needed to allow sensible generations of bounding boxes for
ASGE:Text instances. To support this a new function has been added
that calculates the distance in the Y axis min-max from the
originally requested baseline. See `boundsY` on GLFontSet.

Note: This commit will potentially effet the size of fonts being
rendered in existing games. However, as this is not a API breaking
change it has been released as a X.Y bumped version.
  • Loading branch information
HuxyUK committed Nov 17, 2021
1 parent c9f1012 commit 18f744f
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 53 deletions.
32 changes: 31 additions & 1 deletion engine/include/Engine/Font.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#pragma once
#include "NonCopyable.hpp"
#include <string>
#include <tuple>
//#include "Align.hpp"
namespace ASGE
{
Expand Down Expand Up @@ -104,6 +105,35 @@ namespace ASGE
*/
[[nodiscard]] int pxHeight(const char* ch, float scale) const;

/**
* @brief Returns the distance from the baseline in the y axis.
*
* Font's use a baseline for positioning the main body of text. This function
* calculates the max distance on the Y axis required to render the text. It
* returns both the deviation in the -Y and deviation in the +Y axis. This can
* be used to create bounding boxes. It will also correctly parse new lines.
*
* @param[in] ch The character used in the calculations.
* @param[in] scale Any scaling to apply.
* @return The number of pixels required in the Y axis to render the string.
*/
[[nodiscard]] std::tuple<float, float> boundsY(const char* ch, float scale) const;

/**
* @brief Returns the distance from the baseline in the y axis.
*
* Font's use a baseline for positioning the main body of text. This function
* calculates the max distance on the Y axis required to render the text. It
* returns both the deviation in the -Y and deviation in the +Y axis. This can
* be used to create bounding boxes. It will also correctly parse new lines.
*
* @param[in] str The string used in the calculations.
* @param[in] scale Any scaling to apply.
* @return The number of pixels required in the Y axis to render the string.
*/
[[nodiscard]] virtual std::tuple<float, float>
boundsY(const std::string& string, float scale) const = 0;

/**
* @brief Returns the distance scaled in x pixels.
*
Expand Down Expand Up @@ -136,6 +166,6 @@ namespace ASGE
public:
const char* font_name = ""; //!< The name of the font loaded.
int font_size = 0; //!< The size of the font imported.
int line_height = 0; //!< The recommended height of each line.
float line_height = 0; //!< The recommended height of each line.
};
} // namespace ASGE
5 changes: 5 additions & 0 deletions engine/src/Engine/Font.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,8 @@ int ASGE::Font::pxHeight(const char* ch, float scale) const
{
return static_cast<int>(pxHeight(std::string(ch), scale));
}

std::tuple<float, float> ASGE::Font::boundsY(const char* ch, float scale) const
{
return boundsY(std::string(ch), scale);
}
14 changes: 6 additions & 8 deletions engine/src/Engine/OpenGL/CGLSpriteRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -194,22 +194,20 @@ void ASGE::CGLSpriteRenderer::createCharQuad(
const ASGE::GLCharRender& character, const ASGE::Colour& colour, ASGE::GPUQuad& quad) const
{
// locate the character and set x,y positions
auto& ch = character.font->getAtlas()->getCharacter(character.ch);
GLfloat xpos = character.x + ch.Bearing.x * powf(character.scale, 2);
GLfloat ypos = character.y - ch.Bearing.y * powf(character.scale, 2);
// GLfloat ypos = character.y - ch.Bearing.y *(ch.Size.y - ch.Bearing.y);
const auto& ch = character.font->getAtlas()->getCharacter(character.ch);
float x_pos = character.x + ch.Bearing.x * character.scale;
float y_pos = character.y - ch.Bearing.y * character.scale;

// generate the matrix
auto& model_matrix = quad.position;
model_matrix = { glm::mat4(1.f) };
model_matrix = { glm::mat4(1.F) };

model_matrix = glm::translate(model_matrix, glm::vec3(xpos, ypos, 0.0f));
model_matrix = glm::translate(model_matrix, glm::vec3(x_pos, y_pos, 0.0F));

GLfloat w = ch.Size.x * character.scale;
GLfloat h = ch.Size.y * character.scale;

model_matrix =
glm::scale(model_matrix, glm::vec3(w * character.scale, h * character.scale, 1.0f));
model_matrix = glm::scale(model_matrix, glm::vec3(w, h,1.0F));

// Calculate texture coords for each character
quad.uv_data[0] = glm::vec4{ (float)ch.UV.x, (float)ch.UV.w, GPUQuad::PADDING }; // v1
Expand Down
6 changes: 3 additions & 3 deletions engine/src/Engine/OpenGL/GLAtlas.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ namespace ASGE
[[nodiscard]] GLuint getTextureID() const noexcept;
[[nodiscard]] const Character& getCharacter(int idx) const;

private:
bool generateTexture();
void setSampleParams();
private:
void generateTexture();
void setSampleParams();
bool calculateFontFace(const FT_Face& face);
void calculateTextureSize(const FT_Face& face);

Expand Down
3 changes: 1 addition & 2 deletions engine/src/Engine/OpenGL/GLAtlasManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,7 @@ int ASGE::GLAtlasManager::createAtlas(FT_Face& face, const char* name, int pt)

// store the line spacing
set.line_height =
static_cast<int>(
(face->size->metrics.ascender - face->size->metrics.descender)) / 64;
static_cast<float>(face->size->metrics.ascender - face->size->metrics.descender) / 64.0F;

FT_Done_Face(face);

Expand Down
73 changes: 61 additions & 12 deletions engine/src/Engine/OpenGL/GLFontSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "GLFontSet.hpp"
#include "GLAtlas.hpp"
#include <vector>

ASGE::GLFontSet::GLFontSet(GLFontSet&& rhs) noexcept : atlas(std::move(rhs.atlas))
{
Expand All @@ -33,7 +34,7 @@ const ASGE::FontTextureAtlas* ASGE::GLFontSet::getAtlas() const noexcept
float ASGE::GLFontSet::pxWide(char ch, float scale) const noexcept
{
const auto* atlas_ch = &atlas->getCharacter(ch);
return atlas_ch->Advance.x * powf(scale, 2);
return atlas_ch->Advance.x * scale;
}

float ASGE::GLFontSet::pxWide(const std::string& string, float scale) const
Expand All @@ -49,8 +50,9 @@ float ASGE::GLFontSet::pxWide(const std::string& string, float scale) const
float length = 0;
float max_width = 0;

auto update_length = [&]() {
length -= float(ch->Advance.x - ch->Size.x) * powf(scale, 2);
auto update_length = [&]()
{
length -= float(ch->Advance.x - ch->Size.x) * scale;
if (length > max_width)
{
max_width = length;
Expand All @@ -67,7 +69,7 @@ float ASGE::GLFontSet::pxWide(const std::string& string, float scale) const
}

ch = &atlas->getCharacter(C);
length += ch->Advance.x * powf(scale, 2);
length += ch->Advance.x * scale;
}

update_length();
Expand All @@ -86,16 +88,20 @@ float ASGE::GLFontSet::pxHeight(const std::string& string, float scale) const
return 0;
}

float height = 0;
std::string::size_type pos = 0;
std::string target = "\n";
do
int height = 0;
std::string delimiter = "\n";
for (const auto& c : string)
{
height += (float)line_height * scale;
pos += target.length();
} while ((pos = string.find(target, pos)) != std::string::npos);
if (std::to_string(c) == delimiter)
{
break;
}

return height;
const auto* ch = &atlas->getCharacter(c);
height = std::max(height, ch->Bearing.y);
}

return static_cast<float>(height) * scale;
}

ASGE::GLFontSet& ASGE::GLFontSet::operator=(ASGE::GLFontSet&& rhs) noexcept
Expand All @@ -106,3 +112,46 @@ ASGE::GLFontSet& ASGE::GLFontSet::operator=(ASGE::GLFontSet&& rhs) noexcept
this->atlas = std::move(rhs.atlas);
return *this;
}

std::tuple<float, float> ASGE::GLFontSet::boundsY(const std::string& string, float scale) const
{
if (string.empty())
{
return std::make_tuple(0, 0);
}

std::tuple<float, float> bounds(0, 0);
auto& [min_y, max_y] = bounds;

std::string delimiter = "\n";
std::string::size_type prev = 0;
std::string::size_type pos = 0;
std::vector<std::string> lines;

while ((pos = string.find(delimiter, prev)) != std::string::npos)
{
lines.emplace_back(string.substr(prev, pos));
prev = pos + delimiter.size();
}

lines.emplace_back(string.substr(prev, pos));

// first line tells us the low y value
for (const auto& c : lines.front())
{
const auto* ch = &atlas->getCharacter(c);
min_y = std::max(min_y, static_cast<float>(ch->Bearing.y));
}

// last line tells us the high y value
for (const auto& c : lines.back())
{
const auto* ch = &atlas->getCharacter(c);
max_y = std::max(max_y, static_cast<float>(ch->Size.y - ch->Bearing.y));
}

float line_count = static_cast<float>(lines.size()) - 1;
max_y = (max_y + (line_count * line_height)) * scale;
min_y *= scale;
return bounds;
}
5 changes: 3 additions & 2 deletions engine/src/Engine/OpenGL/GLFontSet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace ASGE
GLFontSet(GLFontSet&&) noexcept;
GLFontSet(const GLFontSet&) = delete;
GLFontSet& operator=(const GLFontSet&) = delete;
GLFontSet& operator=(GLFontSet&&) noexcept;
GLFontSet& operator =(GLFontSet&&) noexcept;
~GLFontSet() override;

/**
Expand Down Expand Up @@ -60,8 +60,9 @@ namespace ASGE
[[nodiscard]] float pxWide(const std::string& string, float scale) const override;
[[nodiscard]] float pxWide(char ch, float scale) const noexcept;
[[nodiscard]] float pxHeight(const std::string& string, float scale) const override;
[[nodiscard]] std::tuple<float, float> boundsY(const std::string& string, float scale) const override;

private:
std::unique_ptr<FontTextureAtlas> atlas;
};
} // namespace ASGE
} // namespace ASGE
16 changes: 8 additions & 8 deletions engine/src/Engine/OpenGL/GLSprite.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ bool ASGE::GLSprite::loadTexture(const std::string& file)
texture = GLTextureCache::getInstance().createCached(file);
if (texture != nullptr)
{
// sane defaults
dimensions()[0] = texture->getWidth();
dimensions()[1] = texture->getHeight();
// sane defaults
dimensions()[0] = texture->getWidth();
dimensions()[1] = texture->getHeight();

// source rectangle
srcRect()[2] = dimensions()[0];
srcRect()[3] = dimensions()[1];
return true;
}
// source rectangle
srcRect()[2] = dimensions()[0];
srcRect()[3] = dimensions()[1];
return true;
}

return false;
}
Expand Down
42 changes: 25 additions & 17 deletions engine/src/Engine/Text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,38 +172,45 @@ ASGE::TextBounds ASGE::Text::getLocalBounds() const
TextBounds bounds;
if (validFont())
{
auto width = static_cast<float>(font->pxWide(string));
auto height = static_cast<float>(font->pxHeight(string));
auto width = getWidth();
auto [min,max] = font->boundsY(string, scale);

// clang-format off
bounds.v1 = {0, 0};
bounds.v2 = {width,0};
bounds.v3 = {width, height};
bounds.v4 = {0, height};
bounds.v1 = {0, 0 };
bounds.v2 = {width, 0 };
bounds.v3 = {width, min+max};
bounds.v4 = {0, min+max};
// clang-format on
}
return bounds;
}

ASGE::TextBounds ASGE::Text::getWorldBounds() const
{
auto width = getWidth();
auto height = getHeight();

// clang-format off
TextBounds bounds;
bounds.v1 = {this->position.x, this->position.y};
bounds.v2 = {this->position.x + width, this->position.y};
bounds.v3 = {this->position.x + width, this->position.y + height};
bounds.v4 = {this->position.x, this->position.y + height};
// clang-format on
if (validFont())
{
auto width = getWidth();
auto [min, max] = font->boundsY(string, scale);

// clang-format off
bounds.v1 = {this->position.x, this->position.y - min};
bounds.v2 = {this->position.x + width, this->position.y - min};
bounds.v3 = {this->position.x + width, this->position.y + max};
bounds.v4 = {this->position.x, this->position.y + max};
// clang-format on
}
return bounds;
}

ASGE::Text::Text(ASGE::Text&& rhs) noexcept :
string(std::move(rhs.string)), colour(rhs.colour), position(std::move(rhs.position)),
opacity(rhs.opacity), scale(rhs.scale), z_order(rhs.z_order), font(rhs.font)
string(std::move(rhs.string)),
colour(rhs.colour),
position(std::move(rhs.position)),
opacity(rhs.opacity),
scale(rhs.scale),
z_order(rhs.z_order),
font(rhs.font)
{
rhs.font = nullptr;
}
Expand Down Expand Up @@ -241,3 +248,4 @@ float ASGE::Text::getHeight() const noexcept

return 0;
}

0 comments on commit 18f744f

Please sign in to comment.