106 lines
2.8 KiB
C++
106 lines
2.8 KiB
C++
#include <psemek/ui/kerned_font.hpp>
|
|
|
|
#include <psemek/util/unicode.hpp>
|
|
|
|
namespace psemek::ui
|
|
{
|
|
|
|
kerned_font::kerned_font(std::string_view name, geom::vector<int, 2> size, int baseline_offset, gfx::texture_2d atlas,
|
|
std::unordered_map<char32_t, glyph_data> glyphs)
|
|
: name_{name}
|
|
, size_{size}
|
|
, baseline_offset_{baseline_offset}
|
|
, atlas_{std::move(atlas)}
|
|
, glyphs_{std::move(glyphs)}
|
|
{
|
|
if (!supports_character('?'))
|
|
throw std::runtime_error("Kerned font must support '?' character");
|
|
if (!supports_character(' '))
|
|
throw std::runtime_error("Kerned font must support ' ' character");
|
|
|
|
std::vector<char32_t> chars;
|
|
for (auto const & g : glyphs_)
|
|
chars.push_back(g.first);
|
|
std::sort(chars.begin(), chars.end());
|
|
|
|
for (auto c : chars)
|
|
{
|
|
if (!ranges_.empty() && ranges_.back().end == c)
|
|
++ranges_.back().end;
|
|
else
|
|
ranges_.push_back({c, c + 1});
|
|
}
|
|
}
|
|
|
|
static geom::vector<float, 2> advance_dir(shape_options::direction_t direction)
|
|
{
|
|
switch (direction)
|
|
{
|
|
case shape_options::left_to_right: return {1.f, 0.f};
|
|
case shape_options::right_to_left: return {-1.f, 0.f};
|
|
case shape_options::top_to_bottom: return {0.f, 1.f};
|
|
case shape_options::bottom_to_top: return {0.f, -1.f};
|
|
}
|
|
|
|
return {1.f, 0.f};
|
|
}
|
|
|
|
bool kerned_font::supports_character(char32_t c) const
|
|
{
|
|
return std::isspace(c) || glyphs_.contains(c);
|
|
}
|
|
|
|
std::vector<glyph> kerned_font::shape(std::string_view str, shape_options const & options) const
|
|
{
|
|
char32_t const unknown = supports_character(options.unknown_character) ? options.unknown_character : '?';
|
|
geom::vector<float, 2> const advance_mask = advance_dir(options.direction);
|
|
|
|
std::vector<glyph> result;
|
|
|
|
geom::vector<float, 2> pos{0.f, (size_[1] - baseline_offset_) * options.scale};
|
|
for (char32_t c : util::utf8_range(str))
|
|
{
|
|
if (!supports_character(c))
|
|
c = unknown;
|
|
|
|
char32_t cc = c;
|
|
if (std::isspace(c))
|
|
cc = ' ';
|
|
|
|
auto const & data = glyphs_.at(cc);
|
|
|
|
glyph g;
|
|
g.character = c;
|
|
g.position[0].min = pos[0] + data.offset_x * options.scale;
|
|
g.position[1].min = pos[1] - (data.offset_y + data.size_y) * options.scale;
|
|
g.position[0].max = pos[0] + (data.offset_x + data.size_x) * options.scale;
|
|
g.position[1].max = pos[1] - data.offset_y * options.scale;
|
|
result.push_back(g);
|
|
|
|
geom::vector<float, 2> advance{data.advance * options.scale, size_[1] * options.scale};
|
|
|
|
pos += geom::pointwise_mult(advance_mask, advance);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::optional<geom::box<float, 2>> kerned_font::texcoords(char32_t c) const
|
|
{
|
|
if (std::isspace(c))
|
|
c = ' ';
|
|
|
|
auto it = glyphs_.find(c);
|
|
if (it == glyphs_.end())
|
|
return std::nullopt;
|
|
|
|
geom::box<float, 2> box;
|
|
box[0].min = it->second.start_x;
|
|
box[1].min = it->second.start_y;
|
|
box[0].max = box[0].min + it->second.size_x;
|
|
box[1].max = box[1].min + it->second.size_y;
|
|
|
|
return box;
|
|
}
|
|
|
|
}
|