From 34e28778732c2e214369281d9c2af61e58a95181 Mon Sep 17 00:00:00 2001 From: lisyarus Date: Tue, 15 Feb 2022 16:24:42 +0300 Subject: [PATCH] Add new non-monospace font --- libs/ui/CMakeLists.txt | 4 +- libs/ui/include/psemek/ui/font.hpp | 4 +- libs/ui/include/psemek/ui/kerned_font.hpp | 49 +++++++++ libs/ui/include/psemek/ui/monospace_font.hpp | 2 +- libs/ui/resources/font_9x12_glyphs.txt | 96 +++++++++++++++++ libs/ui/source/default_fonts.cpp | 45 +++++++- libs/ui/source/kerned_font.cpp | 102 +++++++++++++++++++ 7 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 libs/ui/include/psemek/ui/kerned_font.hpp create mode 100644 libs/ui/resources/font_9x12_glyphs.txt create mode 100644 libs/ui/source/kerned_font.cpp diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 2dc35ac3..ca71c3b8 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -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) diff --git a/libs/ui/include/psemek/ui/font.hpp b/libs/ui/include/psemek/ui/font.hpp index 1813e422..85bf4260 100644 --- a/libs/ui/include/psemek/ui/font.hpp +++ b/libs/ui/include/psemek/ui/font.hpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -54,7 +55,7 @@ namespace psemek::ui virtual geom::vector size() const = 0; virtual bool supports_character(char32_t c) const = 0; - virtual std::vector supported_characters() const = 0; + virtual util::span supported_characters() const = 0; virtual std::vector shape(std::string_view str, shape_options const & options) const = 0; @@ -65,6 +66,7 @@ namespace psemek::ui virtual ~font() {} }; + std::unique_ptr make_default_monospace_9x12_font(); std::unique_ptr make_default_9x12_font(); } diff --git a/libs/ui/include/psemek/ui/kerned_font.hpp b/libs/ui/include/psemek/ui/kerned_font.hpp new file mode 100644 index 00000000..cf3d3548 --- /dev/null +++ b/libs/ui/include/psemek/ui/kerned_font.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include + +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 size, int baseline_offset, gfx::texture_2d atlas, + std::unordered_map glyphs); + + font_type type() const override { return font_type::bitmap; } + + std::string_view name() const override { return name_; } + + geom::vector size() const override { return size_; } + + bool supports_character(char32_t c) const override; + util::span supported_characters() const override { return ranges_; } + + std::vector shape(std::string_view str, shape_options const & options) const override; + + gfx::texture_2d const & atlas() const override { return atlas_; }; + + std::optional> texcoords(char32_t character) const override; + + private: + std::vector ranges_; + std::string_view name_; + geom::vector size_; + int baseline_offset_; + gfx::texture_2d atlas_; + std::unordered_map glyphs_; + }; + +} diff --git a/libs/ui/include/psemek/ui/monospace_font.hpp b/libs/ui/include/psemek/ui/monospace_font.hpp index ca251967..2cfe5add 100644 --- a/libs/ui/include/psemek/ui/monospace_font.hpp +++ b/libs/ui/include/psemek/ui/monospace_font.hpp @@ -17,7 +17,7 @@ namespace psemek::ui geom::vector size() const override { return size_; } bool supports_character(char32_t c) const override; - std::vector supported_characters() const override { return {range_}; } + util::span supported_characters() const override { return {&range_, &range_ + 1}; } std::vector shape(std::string_view str, shape_options const & options) const override; diff --git a/libs/ui/resources/font_9x12_glyphs.txt b/libs/ui/resources/font_9x12_glyphs.txt new file mode 100644 index 00000000..ce32123c --- /dev/null +++ b/libs/ui/resources/font_9x12_glyphs.txt @@ -0,0 +1,96 @@ +# +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 # ~ diff --git a/libs/ui/source/default_fonts.cpp b/libs/ui/source/default_fonts.cpp index 77c67099..b779ab28 100644 --- a/libs/ui/source/default_fonts.cpp +++ b/libs/ui/source/default_fonts.cpp @@ -1,17 +1,21 @@ #include #include +#include #include +#include #include +#include + namespace psemek::ui { - std::unique_ptr make_default_9x12_font() + std::unique_ptr 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 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(range, name, size, std::move(atlas), std::move(texcoords)); } + std::unique_ptr make_default_9x12_font() + { + std::string_view name = "default_9x12"; + geom::vector 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 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(name, size, 2, std::move(atlas), std::move(glyphs)); + } + } diff --git a/libs/ui/source/kerned_font.cpp b/libs/ui/source/kerned_font.cpp new file mode 100644 index 00000000..41247016 --- /dev/null +++ b/libs/ui/source/kerned_font.cpp @@ -0,0 +1,102 @@ +#include + +#include + +namespace psemek::ui +{ + + kerned_font::kerned_font(std::string_view name, geom::vector size, int baseline_offset, gfx::texture_2d atlas, + std::unordered_map 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 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 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 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 const advance_mask = advance_dir(options.direction); + + std::vector result; + + geom::vector 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 advance{data.advance * options.scale, size_[1] * options.scale}; + + pos += geom::pointwise_mult(advance_mask, advance); + } + + return result; + } + + std::optional> 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 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; + } + +}