Add new non-monospace font

This commit is contained in:
Nikita Lisitsa 2022-02-15 16:24:42 +03:00
parent 68e8ece70a
commit 34e2877873
7 changed files with 295 additions and 7 deletions

View file

@ -5,6 +5,4 @@ psemek_add_library(psemek-ui ${PSEMEK_UI_HEADERS} ${PSEMEK_UI_SOURCES})
target_include_directories(psemek-ui PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(psemek-ui PUBLIC psemek-util psemek-log psemek-geom psemek-cg psemek-gfx psemek-async psemek-sdl2)
psemek_add_resources(psemek-ui
resources/cross_red_16x16.png psemek/ui/resources/cross_red_16x16_png
)
psemek_glob_resources(psemek-ui resources psemek/ui/resources)

View file

@ -2,6 +2,7 @@
#include <psemek/geom/vector.hpp>
#include <psemek/geom/box.hpp>
#include <psemek/util/span.hpp>
#include <psemek/gfx/texture.hpp>
@ -54,7 +55,7 @@ namespace psemek::ui
virtual geom::vector<int, 2> size() const = 0;
virtual bool supports_character(char32_t c) const = 0;
virtual std::vector<character_range> supported_characters() const = 0;
virtual util::span<character_range const> supported_characters() const = 0;
virtual std::vector<glyph> shape(std::string_view str, shape_options const & options) const = 0;
@ -65,6 +66,7 @@ namespace psemek::ui
virtual ~font() {}
};
std::unique_ptr<font> make_default_monospace_9x12_font();
std::unique_ptr<font> make_default_9x12_font();
}

View file

@ -0,0 +1,49 @@
#pragma once
#include <psemek/ui/font.hpp>
namespace psemek::ui
{
struct kerned_font
: font
{
struct glyph_data
{
int start_x;
int start_y;
int size_x;
int size_y;
int offset_x;
int offset_y;
int advance;
};
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);
font_type type() const override { return font_type::bitmap; }
std::string_view name() const override { return name_; }
geom::vector<int, 2> size() const override { return size_; }
bool supports_character(char32_t c) const override;
util::span<character_range const> supported_characters() const override { return ranges_; }
std::vector<glyph> shape(std::string_view str, shape_options const & options) const override;
gfx::texture_2d const & atlas() const override { return atlas_; };
std::optional<geom::box<float, 2>> texcoords(char32_t character) const override;
private:
std::vector<character_range> ranges_;
std::string_view name_;
geom::vector<int, 2> size_;
int baseline_offset_;
gfx::texture_2d atlas_;
std::unordered_map<char32_t, glyph_data> glyphs_;
};
}

View file

@ -17,7 +17,7 @@ namespace psemek::ui
geom::vector<int, 2> size() const override { return size_; }
bool supports_character(char32_t c) const override;
std::vector<character_range> supported_characters() const override { return {range_}; }
util::span<character_range const> supported_characters() const override { return {&range_, &range_ + 1}; }
std::vector<glyph> shape(std::string_view str, shape_options const & options) const override;

View file

@ -0,0 +1,96 @@
# <character-id> <texcoord-start-x> <texcoord-start-y> <size-x> <size-y> <offset-x> <offset-y> <advance>
32 0 0 0 0 0 0 6 # ' '
33 16 3 1 8 0 0 2 # !
34 25 3 5 3 0 5 6 # "
35 36 3 5 9 0 -1 6 # #
36 47 3 4 9 0 -1 5 # $
37 58 3 5 8 0 0 6 # %
38 69 5 5 6 0 0 6 # &
39 82 3 1 4 0 4 2 # '
40 93 2 2 10 0 -1 3 # (
41 103 2 2 10 0 -1 3 # )
42 113 3 5 5 0 3 6 # *
43 123 4 7 7 0 0 8 # +
44 136 9 3 4 0 -2 4 # ,
45 146 7 5 1 0 3 6 # -
46 158 9 2 2 0 0 3 # .
47 168 3 5 9 0 -1 6 # /
48 3 17 5 8 0 0 6 # 0
49 14 17 5 8 0 0 6 # 1
50 25 17 5 8 0 0 6 # 2
51 36 17 5 8 0 0 6 # 3
52 47 17 6 8 0 0 7 # 4
53 58 17 5 8 0 0 6 # 5
54 69 17 5 8 0 0 6 # 6
55 80 17 5 8 0 0 6 # 7
56 91 17 5 8 0 0 6 # 8
57 102 17 5 8 0 0 6 # 9
58 114 19 2 6 0 0 3 # :
59 125 19 3 7 0 -1 4 # ;
60 134 18 6 7 0 0 7 # <
61 146 20 5 3 0 2 6 # =
62 156 18 6 7 0 0 7 # >
63 169 18 4 7 0 0 5 # ?
64 3 30 5 10 0 -1 6 # @
65 13 31 7 8 0 0 8 # A
66 24 31 6 8 0 0 7 # B
67 36 31 5 8 0 0 6 # C
68 46 31 6 8 0 0 7 # D
69 57 31 6 8 0 0 7 # E
70 68 31 6 8 0 0 7 # F
71 80 31 6 8 0 0 7 # G
72 90 31 7 8 0 0 8 # H
73 102 31 5 8 0 0 6 # I
74 113 31 5 8 0 0 6 # J
75 123 31 7 8 0 0 8 # K
76 135 31 5 8 0 0 6 # L
77 145 31 7 8 0 0 8 # M
78 156 31 7 8 0 0 8 # N
79 168 31 5 8 0 0 6 # O
80 3 45 5 8 0 0 6 # P
81 14 45 5 9 0 -1 6 # Q
82 24 45 7 8 0 0 8 # R
83 36 45 5 8 0 0 6 # S
84 46 45 7 8 0 0 8 # T
85 57 45 7 8 0 0 8 # U
86 68 45 7 8 0 0 8 # V
87 78 45 7 8 0 0 8 # W
88 90 45 7 8 0 0 8 # X
89 101 45 7 8 0 0 8 # Y
90 113 45 5 8 0 0 6 # Z
91 125 44 3 10 0 -1 4 # [
92 135 45 4 9 0 -1 5 # \
93 147 44 3 10 0 -1 4 # ]
94 157 45 5 4 0 4 6 # ^
95 167 54 7 1 0 -2 8 # _
96 5 58 2 2 0 7 3 # `
97 14 61 6 6 0 0 7 # a
98 24 59 6 8 0 0 7 # b
99 36 61 5 6 0 0 6 # c
100 47 59 6 8 0 0 7 # d
101 58 61 5 6 0 0 6 # e
102 69 59 5 8 0 0 6 # f
103 80 61 6 8 0 -2 7 # g
104 90 59 7 8 0 0 8 # h
105 102 59 5 8 0 0 6 # i
106 113 59 4 10 0 -2 5 # j
107 123 59 6 8 0 0 7 # k
108 135 59 5 8 0 0 6 # l
109 145 61 7 6 0 0 8 # m
110 156 61 7 6 0 0 8 # n
111 168 61 5 6 0 0 6 # o
112 2 75 6 8 0 -2 7 # p
113 14 75 6 8 0 -2 7 # q
114 25 75 5 6 0 0 6 # r
115 36 75 5 6 0 0 6 # s
116 46 74 6 7 0 0 7 # t
117 57 75 7 6 0 0 8 # u
118 68 75 7 6 0 0 8 # v
119 79 75 7 6 0 0 8 # w
120 91 75 6 6 0 0 7 # x
121 101 75 7 8 0 -2 8 # y
122 113 75 5 6 0 0 6 # z
123 125 72 3 10 0 -1 4 # {
124 137 72 1 9 0 0 2 # |
125 147 72 3 10 0 -1 4 # }
126 157 73 5 2 0 6 6 # ~

View file

@ -1,17 +1,21 @@
#include <psemek/ui/font.hpp>
#include <psemek/ui/monospace_font.hpp>
#include <psemek/ui/kerned_font.hpp>
#include <psemek/gfx/resource/font_9x12_png.hpp>
#include <psemek/ui/resources/font_9x12_glyphs_txt.hpp>
#include <psemek/io/memory_stream.hpp>
#include <sstream>
namespace psemek::ui
{
std::unique_ptr<font> make_default_9x12_font()
std::unique_ptr<font> make_default_monospace_9x12_font()
{
character_range range{32, 128};
std::string_view name = "default_9x12";
std::string_view name = "default_monospace_9x12";
geom::vector<int, 2> size{9, 12};
gfx::texture_2d atlas = gfx::texture_2d::from_pixmap(gfx::read_png_monochrome(io::memory_istream{gfx::resource::font_9x12_png}));
@ -40,4 +44,41 @@ namespace psemek::ui
return std::make_unique<monospace_font>(range, name, size, std::move(atlas), std::move(texcoords));
}
std::unique_ptr<font> make_default_9x12_font()
{
std::string_view name = "default_9x12";
geom::vector<int, 2> size{9, 12};
gfx::texture_2d atlas = gfx::texture_2d::from_pixmap(gfx::read_png_monochrome(io::memory_istream{gfx::resource::font_9x12_png}));
atlas.nearest_filter();
atlas.clamp();
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_G, gl::RED);
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED);
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);
std::unordered_map<char32_t, kerned_font::glyph_data> glyphs;
{
std::istringstream is{std::string(ui::resources::font_9x12_glyphs_txt)};
std::string line;
while (std::getline(is, line))
{
if (auto pos = line.find('#'); pos != std::string::npos)
line = line.substr(0, pos);
if (line.empty()) continue;
std::istringstream is{std::move(line)};
int c;
kerned_font::glyph_data data;
is >> c >> data.start_x >> data.start_y >> data.size_x >> data.size_y >> data.offset_x >> data.offset_y >> data.advance;
glyphs[c] = data;
}
}
return std::make_unique<kerned_font>(name, size, 2, std::move(atlas), std::move(glyphs));
}
}

View file

@ -0,0 +1,102 @@
#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;
auto const & data = glyphs_.at(c);
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;
}
}