mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
add vector fonts support
This commit is contained in:
parent
2c81344638
commit
11c3f1c5eb
10 changed files with 212 additions and 34 deletions
|
|
@ -187,6 +187,12 @@ void AssetsLoader::processPreload(
|
|||
config = std::make_shared<ModelCfg>(squashed);
|
||||
break;
|
||||
}
|
||||
case AssetType::FONT: {
|
||||
int size = DEFAULT_FONT_SIZE;
|
||||
map.at("size").get(size);
|
||||
config = std::make_unique<FontCfg>(size);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ struct LayoutCfg : AssetCfg {
|
|||
gui::GUI* gui;
|
||||
scriptenv env;
|
||||
|
||||
LayoutCfg(gui::GUI* gui, scriptenv env) : gui(gui), env(std::move(env)) {
|
||||
}
|
||||
LayoutCfg(gui::GUI* gui, scriptenv env) : gui(gui), env(std::move(env)) {}
|
||||
};
|
||||
|
||||
struct SoundCfg : AssetCfg {
|
||||
|
|
@ -43,6 +42,12 @@ struct SoundCfg : AssetCfg {
|
|||
SoundCfg(bool keepPCM) : keepPCM(keepPCM) {}
|
||||
};
|
||||
|
||||
struct FontCfg : AssetCfg {
|
||||
int size;
|
||||
|
||||
FontCfg(int size) : size(size) {}
|
||||
};
|
||||
|
||||
enum class AtlasType {
|
||||
ATLAS, SEPARATE
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "coders/obj.hpp"
|
||||
#include "coders/vcm.hpp"
|
||||
#include "coders/vec3.hpp"
|
||||
#include "coders/vector_fonts.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
|
|
@ -199,8 +200,18 @@ assetload::postfunc assetload::font(
|
|||
const ResPaths& paths,
|
||||
const std::string& filename,
|
||||
const std::string& name,
|
||||
const std::shared_ptr<AssetCfg>&
|
||||
const std::shared_ptr<AssetCfg>& config
|
||||
) {
|
||||
auto cfg = std::dynamic_pointer_cast<FontCfg>(config);
|
||||
auto ext = fs::path(filename).extension().string();
|
||||
if (ext == ".ttf" || ext == ".otf") {
|
||||
logger.info() << "loading vector font " << util::quote(filename);
|
||||
return [=](auto assets) {
|
||||
auto font = vector_fonts::load_font(paths.find(filename).string());
|
||||
assets->store(font->createInstance(cfg ? cfg->size : 16), name);
|
||||
};
|
||||
}
|
||||
|
||||
auto pages = std::make_shared<std::vector<std::unique_ptr<ImageData>>>();
|
||||
for (size_t i = 0; i <= 1024; i++) {
|
||||
std::string pagefile = filename + "_" + std::to_string(i) + ".png";
|
||||
|
|
@ -214,20 +225,7 @@ assetload::postfunc assetload::font(
|
|||
}
|
||||
}
|
||||
return [=](auto assets) {
|
||||
int res = pages->at(0)->getHeight() / 16;
|
||||
std::vector<std::unique_ptr<Texture>> textures;
|
||||
for (auto& page : *pages) {
|
||||
if (page == nullptr) {
|
||||
textures.emplace_back(nullptr);
|
||||
} else {
|
||||
auto texture = Texture::from(page.get());
|
||||
texture->setMipMapping(false, true);
|
||||
textures.emplace_back(std::move(texture));
|
||||
}
|
||||
}
|
||||
assets->store(
|
||||
std::make_unique<Font>(std::move(textures), res, 4), name
|
||||
);
|
||||
assets->store(Font::createBitmapFont(std::move(*pages)), name);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,34 +1,124 @@
|
|||
#include "vector_fonts.hpp"
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "graphics/core/ImageData.hpp"
|
||||
#include "graphics/core/Texture.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
|
||||
#include <stdexcept>
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
static debug::Logger logger("vector_fonts");
|
||||
|
||||
static FT_Library library;
|
||||
|
||||
static const char* get_ft_error_message(FT_Error error) {
|
||||
#undef FTERRORS_H_
|
||||
#define FT_ERRORDEF( e, v, s ) case v: return s;
|
||||
#define FT_ERROR_START_LIST switch (error) {
|
||||
#define FT_ERROR_END_LIST }
|
||||
#include FT_ERRORS_H
|
||||
return "unknown error";
|
||||
}
|
||||
|
||||
namespace {
|
||||
class FTFontFile : public vector_fonts::FontFile {
|
||||
public:
|
||||
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer) : face(std::move(face)), buffer(std::move(buffer)) {
|
||||
}
|
||||
|
||||
~FTFontFile() {
|
||||
if (FT_Error error = FT_Done_Face(face)) {
|
||||
logger.error()
|
||||
<< "error on FT_Done_Face: " << get_ft_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Font> createInstance(int size) override {
|
||||
int pagesCount = 5;
|
||||
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
std::vector<Font::Glyph> glyphs;
|
||||
|
||||
if (FT_Error error = FT_Set_Pixel_Sizes(face, 0, size)) {
|
||||
throw std::runtime_error(
|
||||
"FT_Set_Pixel_Sizes error: " +
|
||||
std::string(get_ft_error_message(error))
|
||||
);
|
||||
}
|
||||
|
||||
for (int pageid = 0; pageid < pagesCount; pageid++) {
|
||||
int dstSize = size * 16;
|
||||
ImageData canvas(ImageFormat::RGBA8888, dstSize, dstSize);
|
||||
for (int c = 0; c < 256; c++) {
|
||||
int srcWidth = size;
|
||||
int srcHeight = size;
|
||||
ImageData bitmapDst(ImageFormat::RGBA8888, srcWidth, srcHeight);
|
||||
int codepoint = pageid << 8 | c;
|
||||
|
||||
if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) {
|
||||
logger.warning() << get_ft_error_message(error);
|
||||
continue;
|
||||
}
|
||||
FT_Bitmap& bitmap = face->glyph->bitmap;
|
||||
if (codepoint < 1000)
|
||||
logger.info() << codepoint << ": " << bitmap.width << "x" << bitmap.rows << " " << size;
|
||||
auto dstData = bitmapDst.getData();
|
||||
for (int row = 0; row < bitmap.rows; row++) {
|
||||
for (int col = 0; col < bitmap.width; col++) {
|
||||
uint8_t value = bitmap.buffer[row * bitmap.pitch + col];
|
||||
dstData[(row * srcWidth + col) * 4 + 0] = 255;
|
||||
dstData[(row * srcWidth + col) * 4 + 1] = 255;
|
||||
dstData[(row * srcWidth + col) * 4 + 2] = 255;
|
||||
dstData[(row * srcWidth + col) * 4 + 3] = value;
|
||||
}
|
||||
}
|
||||
canvas.blit(bitmapDst, (int)(c % 16) * size, (int)(c / 16) * size);
|
||||
glyphs.push_back(Font::Glyph {
|
||||
face->glyph->bitmap_top - size,
|
||||
static_cast<int>(face->glyph->advance.x >> 6)
|
||||
});
|
||||
}
|
||||
canvas.flipY();
|
||||
pages.push_back(Texture::from(&canvas));
|
||||
}
|
||||
return std::make_unique<Font>(std::move(pages), std::move(glyphs), size, size / 2);
|
||||
}
|
||||
private:
|
||||
FT_Face face;
|
||||
util::Buffer<ubyte> buffer;
|
||||
};
|
||||
}
|
||||
|
||||
void vector_fonts::initialize() {
|
||||
if (FT_Error error = FT_Init_FreeType(&library)) {
|
||||
throw std::runtime_error(
|
||||
"could not initialize freetype: " +
|
||||
std::string(FT_Error_String(error))
|
||||
std::string(get_ft_error_message(error))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static FT_Face load_ft_face(const io::path& path) {
|
||||
auto bytes = io::read_bytes_buffer(path);
|
||||
void vector_fonts::finalize() {
|
||||
if (FT_Error error = FT_Done_FreeType(library)) {
|
||||
logger.error() << "error on FT_Done_FreeType: " << get_ft_error_message(error);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<vector_fonts::FontFile> vector_fonts::load_font(const std::string& filename) {
|
||||
auto bytes = io::read_bytes_buffer(io::path(filename));
|
||||
|
||||
FT_Face face;
|
||||
|
||||
if (FT_Error error =
|
||||
FT_New_Memory_Face(library, bytes.data(), bytes.size(), 0, &face)) {
|
||||
throw std::runtime_error(
|
||||
"could not load font '" + path.string() +
|
||||
"': " + std::string(FT_Error_String(error))
|
||||
"could not load font '" + filename +
|
||||
"': " + std::string(get_ft_error_message(error))
|
||||
);
|
||||
}
|
||||
|
||||
return face;
|
||||
return std::make_unique<FTFontFile>(std::move(face), std::move(bytes));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,20 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
class Font;
|
||||
|
||||
namespace vector_fonts {
|
||||
class FontFile {
|
||||
public:
|
||||
virtual ~FontFile() {}
|
||||
|
||||
virtual std::unique_ptr<Font> createInstance(int size) = 0;
|
||||
};
|
||||
|
||||
void initialize();
|
||||
void finalize();
|
||||
|
||||
std::unique_ptr<vector_fonts::FontFile> load_font(const std::string& filename);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
|
|||
|
||||
inline constexpr int ATLAS_EXTRUSION = 2;
|
||||
|
||||
inline constexpr int DEFAULT_FONT_SIZE = 16;
|
||||
|
||||
inline const std::string SHADERS_FOLDER = "shaders";
|
||||
inline const std::string TEXTURES_FOLDER = "textures";
|
||||
inline const std::string FONTS_FOLDER = "fonts";
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "coders/commons.hpp"
|
||||
#include "coders/GLSLExtension.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
#include "coders/vector_fonts.hpp"
|
||||
#include "content/ContentControl.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
|
@ -167,6 +168,8 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
settings.audio
|
||||
);
|
||||
|
||||
vector_fonts::initialize();
|
||||
|
||||
if (settings.ui.language.get() == "auto") {
|
||||
settings.ui.language.set(
|
||||
langs::locale_by_envlocale(platform::detect_locale())
|
||||
|
|
@ -342,6 +345,9 @@ void Engine::close() {
|
|||
gui.reset();
|
||||
logger.info() << "gui finished";
|
||||
}
|
||||
if (!isHeadless()) {
|
||||
vector_fonts::finalize();
|
||||
}
|
||||
audio::close();
|
||||
debuggingServer.reset();
|
||||
network.reset();
|
||||
|
|
|
|||
|
|
@ -11,8 +11,17 @@ inline constexpr uint GLYPH_SIZE = 16;
|
|||
inline constexpr uint MAX_CODEPAGES = 10000; // idk ho many codepages unicode has
|
||||
inline constexpr glm::vec4 SHADOW_TINT(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
Font::Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset)
|
||||
: lineHeight(lineHeight), yoffset(yoffset), pages(std::move(pages)) {
|
||||
Font::Font(
|
||||
std::vector<std::unique_ptr<Texture>> pages,
|
||||
std::vector<Glyph> glyphs,
|
||||
int lineHeight,
|
||||
int yoffset
|
||||
)
|
||||
: lineHeight(lineHeight),
|
||||
glyphInterval(lineHeight / 2),
|
||||
yoffset(yoffset),
|
||||
pages(std::move(pages)),
|
||||
glyphs(std::move(glyphs)) {
|
||||
}
|
||||
|
||||
Font::~Font() = default;
|
||||
|
|
@ -39,7 +48,7 @@ bool Font::isPrintableChar(uint codepoint) const {
|
|||
}
|
||||
|
||||
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
||||
return std::min(text.length() - offset, length) * glyphInterval;
|
||||
return std::min(text.length() - offset, length) * _glyphInterval;
|
||||
}
|
||||
|
||||
int Font::calcWidth(std::wstring_view text, size_t length) const {
|
||||
|
|
@ -71,7 +80,7 @@ static inline void draw_glyph(
|
|||
|
||||
batch.sprite(
|
||||
pos.x + (offset.x + i / (right.x/glyphInterval/2.0f)) * right.x,
|
||||
pos.y + offset.y * right.y,
|
||||
pos.y + offset.y * up.y,
|
||||
right.x / glyphInterval,
|
||||
up.y,
|
||||
-0.15f * style.italic,
|
||||
|
|
@ -152,11 +161,15 @@ static inline void draw_text(
|
|||
x++;
|
||||
continue;
|
||||
}
|
||||
int yOffset = 0;
|
||||
if (auto glyph = font.getGlyph(c)) {
|
||||
yOffset = glyph->yOffset;
|
||||
}
|
||||
uint charpage = c >> 8;
|
||||
if (charpage == page){
|
||||
batch.texture(font.getPage(charpage));
|
||||
draw_glyph(
|
||||
batch, pos, glm::vec2(x, y), c, right, up, interval, style
|
||||
batch, pos, glm::vec2(x, y - yOffset / static_cast<float>(font.getLineHeight())), c, right, up, interval, style
|
||||
);
|
||||
}
|
||||
else if (charpage > page && charpage < next){
|
||||
|
|
@ -243,3 +256,21 @@ void Font::draw(
|
|||
styleMapOffset
|
||||
);
|
||||
}
|
||||
|
||||
std::unique_ptr<Font> Font::createBitmapFont(
|
||||
std::vector<std::unique_ptr<ImageData>> pages
|
||||
) {
|
||||
int res = pages.at(0)->getHeight() / 16;
|
||||
std::vector<std::unique_ptr<Texture>> textures;
|
||||
std::vector<Glyph> glyphs(textures.size() * 256);
|
||||
for (auto& page : pages) {
|
||||
if (page == nullptr) {
|
||||
textures.emplace_back(nullptr);
|
||||
} else {
|
||||
auto texture = Texture::from(page.get());
|
||||
texture->setMipMapping(false, true);
|
||||
textures.emplace_back(std::move(texture));
|
||||
}
|
||||
}
|
||||
return std::make_unique<Font>(std::move(textures), std::move(glyphs), res, 4);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,23 @@ class Texture;
|
|||
class Batch2D;
|
||||
class Batch3D;
|
||||
class Camera;
|
||||
class ImageData;
|
||||
|
||||
class Font;
|
||||
|
||||
class Font {
|
||||
int lineHeight;
|
||||
int yoffset;
|
||||
int glyphInterval = 8;
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
public:
|
||||
Font(std::vector<std::unique_ptr<Texture>> pages, int lineHeight, int yoffset);
|
||||
struct Glyph {
|
||||
int yOffset;
|
||||
int xAdvance;
|
||||
};
|
||||
|
||||
Font(
|
||||
std::vector<std::unique_ptr<Texture>> pages,
|
||||
std::vector<Glyph> glyphs,
|
||||
int lineHeight,
|
||||
int yoffset
|
||||
);
|
||||
~Font();
|
||||
|
||||
int getLineHeight() const;
|
||||
|
|
@ -69,4 +78,20 @@ public:
|
|||
FontMetrics getMetrics() const {
|
||||
return {lineHeight, yoffset, glyphInterval};
|
||||
}
|
||||
|
||||
const Glyph* getGlyph(int codepoint) const {
|
||||
return (codepoint < 0 || codepoint >= glyphs.size())
|
||||
? nullptr
|
||||
: &glyphs.at(codepoint);
|
||||
}
|
||||
|
||||
static std::unique_ptr<Font> createBitmapFont(
|
||||
std::vector<std::unique_ptr<ImageData>> pages
|
||||
);
|
||||
private:
|
||||
int lineHeight;
|
||||
int yoffset;
|
||||
int glyphInterval;
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
std::vector<Glyph> glyphs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
struct FontMetrics {
|
||||
int lineHeight;
|
||||
int yoffset;
|
||||
int glyphInterval = 8;
|
||||
int _glyphInterval = 8;
|
||||
|
||||
int calcWidth(std::wstring_view text, size_t offset=0, size_t length=-1) const;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue