rewrite bmfont parser, remove sample07

This commit is contained in:
2019-08-17 02:45:10 +07:00
parent 74175287f8
commit d32ad1f895
10 changed files with 508 additions and 3077 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -33,7 +33,6 @@
#include <chrono>
#include <memory>
#include <limits>
#include <sstream>
#include <numeric>
#include <utility>
#include <iterator>

View File

@@ -9,49 +9,31 @@
#include "_utils.hpp"
#include "buffer.hpp"
#include "buffer_view.hpp"
#include "streams.hpp"
namespace e2d
{
class font final {
public:
struct info_data {
str face;
u32 size{0};
};
struct common_data {
u32 line{0};
u32 base{0};
u32 pages{0};
struct font_info {
str atlas_file;
v2u atlas_size;
u32 font_size{0};
u32 line_height{0};
u32 glyph_ascent{0};
};
struct page_data {
u32 id{0};
str file;
struct glyph_info {
v2i offset;
b2u tex_rect;
i32 advance{0};
};
struct char_data {
u32 id{0};
b2hi rect;
v2hi offset;
i16 advance{0};
u16 page{0};
u16 chnl{0};
};
struct kerning_data {
std::pair<u32, u32> chars{0,0};
i32 amount{0};
};
struct data {
info_data info;
common_data common;
flat_set<page_data> pages;
flat_map<u32, char_data> chars;
struct content {
font_info info;
flat_map<u64, i32> kernings;
flat_map<u32, glyph_info> glyphs;
};
public:
font() = default;
@@ -62,60 +44,43 @@ namespace e2d
font(const font& other);
font& operator=(const font& other);
font(data&& data) noexcept;
font(const data& data);
font(content&& content) noexcept;
font(const content& content);
font& assign(font&& other) noexcept;
font& assign(const font& other);
font& assign(data&& data) noexcept;
font& assign(const data& data);
font& assign(content&& content) noexcept;
font& assign(const content& content);
void swap(font& other) noexcept;
void clear() noexcept;
bool empty() const noexcept;
const info_data& info() const noexcept;
const common_data& common() const noexcept;
const flat_set<page_data>& pages() const noexcept;
const flat_map<u32, char_data>& chars() const noexcept;
const font_info& info() const noexcept;
const flat_map<u64, i32>& kernings() const noexcept;
const flat_map<u32, glyph_info>& glyphs() const noexcept;
const page_data* find_page(u32 id) const noexcept;
const char_data* find_char(u32 id) const noexcept;
i32 get_kerning(u32 first, u32 second) const noexcept;
const glyph_info* find_glyph(u32 code_point) const noexcept;
private:
data data_;
content content_;
};
void swap(font& l, font& r) noexcept;
bool operator==(const font& l, const font& r) noexcept;
bool operator!=(const font& l, const font& r) noexcept;
bool operator<(u32 l, const font::page_data& r) noexcept;
bool operator<(const font::page_data& l, u32 r) noexcept;
bool operator<(const font::page_data& l, const font::page_data& r) noexcept;
bool operator<(u32 l, const font::char_data& r) noexcept;
bool operator<(const font::char_data& l, u32 r) noexcept;
bool operator<(const font::char_data& l, const font::char_data& r) noexcept;
bool operator<(const std::pair<u32,u32>& l, const font::kerning_data& r) noexcept;
bool operator<(const font::kerning_data& l, const std::pair<u32,u32>& r) noexcept;
bool operator<(const font::kerning_data& l, const font::kerning_data& r) noexcept;
bool operator==(const font::info_data& l, const font::info_data& r) noexcept;
bool operator==(const font::common_data& l, const font::common_data& r) noexcept;
bool operator==(const font::page_data& l, const font::page_data& r) noexcept;
bool operator==(const font::char_data& l, const font::char_data& r) noexcept;
bool operator==(const font::kerning_data& l, const font::kerning_data& r) noexcept;
bool operator==(const font::font_info& l, const font::font_info& r) noexcept;
bool operator==(const font::glyph_info& l, const font::glyph_info& r) noexcept;
bool operator==(const font::content& l, const font::content& r) noexcept;
}
namespace e2d::fonts
{
bool try_load_font(
font& dst,
const buffer& src) noexcept;
buffer_view src) noexcept;
bool try_load_font(
font& dst,

View File

@@ -36,5 +36,4 @@ add_e2d_sample(02)
add_e2d_sample(03)
add_e2d_sample(04)
add_e2d_sample(05)
add_e2d_sample(07)

View File

@@ -1,78 +0,0 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#include "../common.hpp"
using namespace e2d;
namespace
{
class game_system final : public ecs::system {
public:
void process(ecs::registry& owner) override {
E2D_UNUSED(owner);
const keyboard& k = the<input>().keyboard();
if ( k.is_key_just_released(keyboard_key::f12) ) {
the<dbgui>().toggle_visible(!the<dbgui>().visible());
}
if ( k.is_key_just_released(keyboard_key::escape) ) {
the<window>().set_should_close(true);
}
if ( k.is_key_pressed(keyboard_key::lsuper) && k.is_key_just_released(keyboard_key::enter) ) {
the<window>().toggle_fullscreen(!the<window>().fullscreen());
}
}
};
class camera_system final : public ecs::system {
public:
void process(ecs::registry& owner) override {
owner.for_joined_components<camera>(
[](const ecs::const_entity&, camera& cam){
if ( !cam.target() ) {
cam.viewport(
the<window>().real_size());
cam.projection(math::make_orthogonal_lh_matrix4(
the<window>().real_size().cast_to<f32>(), 0.f, 1000.f));
}
});
}
};
class game final : public starter::application {
public:
bool initialize() final {
return create_scene()
&& create_systems();
}
private:
bool create_scene() {
auto scene_prefab_res = the<library>().load_asset<prefab_asset>("scene_bmf_prefab.json");
auto scene_go = scene_prefab_res
? the<world>().instantiate(scene_prefab_res->content())
: nullptr;
return !!scene_go;
}
bool create_systems() {
ecs::registry_filler(the<world>().registry())
.system<game_system>(world::priority_update)
.system<camera_system>(world::priority_pre_render);
return true;
}
};
}
int e2d_main(int argc, char *argv[]) {
const auto starter_params = starter::parameters(
engine::parameters("sample_07", "enduro2d")
.timer_params(engine::timer_parameters()
.maximal_framerate(100)));
modules::initialize<starter>(argc, argv, starter_params).start<game>();
modules::shutdown<starter>();
return 0;
}

View File

@@ -38,20 +38,20 @@ namespace
f32 get_top_pos(const label& l, u32 strings_count) {
const auto& f = l.font()->content();
f32 label_height = f.common().line * strings_count;
f32 label_height = f.info().line_height * strings_count;
f32 top{0};
switch ( l.valign() ) {
case label::valigns::top :
case label::valigns::top:
top = label_height;
break;
case label::valigns::center :
case label::valigns::center:
top = 0.5f * label_height;
break;
case label::valigns::bottom :
case label::valigns::bottom:
top = 0;
break;
case label::valigns::baseline :
top = label_height - (f.common().line - f.common().base);
case label::valigns::baseline:
top = label_height - (f.info().line_height - f.info().glyph_ascent);
break;
default:
E2D_ASSERT_MSG(false,"label_system: uncknow vertical align flag");
@@ -94,8 +94,7 @@ namespace e2d
return;
}
const auto& f = l.font()->content();
auto common = f.common();
v2f texture_size = common.atlas_size.cast_to<f32>();
v2f texture_size = f.info().atlas_size.cast_to<f32>();
if ( l.width() != 0 ) {
f32 word_width{0};
@@ -108,13 +107,13 @@ namespace e2d
string_width = 0;
last_space = -1;
} else {
auto data = f.find_char(ch);
if ( data ) {
const font::glyph_info* glyph = f.find_glyph(ch);
if ( glyph ) {
prev_char != 0
? kerning = f.get_kerning(prev_char, data->id)
? kerning = f.get_kerning(prev_char, ch)
: kerning = 0;
prev_char = ch;
f32 char_width = data->advance + kerning;
f32 char_width = glyph->advance + kerning;
if ( ch == ' ' ) {
if ( string_width + word_width < l.width() ) {
if ( string_width + word_width + char_width < l.width() ) {
@@ -166,13 +165,13 @@ namespace e2d
prev_char = 0;
for ( size_t i = 0, string_ind = 0; i < text.size(); i++ ) {
if ( text[i] != '\n' ) {
auto data = f.find_char(text[i]);
if ( data ) {
const font::glyph_info* glyph = f.find_glyph(text[i]);
if ( glyph ) {
letters_size++;
prev_char != 0
? kerning = f.get_kerning(prev_char, data->id)
? kerning = f.get_kerning(prev_char, text[i])
: kerning = 0;
strings_width[string_ind] += data->advance + kerning;
strings_width[string_ind] += glyph->advance + kerning;
}
} else {
string_ind++;
@@ -190,49 +189,49 @@ namespace e2d
for ( size_t i = 0, str_ind = 0; i < text.size(); i++ ) {
if ( text[i] == '\n' ) {
str_ind++;
y_pos -= common.line;
y_pos -= f.info().line_height;
x_pos = get_x_pos(l, strings_width[str_ind]);
prev_char = 0;
continue;
}
auto data = f.find_char(text[i]);
if ( data ) {
offset = data->offset.cast_to<f32>();
const font::glyph_info* glyph = f.find_glyph(text[i]);
if ( glyph ) {
offset = glyph->offset.cast_to<f32>();
prev_char != 0
? kerning = f.get_kerning(prev_char, data->id)
? kerning = f.get_kerning(prev_char, text[i])
: kerning = 0;
offset.x += kerning;
prev_char = data->id;
prev_char = text[i];
size_t start_vertices = letters_counter * 4;
vertices[start_vertices] = v3f(
x_pos + offset.x,
y_pos - offset.y - data->rect.size.y,
y_pos - offset.y - glyph->tex_rect.size.y,
0);
vertices[start_vertices + 1] = v3f(
x_pos + offset.x,
y_pos - offset.y,
0);
vertices[start_vertices + 2] = v3f(
x_pos + data->rect.size.x + offset.x,
x_pos + glyph->tex_rect.size.x + offset.x,
y_pos - offset.y,
0);
vertices[start_vertices + 3] = v3f(
x_pos + data->rect.size.x + offset.x,
y_pos - offset.y - data->rect.size.y,
x_pos + glyph->tex_rect.size.x + offset.x,
y_pos - offset.y - glyph->tex_rect.size.y,
0);
uvs[start_vertices] = v2f(
data->rect.position.x / texture_size.x,
data->rect.position.y / texture_size.y);
glyph->tex_rect.position.x / texture_size.x,
glyph->tex_rect.position.y / texture_size.y);
uvs[start_vertices + 1] = v2f(
data->rect.position.x / texture_size.x,
(data->rect.position.y + data->rect.size.y) / texture_size.y);
glyph->tex_rect.position.x / texture_size.x,
(glyph->tex_rect.position.y + glyph->tex_rect.size.y) / texture_size.y);
uvs[start_vertices + 2] = v2f(
(data->rect.position.x + data->rect.size.x) / texture_size.x,
(data->rect.position.y + data->rect.size.y) / texture_size.y);
(glyph->tex_rect.position.x + glyph->tex_rect.size.x) / texture_size.x,
(glyph->tex_rect.position.y + glyph->tex_rect.size.y) / texture_size.y);
uvs[start_vertices + 3] = v2f(
(data->rect.position.x + data->rect.size.x) / texture_size.x,
data->rect.position.y / texture_size.y);
(glyph->tex_rect.position.x + glyph->tex_rect.size.x) / texture_size.x,
glyph->tex_rect.position.y / texture_size.y);
colors[start_vertices] = l.tint();
colors[start_vertices + 1] = l.tint();
@@ -247,7 +246,7 @@ namespace e2d
indices[start_indices + 4] = start_vertices + 3;
indices[start_indices + 5] = start_vertices;
x_pos += data->advance + kerning;
x_pos += glyph->advance + kerning;
letters_counter++;
}
}
@@ -270,8 +269,7 @@ namespace e2d
if ( r.materials().size() > 0 &&
r.materials().front()->content().properties().sampler_count() == 0 ) {
str page_file = f.find_page(0)->file;
auto texture_p = the<library>().load_asset<texture_asset>(page_file);
auto texture_p = the<library>().load_asset<texture_asset>(f.info().atlas_file);
if ( !texture_p ) {
return;
}

View File

@@ -4,228 +4,15 @@
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#include <enduro2d/utils/font.hpp>
#include <enduro2d/utils/strings.hpp>
#include "font_impl/font_impl.hpp"
namespace
{
using namespace e2d;
using namespace e2d::strings;
class bmfont_loading_exception : public exception {
const char* what() const noexcept override {
return "bmfont loading exception";
}
};
inline u64 make_kerning_key(u32 first, u32 second) noexcept {
u64 make_kerning_key(u32 first, u32 second) noexcept {
return (static_cast<u64>(first) << 32) | static_cast<u64>(second);
}
str_view read_tag(str_view buf, u32& pos) {
str_view res;
auto end = buf.find(' ', pos);
if ( end != str_view::npos ) {
res = buf.substr(pos, end - pos);
pos = end;
}
return res;
}
bool read_key(str_view buf, u32& pos, str& key) {
auto end = buf.find('=', pos);
if ( end != str_view::npos ) {
auto start = buf.rfind(' ', end);
if ( start != str_view::npos ) {
start++;
key = buf.substr(start, end - start);
pos = end + 1;
return true;
}
}
return false;
}
i32 read_int(str_view buf, u32& pos) {
i32 res{0};
auto end = buf.find(' ', pos);
if ( end == str_view::npos ) {
end = buf.find("\r\n", pos);
if ( end == str_view::npos ) {
end = buf.find('\n', pos);
}
}
if ( end == str_view::npos ) {
throw bmfont_loading_exception();
} else {
if ( !try_parse(buf.substr(pos, end - pos), res) ) {
throw bmfont_loading_exception();
}
pos = end;
}
return res;
}
str_view read_string(const str_view& buf, u32& pos) {
str_view res;
auto start = buf.find('"', pos);
if ( start == str_view::npos ) {
throw bmfont_loading_exception();
} else {
start++;
auto end = buf.find('"', start);
if ( end == str_view::npos ) {
throw bmfont_loading_exception();
} else {
res = buf.substr(start, end - start);
pos = end + 1;
}
}
return res;
}
font::data load_font_data(str_view content) {
u32 pos{0};
str_view line;
str_view tag;
str key;
font::data data;
vector<font::char_data> chars;
vector<font::kerning_data> kernings;
chars.reserve(120);
kernings.reserve(120);
size_t start_line{0};
size_t end_line = content.find('\n', start_line);
while ( end_line != str_view::npos ) {
pos = 0;
line = content.substr(start_line, end_line - start_line + 1);
if ( !line.empty() ) {
tag = read_tag(line, pos);
if ( tag == "info" ) {
// info face="Arial-Black" size=32 bold=0 italic=0 charset=""
// unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=2,2
while ( read_key(line, pos, key) ) {
if ( key == "face" ) {
data.info.face = read_string(line, pos);
} else if ( key == "size" ) {
data.info.size = read_int(line, pos);
}
}
} else if ( tag == "common" ) {
// common lineHeight=54 base=35 scaleW=512 scaleH=512 pages=1 packed=0
while ( read_key(line, pos, key) ) {
if ( key == "lineHeight" ) {
data.common.line = read_int(line, pos);
} else if ( key == "base" ) {
data.common.base = read_int(line, pos);
} else if ( key == "scaleW" ) {
data.common.atlas_size.x = read_int(line, pos);
} else if ( key == "scaleH" ) {
data.common.atlas_size.y = read_int(line, pos);
} else if ( key == "pages" ) {
data.common.pages = read_int(line, pos);
}
}
} else if ( tag == "page" ) {
// page id=0 file="arial.png"
font::page_data page_data;
while ( read_key(line, pos, key) ) {
if ( key == "id" ) {
page_data.id = read_int(line, pos);
} else if ( key == "file" ) {
page_data.file = read_string(line, pos);
}
}
data.pages.insert(std::move(page_data));
} else if ( tag == "chars" ) {
// chars count=95
while ( read_key(line, pos, key) ) {
if ( key == "count" ) {
chars.reserve(read_int(line, pos));
}
}
} else if ( tag == "char" ) {
// char id=123 x=2 y=2 width=38 height=54
// xoffset=0 yoffset=-3 xadvance=12 page=0 chnl=0
font::char_data c;
while ( read_key(line, pos, key) ) {
if ( key == "id" ) {
c.id = read_int(line, pos);
} else if ( key == "x" ) {
c.rect.position.x = read_int(line, pos);
} else if ( key == "y" ) {
c.rect.position.y = read_int(line, pos);
} else if ( key == "width" ) {
c.rect.size.x = read_int(line, pos);
} else if ( key == "height" ) {
c.rect.size.y = read_int(line, pos);
c.rect.position.y =
data.common.atlas_size.y -
c.rect.position.y -
c.rect.size.y;
} else if ( key == "xoffset" ) {
c.offset.x = read_int(line, pos);
} else if ( key == "yoffset" ) {
c.offset.y = read_int(line, pos);
} else if ( key == "xadvance" ) {
c.advance = read_int(line, pos);
} else if ( key == "page" ) {
c.page = read_int(line, pos);
} else if ( key == "chnl" ) {
c.chnl = read_int(line, pos);
}
}
chars.push_back(std::move(c));
} else if ( tag == "kernings" ) {
while ( read_key(line, pos, key) ) {
if ( key == "count" ) {
kernings.reserve(read_int(line, pos));
}
}
} else if ( tag == "kerning" ) {
font::kerning_data k;
while ( read_key(line, pos, key) ) {
if ( key == "first" ) {
k.chars.first = read_int(line, pos);
} else if ( key == "second" ) {
k.chars.second = read_int(line, pos);
} else if ( key == "amount" ) {
k.amount = read_int(line, pos);
}
}
kernings.push_back(std::move(k));
}
}
start_line = content.find_first_not_of(' ', end_line + 1);
end_line = content.find('\n', start_line);
}
if (chars.empty()) {
throw bmfont_loading_exception();
}
data.chars.reserve(chars.size());
for ( size_t i = 0; i < chars.size(); i++ ) {
data.chars.insert(std::make_pair(chars[i].id, chars[i]));
}
data.kernings.reserve(kernings.size());
for ( size_t i = 0; i < kernings.size(); i++ ) {
auto& k = kernings[i];
data.kernings.insert(
std::make_pair(
make_kerning_key(k.chars.first, k.chars.second),
k.amount));
}
return data;
}
}
namespace e2d
@@ -246,12 +33,12 @@ namespace e2d
return assign(other);
}
font::font(data&& data) noexcept {
assign(std::move(data));
font::font(content&& content) noexcept {
assign(std::move(content));
}
font::font(const data& data) {
assign(data);
font::font(const content& content) {
assign(content);
}
font& font::assign(font&& other) noexcept {
@@ -264,79 +51,62 @@ namespace e2d
font& font::assign(const font& other) {
if ( this != &other ) {
data_ = other.data_;
content_ = other.content_;
}
return *this;
}
font& font::assign(data&& data) noexcept {
data_ = std::move(data);
font& font::assign(content&& content) noexcept {
content_ = std::move(content);
return *this;
}
font& font::assign(const data& data) {
data_ = data;
font& font::assign(const content& content) {
content_ = content;
return *this;
}
void font::swap(font& other) noexcept {
using std::swap;
swap(data_.info, other.data_.info);
swap(data_.common, other.data_.common);
swap(data_.pages, other.data_.pages);
swap(data_.chars, other.data_.chars);
swap(data_.kernings, other.data_.kernings);
swap(content_.info, other.content_.info);
swap(content_.kernings, other.content_.kernings);
swap(content_.glyphs, other.content_.glyphs);
}
void font::clear() noexcept {
data_ = data();
content_ = content();
}
bool font::empty() const noexcept {
return data_.pages.empty();
return !content_.info.font_size;
}
const font::info_data& font::info() const noexcept {
return data_.info;
}
const font::common_data& font::common() const noexcept {
return data_.common;
}
const flat_set<font::page_data>& font::pages() const noexcept {
return data_.pages;
}
const flat_map<u32, font::char_data>& font::chars() const noexcept {
return data_.chars;
const font::font_info& font::info() const noexcept {
return content_.info;
}
const flat_map<u64, i32>& font::kernings() const noexcept {
return data_.kernings;
return content_.kernings;
}
const font::page_data* font::find_page(u32 id) const noexcept {
const auto iter = data_.pages.find(id);
return iter != data_.pages.end()
? &*iter
: nullptr;
}
const font::char_data* font::find_char(u32 id) const noexcept {
const auto iter = data_.chars.find(id);
return iter != data_.chars.end()
? &iter->second
: nullptr;
const flat_map<u32, font::glyph_info>& font::glyphs() const noexcept {
return content_.glyphs;
}
i32 font::get_kerning(u32 first, u32 second) const noexcept {
u64 key = make_kerning_key(first, second);
const auto iter = data_.kernings.find(key);
return iter != data_.kernings.end()
const u64 key = make_kerning_key(first, second);
const auto iter = content_.kernings.find(key);
return iter != content_.kernings.end()
? iter->second
: 0;
}
const font::glyph_info* font::find_glyph(u32 code_point) const noexcept {
const auto iter = content_.glyphs.find(code_point);
return iter != content_.glyphs.end()
? &iter->second
: nullptr;
}
}
namespace e2d
@@ -347,98 +117,52 @@ namespace e2d
bool operator==(const font& l, const font& r) noexcept {
return l.info() == r.info()
&& l.common() == r.common()
&& l.pages() == r.pages()
&& l.chars() == r.chars()
&& l.kernings() == r.kernings();
&& l.kernings() == r.kernings()
&& l.glyphs() == r.glyphs();
}
bool operator!=(const font& l, const font& r) noexcept {
return !(l == r);
}
bool operator<(u32 l, const font::page_data& r) noexcept {
return l < r.id;
bool operator==(const font::font_info& l, const font::font_info& r) noexcept {
return l.atlas_file == r.atlas_file
&& l.atlas_size == r.atlas_size
&& l.font_size == r.font_size
&& l.line_height == r.line_height
&& l.glyph_ascent == r.glyph_ascent;
}
bool operator<(const font::page_data& l, u32 r) noexcept {
return l.id < r;
bool operator==(const font::glyph_info& l, const font::glyph_info& r) noexcept {
return l.offset == r.offset
&& l.tex_rect == r.tex_rect
&& l.advance == r.advance;
}
bool operator<(const font::page_data& l, const font::page_data& r) noexcept {
return l.id < r.id;
}
bool operator<(u32 l, const font::char_data& r) noexcept {
return l < r.id;
}
bool operator<(const font::char_data& l, u32 r) noexcept {
return l.id < r;
}
bool operator<(const font::char_data& l, const font::char_data& r) noexcept {
return l.id < r.id;
}
bool operator<(const std::pair<u32,u32>& l, const font::kerning_data& r) noexcept {
return l < r.chars;
}
bool operator<(const font::kerning_data& l, const std::pair<u32,u32>& r) noexcept {
return l.chars < r;
}
bool operator<(const font::kerning_data& l, const font::kerning_data& r) noexcept {
return l.chars < r.chars;
}
bool operator==(const font::info_data& l, const font::info_data& r) noexcept {
return l.face == r.face
&& l.size == r.size;
}
bool operator==(const font::common_data& l, const font::common_data& r) noexcept {
return l.line == r.line
&& l.base == r.base
&& l.pages == r.pages
&& l.atlas_size == r.atlas_size;
}
bool operator==(const font::page_data& l, const font::page_data& r) noexcept {
return l.id == r.id
&& l.file == r.file;
}
bool operator==(const font::char_data& l, const font::char_data& r) noexcept {
return l.id == r.id
&& l.rect == r.rect
&& l.offset == r.offset
&& l.advance == r.advance
&& l.page == r.page
&& l.chnl == r.chnl;
}
bool operator==(const font::kerning_data& l, const font::kerning_data& r) noexcept {
return l.chars == r.chars
&& l.amount == r.amount;
bool operator==(const font::content& l, const font::content& r) noexcept {
return l.info == r.info
&& l.kernings == r.kernings
&& l.glyphs == r.glyphs;
}
}
namespace e2d::fonts
{
bool try_load_font(font& dst, const buffer& src) noexcept {
bool try_load_font(
font& dst,
buffer_view src) noexcept
{
try {
dst = load_font_data(str_view{
reinterpret_cast<const char*>(src.data()),
src.size()});
return true;
return impl::load_font_bmfont(dst, src);
} catch (...) {
return false;
}
}
bool try_load_font(font& dst, const input_stream_uptr& src) noexcept {
bool try_load_font(
font& dst,
const input_stream_uptr& src) noexcept
{
buffer file_data;
return streams::try_read_tail(file_data, src)
&& try_load_font(dst, file_data);

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#pragma once
#include <enduro2d/utils/font.hpp>
#include <enduro2d/utils/buffer.hpp>
#include <enduro2d/utils/strings.hpp>
#include <enduro2d/utils/buffer_view.hpp>
namespace e2d::fonts::impl
{
bool load_font_bmfont(font& dst, buffer_view src);
}

View File

@@ -0,0 +1,359 @@
/*******************************************************************************
* This file is part of the "Enduro2D"
* For conditions of distribution and use, see copyright notice in LICENSE.md
* Copyright (C) 2018-2019, by Matvey Cherevko (blackmatov@gmail.com)
******************************************************************************/
#include "font_impl.hpp"
namespace
{
using namespace e2d;
bool read_line(str_view content, std::size_t& pos, str_view& line) {
if ( pos > content.size() ) {
return false;
}
auto end_of_line = content.find('\n', pos);
if ( end_of_line == str_view::npos ) {
end_of_line = content.size();
}
line = content.substr(pos, end_of_line - pos);
if ( !line.empty() && line.back() == '\r' ) {
line.remove_suffix(1u);
}
pos = end_of_line + 1u;
return true;
}
bool read_tag(str_view line, std::size_t& pos, str_view& tag) {
const auto start = line.find_first_not_of(' ', pos);
if ( start == str_view::npos ) {
return false;
}
const auto end = line.find(' ', start);
if ( end == str_view::npos || end == line.size() ) {
return false;
}
tag = line.substr(start, end - start);
pos = end + 1;
return true;
}
bool read_key(str_view line, std::size_t& pos, str_view& key) {
const auto eq = line.find('=', pos);
if ( eq == str_view::npos || eq == 0u || eq == line.size() ) {
return false;
}
const auto last = line.find_last_not_of(' ', eq - 1u);
if ( last == str_view::npos ) {
return false;
}
const auto prestart = line.rfind(' ', last);
const auto start = prestart == str_view::npos
? 0u
: prestart + 1u;
key = line.substr(start, last - prestart);
pos = eq + 1;
return true;
}
template < typename T >
bool read_value(str_view line, std::size_t& pos, T& value) {
const auto start = line.find_first_not_of(' ', pos);
if ( start == str_view::npos ) {
return false;
}
auto end = line.find(' ', start);
if ( end == str_view::npos ) {
end = line.size();
}
if ( !strings::try_parse(line.substr(start, end - start), value) ) {
return false;
}
pos = end + 1;
return true;
}
bool read_value(str_view line, std::size_t& pos, str& value) {
const auto start = line.find("\"", pos);
if ( start == str_view::npos || start == line.size() ) {
return false;
}
const auto end = line.find("\"", start + 1u);
if ( end == str_view::npos ) {
return false;
}
value = line.substr(start + 1, end - start - 1u);
pos = end + 1;
return true;
}
// info face="Arial-Black" size=32 bold=0 italic=0 charset=""
// unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=2,2
bool parse_info_line(str_view line, font::content& font_content) {
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "size" ) {
if ( !read_value(line, pos, font_content.info.font_size) ) {
return false;
}
}
}
return true;
}
// common lineHeight=54 base=35 scaleW=512 scaleH=512 pages=1 packed=0
bool parse_common_line(str_view line, font::content& font_content) {
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "lineHeight" ) {
if ( !read_value(line, pos, font_content.info.line_height) ) {
return false;
}
} else if ( key == "base" ) {
if ( !read_value(line, pos, font_content.info.glyph_ascent) ) {
return false;
}
} else if ( key == "scaleW" ) {
if ( !read_value(line, pos, font_content.info.atlas_size.x) ) {
return false;
}
} else if ( key == "scaleH" ) {
if ( !read_value(line, pos, font_content.info.atlas_size.y) ) {
return false;
}
} else if ( key == "pages" ) {
u32 pages = 0u;
if ( !read_value(line, pos, pages) || pages > 1u ) {
return false;
}
}
}
return true;
}
// page id=0 file="arial.png"
bool parse_page_line(str_view line, font::content& font_content) {
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "id" ) {
u32 id = 0u;
if ( !read_value(line, pos, id) || id > 0 ) {
return false;
}
} else if ( key == "file" ) {
if ( !read_value(line, pos, font_content.info.atlas_file) ) {
return false;
}
}
}
return true;
}
// chars count=95
bool parse_chars_line(str_view line, font::content& font_content) {
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "count" ) {
std::size_t count = 0u;
if ( !read_value(line, pos, count) ) {
return false;
}
font_content.glyphs.reserve(count);
}
}
return true;
}
// char id=123 x=2 y=2 width=38 height=54
// xoffset=0 yoffset=-3 xadvance=12 page=0 chnl=0
bool parse_char_line(str_view line, font::content& font_content) {
u32 code_point = 0;
font::glyph_info glyph;
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "id" ) {
if ( !read_value(line, pos, code_point) ) {
return false;
}
} else if ( key == "x" ) {
if ( !read_value(line, pos, glyph.tex_rect.position.x) ) {
return false;
}
} else if ( key == "y" ) {
if ( !read_value(line, pos, glyph.tex_rect.position.y) ) {
return false;
}
} else if ( key == "width" ) {
if ( !read_value(line, pos, glyph.tex_rect.size.x) ) {
return false;
}
} else if ( key == "height" ) {
if ( !read_value(line, pos, glyph.tex_rect.size.y) ) {
return false;
}
} else if ( key == "xoffset" ) {
if ( !read_value(line, pos, glyph.offset.x) ) {
return false;
}
} else if ( key == "yoffset" ) {
if ( !read_value(line, pos, glyph.offset.y) ) {
return false;
}
} else if ( key == "xadvance" ) {
if ( !read_value(line, pos, glyph.advance) ) {
return false;
}
} else if ( key == "page" ) {
u32 page = 0;
if ( !read_value(line, pos, page) || page > 0 ) {
return false;
}
}
}
font_content.glyphs.insert_or_assign(
code_point,
std::move(glyph));
return true;
}
// kernings count=343
bool parse_kernings_line(str_view line, font::content& font_content) {
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "count" ) {
std::size_t count = 0u;
if ( !read_value(line, pos, count) ) {
return false;
}
font_content.kernings.reserve(count);
}
}
return true;
}
// kerning first=1043 second=1071 amount=-1
bool parse_kerning_line(str_view line, font::content& font_content) {
u32 first = 0;
u32 second = 0;
i32 amount = 0;
str_view key;
std::size_t pos = 0u;
while ( read_key(line, pos, key) ) {
if ( key == "first" ) {
if ( !read_value(line, pos, first) ) {
return false;
}
} else if ( key == "second" ) {
if ( !read_value(line, pos, second) ) {
return false;
}
} else if ( key == "amount" ) {
if ( !read_value(line, pos, amount) ) {
return false;
}
}
}
font_content.kernings.insert_or_assign(
(static_cast<u64>(first) << 32) | static_cast<u64>(second),
amount);
return true;
}
}
namespace e2d::fonts::impl
{
bool load_font_bmfont(font& dst, buffer_view src) {
const auto text_src = str_view{
reinterpret_cast<const char*>(src.data()),
src.size()};
font::content font_content;
str_view line;
std::size_t pos = 0u;
while ( read_line(text_src, pos, line) ) {
str_view tag;
std::size_t tag_pos = 0u;
if ( !read_tag(line, tag_pos, tag) ) {
continue;
}
line.remove_prefix(tag_pos);
if ( tag == "info" ) {
if ( !parse_info_line(line, font_content) ) {
return false;
}
} else if ( tag == "common" ) {
if ( !parse_common_line(line, font_content) ) {
return false;
}
} else if ( tag == "page" ) {
if ( !parse_page_line(line, font_content) ) {
return false;
}
} else if ( tag == "chars" ) {
if ( !parse_chars_line(line, font_content) ) {
return false;
}
} else if ( tag == "char" ) {
if ( !parse_char_line(line, font_content) ) {
return false;
}
} else if ( tag == "kernings" ) {
if ( !parse_kernings_line(line, font_content) ) {
return false;
}
} else if ( tag == "kerning" ) {
if ( !parse_kerning_line(line, font_content) ) {
return false;
}
}
}
for ( auto& glyph : font_content.glyphs ) {
glyph.second.tex_rect.position.y =
font_content.info.atlas_size.y -
glyph.second.tex_rect.position.y -
glyph.second.tex_rect.size.y;
}
bool info_valid = !font_content.info.atlas_file.empty()
&& font_content.info.atlas_size.x > 0u
&& font_content.info.atlas_size.y > 0u
&& font_content.info.font_size > 0u
&& font_content.info.line_height > 0u
&& font_content.info.font_size > 0u
&& font_content.info.glyph_ascent > 0u;
bool glyphs_valid = true;
for ( const auto& glyph : font_content.glyphs ) {
const v2u& mst = glyph.second.tex_rect.position + glyph.second.tex_rect.size;
glyphs_valid = glyphs_valid && math::inside(b2u(font_content.info.atlas_size), mst);
}
if ( !info_valid || !glyphs_valid ) {
return false;
}
dst = font(font_content);
return true;
}
}

View File

@@ -10,13 +10,15 @@ using namespace e2d;
namespace
{
const char* const mini_fnt = R"fnt(
info face="Arial" size=50 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0
common lineHeight=58 base=49 scaleW=512 scaleH=512 pages=1 packed=0
info face ="Arial" size= 50 bold=0 italic=0 charset="" unicode=0 stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=0,0
common lineHeight =58 base= 49 scaleW = 512 scaleH=512 pages=1 packed=0
page id=0 file="arial.png"
chars count=3
char id=32 x=342 y=38 width=0 height=0 xoffset=0 yoffset=46 xadvance=14 page=0 chnl=0
char id=33 x=448 y=386 width=10 height=40 xoffset=5 yoffset=10 xadvance=14 page=0 chnl=0
char id=34 x=394 y=306 width=18 height=18 xoffset=3 yoffset=8 xadvance=18 page=0 chnl=0
kernings count=2
kerning first=1059 second=1081 amount=-1
kerning first=1043 second=1071 amount=-1
@@ -39,32 +41,22 @@ TEST_CASE("font") {
REQUIRE(fonts::try_load_font(f2, make_memory_stream(buffer(mini_fnt, std::strlen(mini_fnt)))));
REQUIRE(f == f2);
REQUIRE(f.info().face == "Arial");
REQUIRE(f.info().size == 50);
REQUIRE(f.info().atlas_file == "arial.png");
REQUIRE(f.info().atlas_size == v2u(512,512));
REQUIRE(f.info().font_size == 50);
REQUIRE(f.info().line_height == 58);
REQUIRE(f.info().glyph_ascent == 49);
REQUIRE(f.common().line == 58);
REQUIRE(f.common().base == 49);
REQUIRE(f.common().pages == 1);
REQUIRE(f.common().atlas_size == v2u(512,512));
REQUIRE(f.pages().size() == 1);
REQUIRE(f.find_page(0));
REQUIRE(f.find_page(0)->id == 0);
REQUIRE(f.find_page(0)->file == "arial.png");
REQUIRE(f.chars().size() == 3);
REQUIRE_FALSE(f.find_char(100500));
REQUIRE(f.find_char(33));
REQUIRE(f.glyphs().size() == 3);
REQUIRE_FALSE(f.find_glyph(100500));
REQUIRE(f.find_glyph(33));
{
// char id=33 x=448 y=386 width=10 height=40 xoffset=5 yoffset=10 xadvance=14 page=0 chnl=0
font::char_data c;
c.id = 33;
c.rect = b2hi(448, 86, 10, 40);
c.offset = v2hi(5, 10);
font::glyph_info c;
c.offset = v2i(5, 10);
c.tex_rect = b2u(448, 86, 10, 40);
c.advance = 14;
c.page = 0;
c.chnl = 0;
REQUIRE(*f.find_char(33) == c);
REQUIRE(*f.find_glyph(33) == c);
}
REQUIRE(f.kernings().size() == 2);