Add rapidjson, msdf & bitmap fonts support and bmfont parser
This commit is contained in:
parent
87c612a0eb
commit
b00e59ab08
18 changed files with 418 additions and 42 deletions
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "3rdparty/rapidjson"]
|
||||||
|
path = 3rdparty/rapidjson
|
||||||
|
url = https://github.com/Tencent/rapidjson.git
|
||||||
3
3rdparty/CMakeLists.txt
vendored
Normal file
3
3rdparty/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
file(GLOB_RECURSE RAPIDJSON_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/rapidjson/include/*")
|
||||||
|
add_library(rapidjson INTERFACE EXCLUDE_FROM_ALL "${RAPIDJSON_SOURCES}")
|
||||||
|
target_include_directories(rapidjson INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/rapidjson/include")
|
||||||
1
3rdparty/rapidjson
vendored
Submodule
1
3rdparty/rapidjson
vendored
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 80b6d1c83402a5785c486603c5611923159d0894
|
||||||
|
|
@ -41,6 +41,8 @@ if(PSEMEK_UBSAN)
|
||||||
list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined)
|
list(APPEND CMAKE_CXX_FLAGS -fsanitize=undefined)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
add_subdirectory(3rdparty)
|
||||||
|
|
||||||
get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY)
|
get_directory_property(PSEMEK_PARENT_DIRECTORY PARENT_DIRECTORY)
|
||||||
|
|
||||||
add_subdirectory(package)
|
add_subdirectory(package)
|
||||||
|
|
|
||||||
27
libs/ui/bmfont-converter.py
Executable file
27
libs/ui/bmfont-converter.py
Executable file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
|
result = dict()
|
||||||
|
result["info"] = dict()
|
||||||
|
result["common"] = dict()
|
||||||
|
result["chars"] = list()
|
||||||
|
|
||||||
|
with open(sys.argv[1], "rt") as in_file:
|
||||||
|
for line in in_file:
|
||||||
|
#print("Line:", line)
|
||||||
|
parts = line.strip("\n").split(" ")
|
||||||
|
#print("Parts:", parts)
|
||||||
|
key = parts[0]
|
||||||
|
obj = {p.split("=")[0]: p.split("=")[1] for p in parts[1:] if len(p) > 0}
|
||||||
|
if key == "info":
|
||||||
|
result[key]["face"] = obj["face"].strip("\"")
|
||||||
|
result[key]["size"] = abs(int(obj["size"]))
|
||||||
|
if key == "common":
|
||||||
|
result[key]["base"] = int(obj["base"])
|
||||||
|
if key == "char":
|
||||||
|
char = {k: int(v) for k, v in obj.items()}
|
||||||
|
result["chars"].append(char)
|
||||||
|
|
||||||
|
print(json.dumps(result))
|
||||||
34
libs/ui/include/psemek/ui/bmfont.hpp
Normal file
34
libs/ui/include/psemek/ui/bmfont.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/geom/vector.hpp>
|
||||||
|
#include <psemek/io/stream.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct bmfont_data
|
||||||
|
{
|
||||||
|
struct glyph_data
|
||||||
|
{
|
||||||
|
int start_x;
|
||||||
|
int start_y;
|
||||||
|
int size_x;
|
||||||
|
int size_y;
|
||||||
|
int offset_x;
|
||||||
|
int offset_y;
|
||||||
|
int advance;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string name;
|
||||||
|
geom::vector<int, 2> size;
|
||||||
|
int baseline;
|
||||||
|
std::unordered_map<char32_t, glyph_data> glyphs;
|
||||||
|
float sdf_scale = 0.f;
|
||||||
|
|
||||||
|
static bmfont_data parse(io::istream && stream);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <psemek/util/span.hpp>
|
#include <psemek/util/span.hpp>
|
||||||
|
|
||||||
#include <psemek/gfx/texture.hpp>
|
#include <psemek/gfx/texture.hpp>
|
||||||
|
#include <psemek/io/stream.hpp>
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -16,7 +17,7 @@ namespace psemek::ui
|
||||||
enum class font_type
|
enum class font_type
|
||||||
{
|
{
|
||||||
bitmap,
|
bitmap,
|
||||||
sdf,
|
msdf,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct character_range
|
struct character_range
|
||||||
|
|
@ -74,6 +75,8 @@ namespace psemek::ui
|
||||||
|
|
||||||
virtual gfx::texture_2d const & atlas() const = 0;
|
virtual gfx::texture_2d const & atlas() const = 0;
|
||||||
|
|
||||||
|
virtual float sdf_scale() const { return 0.f; }
|
||||||
|
|
||||||
virtual std::optional<geom::box<float, 2>> texcoords(char32_t character) const = 0;
|
virtual std::optional<geom::box<float, 2>> texcoords(char32_t character) const = 0;
|
||||||
|
|
||||||
virtual ~font() {}
|
virtual ~font() {}
|
||||||
|
|
@ -83,4 +86,7 @@ namespace psemek::ui
|
||||||
std::unique_ptr<font> make_default_9x12_font();
|
std::unique_ptr<font> make_default_9x12_font();
|
||||||
std::unique_ptr<font> make_default_10x12_bold_font();
|
std::unique_ptr<font> make_default_10x12_bold_font();
|
||||||
|
|
||||||
|
std::unique_ptr<font> make_bitmap_font(io::istream && description, io::istream && texture);
|
||||||
|
std::unique_ptr<font> make_msdf_font(io::istream && description, io::istream && texture);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <psemek/ui/font.hpp>
|
#include <psemek/ui/font.hpp>
|
||||||
|
#include <psemek/ui/bmfont.hpp>
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
@ -10,25 +11,13 @@ namespace psemek::ui
|
||||||
struct kerned_font
|
struct kerned_font
|
||||||
: font
|
: font
|
||||||
{
|
{
|
||||||
struct glyph_data
|
kerned_font(bmfont_data data, gfx::texture_2d atlas);
|
||||||
{
|
|
||||||
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; }
|
font_type type() const override { return font_type::bitmap; }
|
||||||
|
|
||||||
std::string_view name() const override { return name_; }
|
std::string_view name() const override { return data_.name; }
|
||||||
|
|
||||||
geom::vector<int, 2> size() const override { return size_; }
|
geom::vector<int, 2> size() const override { return data_.size; }
|
||||||
|
|
||||||
bool supports_character(char32_t c) const override;
|
bool supports_character(char32_t c) const override;
|
||||||
util::span<character_range const> supported_characters() const override { return ranges_; }
|
util::span<character_range const> supported_characters() const override { return ranges_; }
|
||||||
|
|
@ -40,13 +29,10 @@ namespace psemek::ui
|
||||||
|
|
||||||
std::optional<geom::box<float, 2>> texcoords(char32_t character) const override;
|
std::optional<geom::box<float, 2>> texcoords(char32_t character) const override;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
std::vector<character_range> ranges_;
|
std::vector<character_range> ranges_;
|
||||||
std::string_view name_;
|
bmfont_data data_;
|
||||||
geom::vector<int, 2> size_;
|
|
||||||
int baseline_offset_;
|
|
||||||
gfx::texture_2d atlas_;
|
gfx::texture_2d atlas_;
|
||||||
std::unordered_map<char32_t, glyph_data> glyphs_;
|
|
||||||
|
|
||||||
template <typename String>
|
template <typename String>
|
||||||
std::vector<glyph> shape_impl(String const & str, shape_options const & options, geom::point<float, 2> & pen) const;
|
std::vector<glyph> shape_impl(String const & str, shape_options const & options, geom::point<float, 2> & pen) const;
|
||||||
|
|
|
||||||
|
|
@ -142,6 +142,9 @@ namespace psemek::ui
|
||||||
std::vector<batch> batches;
|
std::vector<batch> batches;
|
||||||
geom::vector<float, 2> size{0.f, 0.f};
|
geom::vector<float, 2> size{0.f, 0.f};
|
||||||
|
|
||||||
|
ui::font_type font_type = ui::font_type::bitmap;
|
||||||
|
float sdf_scale = 0.f;
|
||||||
|
|
||||||
std::vector<std::pair<geom::box<float, 2>, std::string>> link_bboxes;
|
std::vector<std::pair<geom::box<float, 2>, std::string>> link_bboxes;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
21
libs/ui/include/psemek/ui/msdf_font.hpp
Normal file
21
libs/ui/include/psemek/ui/msdf_font.hpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <psemek/ui/font.hpp>
|
||||||
|
#include <psemek/ui/kerned_font.hpp>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
struct msdf_font
|
||||||
|
: kerned_font
|
||||||
|
{
|
||||||
|
msdf_font(bmfont_data data, gfx::texture_2d atlas);
|
||||||
|
|
||||||
|
font_type type() const override { return font_type::msdf; }
|
||||||
|
|
||||||
|
float sdf_scale() const override { return data_.sdf_scale; }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -45,6 +45,7 @@ namespace psemek::ui
|
||||||
using image_options = detail::image_options;
|
using image_options = detail::image_options;
|
||||||
|
|
||||||
virtual void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts = image_options{}) = 0;
|
virtual void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts = image_options{}) = 0;
|
||||||
|
virtual void draw_msdf_glyph(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & opts = image_options{}) = 0;
|
||||||
|
|
||||||
virtual void begin_stencil() = 0;
|
virtual void begin_stencil() = 0;
|
||||||
virtual void commit_stencil() = 0;
|
virtual void commit_stencil() = 0;
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ namespace psemek::ui
|
||||||
void draw_triangle(geom::triangle<geom::point<float, 2>> const & tri, gfx::color_rgba const & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) override;
|
void draw_triangle(geom::triangle<geom::point<float, 2>> const & tri, gfx::color_rgba const & c0, gfx::color_rgba const & c1, gfx::color_rgba const & c2) override;
|
||||||
|
|
||||||
void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts) override;
|
void draw_image(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, image_options const & opts) override;
|
||||||
|
void draw_msdf_glyph(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & opts) override;
|
||||||
|
|
||||||
void begin_stencil() override;
|
void begin_stencil() override;
|
||||||
void commit_stencil() override;
|
void commit_stencil() override;
|
||||||
|
|
|
||||||
101
libs/ui/source/bmfont.cpp
Normal file
101
libs/ui/source/bmfont.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#include <psemek/ui/bmfont.hpp>
|
||||||
|
#include <psemek/util/to_string.hpp>
|
||||||
|
|
||||||
|
#include <boost/preprocessor/stringize.hpp>
|
||||||
|
|
||||||
|
#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing font description: " BOOST_PP_STRINGIZE(x));
|
||||||
|
|
||||||
|
#include <rapidjson/document.h>
|
||||||
|
#include <rapidjson/istreamwrapper.h>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
std::string_view to_string(rapidjson::ParseErrorCode const & code)
|
||||||
|
{
|
||||||
|
using namespace rapidjson;
|
||||||
|
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case kParseErrorNone: return "no error";
|
||||||
|
case kParseErrorDocumentEmpty: return "document is empty";
|
||||||
|
case kParseErrorDocumentRootNotSingular: return "document root must not be followed by other values";
|
||||||
|
case kParseErrorValueInvalid: return "invalid value";
|
||||||
|
case kParseErrorObjectMissName: return "missing a name for object member";
|
||||||
|
case kParseErrorObjectMissColon: return "missing a colon after a name of object member";
|
||||||
|
case kParseErrorObjectMissCommaOrCurlyBracket: return "missing a comma or '}' after an object member";
|
||||||
|
case kParseErrorArrayMissCommaOrSquareBracket: return "missing a comma or ']' after an array element";
|
||||||
|
case kParseErrorStringUnicodeEscapeInvalidHex: return R"(Incorrect hex digit after \\u escape in string)";
|
||||||
|
case kParseErrorStringUnicodeSurrogateInvalid: return "surrogate pair in string is invalid";
|
||||||
|
case kParseErrorStringEscapeInvalid: return "invalid escape character in string";
|
||||||
|
case kParseErrorStringMissQuotationMark: return "missing a closing quotation mark in string";
|
||||||
|
case kParseErrorStringInvalidEncoding: return "invalid encoding in string";
|
||||||
|
case kParseErrorNumberTooBig: return "number too big to be stored in double";
|
||||||
|
case kParseErrorNumberMissFraction: return "missing fraction part in number";
|
||||||
|
case kParseErrorNumberMissExponent: return "missing exponent in number";
|
||||||
|
case kParseErrorTermination: return "parsing was terminated";
|
||||||
|
case kParseErrorUnspecificSyntaxError: return "unspecific syntax error";
|
||||||
|
default: return "(unknown error)";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bmfont_data bmfont_data::parse(io::istream && stream)
|
||||||
|
{
|
||||||
|
auto description_str = io::read_full(std::move(stream));
|
||||||
|
description_str.push_back(0);
|
||||||
|
|
||||||
|
rapidjson::Document document;
|
||||||
|
document.ParseInsitu(description_str.data());
|
||||||
|
|
||||||
|
if (document.HasParseError())
|
||||||
|
throw std::runtime_error(util::to_string("Error msdf font description: ", to_string(document.GetParseError()), " at ", document.GetErrorOffset()));
|
||||||
|
|
||||||
|
bmfont_data result;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const & info = document["info"];
|
||||||
|
result.name = info["face"].GetString();
|
||||||
|
int size = info["size"].GetInt();
|
||||||
|
result.size = {size, size};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.HasMember("distanceField"))
|
||||||
|
{
|
||||||
|
auto const & sdf = document["distanceField"];
|
||||||
|
result.sdf_scale = sdf["distanceRange"].GetFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto const & sdf = document["common"];
|
||||||
|
result.baseline = sdf["base"].GetInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto chars = document["chars"].GetArray();
|
||||||
|
|
||||||
|
for (auto const & charInfo : chars)
|
||||||
|
{
|
||||||
|
char32_t id = charInfo["id"].GetUint();
|
||||||
|
|
||||||
|
auto & data = result.glyphs[id];
|
||||||
|
data.start_x = charInfo["x"].GetInt();
|
||||||
|
data.start_y = charInfo["y"].GetInt();
|
||||||
|
data.size_x = charInfo["width"].GetInt();
|
||||||
|
data.size_y = charInfo["height"].GetInt();
|
||||||
|
data.offset_x = charInfo["xoffset"].GetInt();
|
||||||
|
data.offset_y = charInfo["yoffset"].GetInt();
|
||||||
|
data.advance = charInfo["xadvance"].GetInt();
|
||||||
|
|
||||||
|
data.offset_y = - data.offset_y - data.size_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -55,7 +55,11 @@ namespace psemek::ui
|
||||||
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED);
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::RED);
|
||||||
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);
|
||||||
|
|
||||||
std::unordered_map<char32_t, kerned_font::glyph_data> glyphs;
|
bmfont_data data;
|
||||||
|
data.name = name;
|
||||||
|
data.size = size;
|
||||||
|
data.baseline = 2;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::istringstream is{std::string(glyphs_resource.data)};
|
std::istringstream is{std::string(glyphs_resource.data)};
|
||||||
|
|
||||||
|
|
@ -70,14 +74,14 @@ namespace psemek::ui
|
||||||
std::istringstream is{std::move(line)};
|
std::istringstream is{std::move(line)};
|
||||||
|
|
||||||
int c;
|
int c;
|
||||||
kerned_font::glyph_data data;
|
is >> c;
|
||||||
|
auto & glyph = data.glyphs[c];
|
||||||
|
|
||||||
is >> c >> data.start_x >> data.start_y >> data.size_x >> data.size_y >> data.offset_x >> data.offset_y >> data.advance;
|
is >> glyph.start_x >> glyph.start_y >> glyph.size_x >> glyph.size_y >> glyph.offset_x >> glyph.offset_y >> glyph.advance;
|
||||||
glyphs[c] = data;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_unique<kerned_font>(name, size, 2, std::move(atlas), std::move(glyphs));
|
return std::make_unique<kerned_font>(std::move(data), std::move(atlas));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<font> make_default_9x12_font()
|
std::unique_ptr<font> make_default_9x12_font()
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,9 @@
|
||||||
namespace psemek::ui
|
namespace psemek::ui
|
||||||
{
|
{
|
||||||
|
|
||||||
kerned_font::kerned_font(std::string_view name, geom::vector<int, 2> size, int baseline_offset, gfx::texture_2d atlas,
|
kerned_font::kerned_font(bmfont_data data, gfx::texture_2d atlas)
|
||||||
std::unordered_map<char32_t, glyph_data> glyphs)
|
: data_(std::move(data))
|
||||||
: name_{name}
|
|
||||||
, size_{size}
|
|
||||||
, baseline_offset_{baseline_offset}
|
|
||||||
, atlas_{std::move(atlas)}
|
, atlas_{std::move(atlas)}
|
||||||
, glyphs_{std::move(glyphs)}
|
|
||||||
{
|
{
|
||||||
if (!supports_character('?'))
|
if (!supports_character('?'))
|
||||||
throw std::runtime_error("Kerned font must support '?' character");
|
throw std::runtime_error("Kerned font must support '?' character");
|
||||||
|
|
@ -19,7 +15,7 @@ namespace psemek::ui
|
||||||
throw std::runtime_error("Kerned font must support ' ' character");
|
throw std::runtime_error("Kerned font must support ' ' character");
|
||||||
|
|
||||||
std::vector<char32_t> chars;
|
std::vector<char32_t> chars;
|
||||||
for (auto const & g : glyphs_)
|
for (auto const & g : data_.glyphs)
|
||||||
chars.push_back(g.first);
|
chars.push_back(g.first);
|
||||||
std::sort(chars.begin(), chars.end());
|
std::sort(chars.begin(), chars.end());
|
||||||
|
|
||||||
|
|
@ -47,7 +43,7 @@ namespace psemek::ui
|
||||||
|
|
||||||
bool kerned_font::supports_character(char32_t c) const
|
bool kerned_font::supports_character(char32_t c) const
|
||||||
{
|
{
|
||||||
return std::isspace(c) || glyphs_.contains(c);
|
return std::isspace(c) || data_.glyphs.contains(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<glyph> kerned_font::shape(std::string_view str, shape_options const & options, geom::point<float, 2> & pen) const
|
std::vector<glyph> kerned_font::shape(std::string_view str, shape_options const & options, geom::point<float, 2> & pen) const
|
||||||
|
|
@ -68,7 +64,7 @@ namespace psemek::ui
|
||||||
|
|
||||||
std::vector<glyph> result;
|
std::vector<glyph> result;
|
||||||
|
|
||||||
float const offset_to_baseline = (size_[1] - baseline_offset_) * options.scale;
|
float const offset_to_baseline = (data_.size[1] - data_.baseline) * options.scale;
|
||||||
for (char32_t c : str)
|
for (char32_t c : str)
|
||||||
{
|
{
|
||||||
if (!supports_character(c))
|
if (!supports_character(c))
|
||||||
|
|
@ -78,7 +74,7 @@ namespace psemek::ui
|
||||||
if (std::isspace(c))
|
if (std::isspace(c))
|
||||||
cc = ' ';
|
cc = ' ';
|
||||||
|
|
||||||
auto const & data = glyphs_.at(cc);
|
auto const & data = data_.glyphs.at(cc);
|
||||||
|
|
||||||
glyph g;
|
glyph g;
|
||||||
g.character = c;
|
g.character = c;
|
||||||
|
|
@ -88,7 +84,7 @@ namespace psemek::ui
|
||||||
g.position[1].max = pen[1] + offset_to_baseline - data.offset_y * options.scale;
|
g.position[1].max = pen[1] + offset_to_baseline - data.offset_y * options.scale;
|
||||||
result.push_back(g);
|
result.push_back(g);
|
||||||
|
|
||||||
geom::vector<float, 2> advance{data.advance * options.scale, size_[1] * options.scale};
|
geom::vector<float, 2> advance{data.advance * options.scale, data_.size[1] * options.scale};
|
||||||
|
|
||||||
pen += geom::pointwise_mult(advance_mask, advance);
|
pen += geom::pointwise_mult(advance_mask, advance);
|
||||||
}
|
}
|
||||||
|
|
@ -101,8 +97,8 @@ namespace psemek::ui
|
||||||
if (std::isspace(c))
|
if (std::isspace(c))
|
||||||
c = ' ';
|
c = ' ';
|
||||||
|
|
||||||
auto it = glyphs_.find(c);
|
auto it = data_.glyphs.find(c);
|
||||||
if (it == glyphs_.end())
|
if (it == data_.glyphs.end())
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
geom::box<float, 2> box;
|
geom::box<float, 2> box;
|
||||||
|
|
@ -114,4 +110,21 @@ namespace psemek::ui
|
||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<font> make_bitmap_font(io::istream && description, io::istream && texture)
|
||||||
|
{
|
||||||
|
auto data = bmfont_data::parse(std::move(description));
|
||||||
|
|
||||||
|
auto pixmap = gfx::read_png_monochrome(std::move(texture));
|
||||||
|
gfx::texture_2d atlas;
|
||||||
|
atlas.load(pixmap);
|
||||||
|
atlas.linear_filter();
|
||||||
|
atlas.clamp();
|
||||||
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_R, gl::ONE);
|
||||||
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_G, gl::ONE);
|
||||||
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_B, gl::ONE);
|
||||||
|
gl::TexParameteri(atlas.target, gl::TEXTURE_SWIZZLE_A, gl::RED);
|
||||||
|
|
||||||
|
return std::make_unique<kerned_font>(std::move(data), std::move(atlas));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -308,13 +308,25 @@ namespace psemek::ui
|
||||||
auto color = *st->shadow_color;
|
auto color = *st->shadow_color;
|
||||||
color[3] = (color[3] * 1.f * batch.color[3]) / 255.f;
|
color[3] = (color[3] * 1.f * batch.color[3]) / 255.f;
|
||||||
for (auto const & image : batch.images)
|
for (auto const & image : batch.images)
|
||||||
p.draw_image(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, {color, painter::color_mode::multiply});
|
{
|
||||||
|
if (cached_state_->font_type == font_type::bitmap)
|
||||||
|
p.draw_image(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, {color, painter::color_mode::multiply});
|
||||||
|
else if (cached_state_->font_type == font_type::msdf)
|
||||||
|
p.draw_msdf_glyph(image.position + offset, gfx::texture_view_2d{batch.texture, image.texcoords}, cached_state_->sdf_scale, {color});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto const & batch : cached_state_->batches)
|
for (auto const & batch : cached_state_->batches)
|
||||||
|
{
|
||||||
for (auto const & image : batch.images)
|
for (auto const & image : batch.images)
|
||||||
p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, batch.text ? painter::color_mode::multiply : painter::color_mode::mix});
|
{
|
||||||
|
if (cached_state_->font_type == font_type::bitmap)
|
||||||
|
p.draw_image(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, {batch.color, batch.text ? painter::color_mode::multiply : painter::color_mode::mix});
|
||||||
|
else if (cached_state_->font_type == font_type::msdf)
|
||||||
|
p.draw_msdf_glyph(image.position, gfx::texture_view_2d{batch.texture, image.texcoords}, cached_state_->sdf_scale, {batch.color});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void label::on_state_changed()
|
void label::on_state_changed()
|
||||||
|
|
@ -342,6 +354,9 @@ namespace psemek::ui
|
||||||
|
|
||||||
auto st = merged_own_style();
|
auto st = merged_own_style();
|
||||||
|
|
||||||
|
state.font_type = st->font->type();
|
||||||
|
state.sdf_scale = st->font->sdf_scale();
|
||||||
|
|
||||||
auto const font_height = st->font->size()[1] * (*st->text_scale);
|
auto const font_height = st->font->size()[1] * (*st->text_scale);
|
||||||
|
|
||||||
// Shape items (glyphs/images) chunk by chunk
|
// Shape items (glyphs/images) chunk by chunk
|
||||||
|
|
|
||||||
25
libs/ui/source/msdf_font.cpp
Normal file
25
libs/ui/source/msdf_font.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#include <psemek/ui/msdf_font.hpp>
|
||||||
|
#include <psemek/gfx/pixmap.hpp>
|
||||||
|
|
||||||
|
namespace psemek::ui
|
||||||
|
{
|
||||||
|
|
||||||
|
std::unique_ptr<font> make_msdf_font(io::istream && description, io::istream && texture)
|
||||||
|
{
|
||||||
|
auto data = bmfont_data::parse(std::move(description));
|
||||||
|
|
||||||
|
auto pixmap = gfx::read_png(std::move(texture));
|
||||||
|
gfx::texture_2d atlas;
|
||||||
|
atlas.load(pixmap);
|
||||||
|
atlas.linear_mipmap_filter();
|
||||||
|
atlas.anisotropy();
|
||||||
|
atlas.generate_mipmap();
|
||||||
|
|
||||||
|
return std::make_unique<msdf_font>(std::move(data), std::move(atlas));
|
||||||
|
}
|
||||||
|
|
||||||
|
msdf_font::msdf_font(bmfont_data data, gfx::texture_2d atlas)
|
||||||
|
: kerned_font(std::move(data), std::move(atlas))
|
||||||
|
{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -103,6 +103,58 @@ void main()
|
||||||
if (tex_color.a == 0.0) discard;
|
if (tex_color.a == 0.0) discard;
|
||||||
out_color = tex_color * color;
|
out_color = tex_color * color;
|
||||||
}
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
char const msdf_vs[] =
|
||||||
|
R"(#version 330
|
||||||
|
|
||||||
|
uniform mat4 u_transform;
|
||||||
|
|
||||||
|
uniform vec2 u_texture_size;
|
||||||
|
|
||||||
|
layout (location = 0) in vec2 in_position;
|
||||||
|
layout (location = 1) in float in_depth;
|
||||||
|
layout (location = 2) in vec4 in_color;
|
||||||
|
layout (location = 3) in vec2 in_texcoord;
|
||||||
|
|
||||||
|
out vec4 color;
|
||||||
|
out vec2 texcoord;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = u_transform * vec4(in_position, 1.0 - float(in_depth) / 16777216.0 * 2.0, 1.0);
|
||||||
|
color = in_color;
|
||||||
|
texcoord = in_texcoord / u_texture_size;
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
|
||||||
|
char const msdf_fs[] =
|
||||||
|
R"(#version 330
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_sdf_scale;
|
||||||
|
|
||||||
|
in vec2 texcoord;
|
||||||
|
in vec4 color;
|
||||||
|
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
float median(vec3 v) {
|
||||||
|
return max(min(v.r, v.g), min(max(v.r, v.g), v.b));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
float sdf_value = u_sdf_scale * (median(texture(u_texture, texcoord).rgb) - 0.5);
|
||||||
|
|
||||||
|
float aa_step = length(vec2(dFdx(sdf_value), dFdy(sdf_value))) * sqrt(2.0);
|
||||||
|
|
||||||
|
float alpha = smoothstep(-aa_step * 0.5, aa_step * 0.5, sdf_value);
|
||||||
|
|
||||||
|
if (alpha == 0.0) discard;
|
||||||
|
|
||||||
|
out_color = vec4(color.rgb, color.a * alpha);
|
||||||
|
}
|
||||||
)";
|
)";
|
||||||
|
|
||||||
struct colored_vertex
|
struct colored_vertex
|
||||||
|
|
@ -146,6 +198,29 @@ void main()
|
||||||
return b1.texture == b2.texture && b1.mode == b2.mode;
|
return b1.texture == b2.texture && b1.mode == b2.mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct msdf_vertex
|
||||||
|
{
|
||||||
|
geom::point<float, 2> position;
|
||||||
|
std::uint32_t depth;
|
||||||
|
gfx::color_rgba color;
|
||||||
|
geom::point<float, 2> texcoords;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(textured_vertex) == 24);
|
||||||
|
|
||||||
|
struct msdf_batch
|
||||||
|
{
|
||||||
|
gfx::texture_2d const * texture = nullptr;
|
||||||
|
painter::color_mode mode = painter::color_mode::mix;
|
||||||
|
float sdf_scale{0.f};
|
||||||
|
std::vector<textured_vertex> vertices{};
|
||||||
|
std::vector<std::uint32_t> indices{};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool operator == (msdf_batch const & b1, msdf_batch const & b2)
|
||||||
|
{
|
||||||
|
return b1.texture == b2.texture && b1.mode == b2.mode && b1.sdf_scale == b2.sdf_scale;
|
||||||
|
}
|
||||||
|
|
||||||
struct stencil_batch
|
struct stencil_batch
|
||||||
{
|
{
|
||||||
std::optional<bool> enable_stencil;
|
std::optional<bool> enable_stencil;
|
||||||
|
|
@ -176,13 +251,16 @@ void main()
|
||||||
gfx::program textured_multiply_program;
|
gfx::program textured_multiply_program;
|
||||||
gfx::mesh textured_mesh;
|
gfx::mesh textured_mesh;
|
||||||
|
|
||||||
|
gfx::program msdf_program;
|
||||||
|
gfx::mesh msdf_mesh;
|
||||||
|
|
||||||
int stencil_level = 0;
|
int stencil_level = 0;
|
||||||
static constexpr int max_stencil_level = 255;
|
static constexpr int max_stencil_level = 255;
|
||||||
std::vector<geom::box<float, 2>> stencil_bbox;
|
std::vector<geom::box<float, 2>> stencil_bbox;
|
||||||
|
|
||||||
geom::box<float, 2> draw_bbox;
|
geom::box<float, 2> draw_bbox;
|
||||||
|
|
||||||
std::vector<std::variant<colored_batch, textured_batch, stencil_batch>> batches;
|
std::vector<std::variant<colored_batch, textured_batch, msdf_batch, stencil_batch>> batches;
|
||||||
|
|
||||||
std::size_t max_batch_count = 0;
|
std::size_t max_batch_count = 0;
|
||||||
std::size_t max_primitive_count = 0;
|
std::size_t max_primitive_count = 0;
|
||||||
|
|
@ -207,6 +285,7 @@ void main()
|
||||||
: colored_program{colored_vs, colored_fs}
|
: colored_program{colored_vs, colored_fs}
|
||||||
, textured_mix_program{textured_vs, textured_mix_fs}
|
, textured_mix_program{textured_vs, textured_mix_fs}
|
||||||
, textured_multiply_program{textured_vs, textured_multiply_fs}
|
, textured_multiply_program{textured_vs, textured_multiply_fs}
|
||||||
|
, msdf_program{msdf_vs, msdf_fs}
|
||||||
{
|
{
|
||||||
textured_mix_program.bind();
|
textured_mix_program.bind();
|
||||||
textured_mix_program["u_texture"] = 0;
|
textured_mix_program["u_texture"] = 0;
|
||||||
|
|
@ -214,8 +293,12 @@ void main()
|
||||||
textured_multiply_program.bind();
|
textured_multiply_program.bind();
|
||||||
textured_multiply_program["u_texture"] = 0;
|
textured_multiply_program["u_texture"] = 0;
|
||||||
|
|
||||||
|
msdf_program.bind();
|
||||||
|
msdf_program["u_texture"] = 0;
|
||||||
|
|
||||||
colored_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>>();
|
colored_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>>();
|
||||||
textured_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
|
textured_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
|
||||||
|
msdf_mesh.setup<geom::point<float, 2>, std::uint32_t, gfx::normalized<gfx::color_rgba>, geom::vector<float, 2>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
painter_impl::painter_impl()
|
painter_impl::painter_impl()
|
||||||
|
|
@ -295,6 +378,38 @@ void main()
|
||||||
impl().draw_bbox |= rect;
|
impl().draw_bbox |= rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void painter_impl::draw_msdf_glyph(geom::box<float, 2> const & rect, gfx::texture_view_2d const & tex, float sdf_scale, image_options const & options)
|
||||||
|
{
|
||||||
|
auto & batch = impl().batch<msdf_batch>(msdf_batch{tex.texture, options.mode, sdf_scale});
|
||||||
|
|
||||||
|
std::uint32_t const depth = impl().depth++;
|
||||||
|
std::uint32_t const base = batch.vertices.size();
|
||||||
|
|
||||||
|
auto p00 = rect.corner(0.f, 0.f);
|
||||||
|
auto p10 = rect.corner(1.f, 0.f);
|
||||||
|
auto p01 = rect.corner(0.f, 1.f);
|
||||||
|
auto p11 = rect.corner(1.f, 1.f);
|
||||||
|
|
||||||
|
if (options.rotation != 0.f)
|
||||||
|
{
|
||||||
|
auto const c = rect.center();
|
||||||
|
|
||||||
|
p00 = c + geom::rotate(p00 - c, options.rotation);
|
||||||
|
p10 = c + geom::rotate(p10 - c, options.rotation);
|
||||||
|
p01 = c + geom::rotate(p01 - c, options.rotation);
|
||||||
|
p11 = c + geom::rotate(p11 - c, options.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
batch.vertices.push_back({p00, depth, options.color, tex.part.corner(0.f, 0.f)});
|
||||||
|
batch.vertices.push_back({p10, depth, options.color, tex.part.corner(1.f, 0.f)});
|
||||||
|
batch.vertices.push_back({p01, depth, options.color, tex.part.corner(0.f, 1.f)});
|
||||||
|
batch.vertices.push_back({p11, depth, options.color, tex.part.corner(1.f, 1.f)});
|
||||||
|
|
||||||
|
batch.indices.insert(batch.indices.end(), {base + 0, base + 1, base + 2, base + 2, base + 1, base + 3});
|
||||||
|
|
||||||
|
impl().draw_bbox |= rect;
|
||||||
|
}
|
||||||
|
|
||||||
void painter_impl::begin_stencil()
|
void painter_impl::begin_stencil()
|
||||||
{
|
{
|
||||||
if (impl().stencil_level == impl().max_stencil_level)
|
if (impl().stencil_level == impl().max_stencil_level)
|
||||||
|
|
@ -387,6 +502,8 @@ void main()
|
||||||
impl().textured_mix_program["u_transform"] = transform;
|
impl().textured_mix_program["u_transform"] = transform;
|
||||||
impl().textured_multiply_program.bind();
|
impl().textured_multiply_program.bind();
|
||||||
impl().textured_multiply_program["u_transform"] = transform;
|
impl().textured_multiply_program["u_transform"] = transform;
|
||||||
|
impl().msdf_program.bind();
|
||||||
|
impl().msdf_program["u_transform"] = transform;
|
||||||
|
|
||||||
gl::ActiveTexture(gl::TEXTURE0);
|
gl::ActiveTexture(gl::TEXTURE0);
|
||||||
|
|
||||||
|
|
@ -409,6 +526,19 @@ void main()
|
||||||
program.bind();
|
program.bind();
|
||||||
program["u_texture_size"] = geom::cast<float>(b.texture->size());
|
program["u_texture_size"] = geom::cast<float>(b.texture->size());
|
||||||
b.texture->bind();
|
b.texture->bind();
|
||||||
|
impl().msdf_mesh.load(b.vertices, b.indices, gl::TRIANGLES);
|
||||||
|
impl().msdf_mesh.draw();
|
||||||
|
},
|
||||||
|
[&](msdf_batch const & b){
|
||||||
|
if (!b.texture) return;
|
||||||
|
|
||||||
|
primitive_count += b.indices.size() / 3;
|
||||||
|
|
||||||
|
gfx::program & program = impl().msdf_program;
|
||||||
|
program.bind();
|
||||||
|
program["u_texture_size"] = geom::cast<float>(b.texture->size());
|
||||||
|
program["u_sdf_scale"] = b.sdf_scale;
|
||||||
|
b.texture->bind();
|
||||||
impl().textured_mesh.load(b.vertices, b.indices, gl::TRIANGLES);
|
impl().textured_mesh.load(b.vertices, b.indices, gl::TRIANGLES);
|
||||||
impl().textured_mesh.draw();
|
impl().textured_mesh.draw();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue