mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-14 15:31:49 +00:00
feat: non-monospaced fonts support
This commit is contained in:
parent
93dbf34d15
commit
ae54ac9975
8 changed files with 90 additions and 46 deletions
|
|
@ -82,6 +82,8 @@ namespace {
|
||||||
|
|
||||||
bool renderGlyph(int codepoint, ImageData& bitmapDst) {
|
bool renderGlyph(int codepoint, ImageData& bitmapDst) {
|
||||||
int width = bitmapDst.getWidth();
|
int width = bitmapDst.getWidth();
|
||||||
|
int height = bitmapDst.getHeight();
|
||||||
|
|
||||||
if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) {
|
if (FT_Error error = FT_Load_Char(face, codepoint, FT_LOAD_RENDER)) {
|
||||||
logger.warning() << get_ft_error_message(error);
|
logger.warning() << get_ft_error_message(error);
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -89,8 +91,8 @@ namespace {
|
||||||
const FT_Bitmap& bitmap = face->glyph->bitmap;
|
const FT_Bitmap& bitmap = face->glyph->bitmap;
|
||||||
auto dstData = bitmapDst.getData();
|
auto dstData = bitmapDst.getData();
|
||||||
std::memset(dstData, 0, bitmapDst.getDataSize());
|
std::memset(dstData, 0, bitmapDst.getDataSize());
|
||||||
for (int row = 0; row < bitmap.rows; row++) {
|
for (int row = 0; row < std::min<int>(bitmap.rows, height); row++) {
|
||||||
for (int col = 0; col < bitmap.width; col++) {
|
for (int col = 0; col < std::min<int>(bitmap.width, width); col++) {
|
||||||
uint8_t value = bitmap.buffer[row * bitmap.pitch + col];
|
uint8_t value = bitmap.buffer[row * bitmap.pitch + col];
|
||||||
dstData[(row * width + col) * 4 + 0] = 255;
|
dstData[(row * width + col) * 4 + 0] = 255;
|
||||||
dstData[(row * width + col) * 4 + 1] = 255;
|
dstData[(row * width + col) * 4 + 1] = 255;
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,8 @@ Font::Font(
|
||||||
int yoffset
|
int yoffset
|
||||||
)
|
)
|
||||||
: lineHeight(lineHeight),
|
: lineHeight(lineHeight),
|
||||||
glyphInterval(lineHeight / 2),
|
|
||||||
yoffset(yoffset),
|
yoffset(yoffset),
|
||||||
|
glyphInterval(lineHeight / 2),
|
||||||
pages(std::move(pages)),
|
pages(std::move(pages)),
|
||||||
glyphs(std::move(glyphs)) {
|
glyphs(std::move(glyphs)) {
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +48,22 @@ bool Font::isPrintableChar(uint codepoint) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
||||||
return std::min(text.length() - offset, length) * _glyphInterval;
|
auto font = this->font.has_value() ? this->font->lock() : nullptr;
|
||||||
|
if (font == nullptr) {
|
||||||
|
return std::min(text.length() - offset, length) * _glyphInterval;
|
||||||
|
}
|
||||||
|
int totalWidth = 0;
|
||||||
|
for (int i = offset; i < offset + length && i < text.length(); i++) {
|
||||||
|
auto codepoint = text[i];
|
||||||
|
if (!font->isPrintableChar(codepoint)) {
|
||||||
|
totalWidth += _glyphInterval;
|
||||||
|
} else if (auto glyph = font->getGlyph(codepoint)) {
|
||||||
|
totalWidth += glyph->xAdvance;
|
||||||
|
} else {
|
||||||
|
totalWidth += _glyphInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return totalWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Font::calcWidth(std::wstring_view text, size_t length) const {
|
int Font::calcWidth(std::wstring_view text, size_t length) const {
|
||||||
|
|
@ -110,7 +125,6 @@ static inline void draw_glyph(
|
||||||
color = style.color;
|
color = style.color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
batch.sprite(
|
batch.sprite(
|
||||||
pos + right * (offset.x + i) + up * offset.y,
|
pos + right * (offset.x + i) + up * offset.y,
|
||||||
up, right / glyphInterval,
|
up, right / glyphInterval,
|
||||||
|
|
@ -143,10 +157,12 @@ static inline void draw_text(
|
||||||
|
|
||||||
uint page = 0;
|
uint page = 0;
|
||||||
uint next = MAX_CODEPAGES;
|
uint next = MAX_CODEPAGES;
|
||||||
int x = 0;
|
float x = 0;
|
||||||
int y = 0;
|
int y = 0;
|
||||||
bool hasLines = false;
|
bool hasLines = false;
|
||||||
|
|
||||||
|
float baseAdvance = glm::length(right);
|
||||||
|
|
||||||
do {
|
do {
|
||||||
for (size_t i = 0; i < text.length(); i++) {
|
for (size_t i = 0; i < text.length(); i++) {
|
||||||
uint c = text[i];
|
uint c = text[i];
|
||||||
|
|
@ -162,8 +178,10 @@ static inline void draw_text(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
int yOffset = 0;
|
int yOffset = 0;
|
||||||
|
float advance = baseAdvance;
|
||||||
if (auto glyph = font.getGlyph(c)) {
|
if (auto glyph = font.getGlyph(c)) {
|
||||||
yOffset = glyph->yOffset;
|
yOffset = glyph->yOffset;
|
||||||
|
advance = glyph->xAdvance;
|
||||||
}
|
}
|
||||||
uint charpage = c >> 8;
|
uint charpage = c >> 8;
|
||||||
if (charpage == page){
|
if (charpage == page){
|
||||||
|
|
@ -175,7 +193,7 @@ static inline void draw_text(
|
||||||
else if (charpage > page && charpage < next){
|
else if (charpage > page && charpage < next){
|
||||||
next = charpage;
|
next = charpage;
|
||||||
}
|
}
|
||||||
x++;
|
x += advance / baseAdvance;
|
||||||
}
|
}
|
||||||
page = next;
|
page = next;
|
||||||
next = MAX_CODEPAGES;
|
next = MAX_CODEPAGES;
|
||||||
|
|
@ -263,6 +281,10 @@ std::unique_ptr<Font> Font::createBitmapFont(
|
||||||
int res = pages.at(0)->getHeight() / 16;
|
int res = pages.at(0)->getHeight() / 16;
|
||||||
std::vector<std::unique_ptr<Texture>> textures;
|
std::vector<std::unique_ptr<Texture>> textures;
|
||||||
std::vector<Glyph> glyphs(textures.size() * 256);
|
std::vector<Glyph> glyphs(textures.size() * 256);
|
||||||
|
for (auto& glyph : glyphs) {
|
||||||
|
glyph.yOffset = 0;
|
||||||
|
glyph.xAdvance = res / 2;
|
||||||
|
}
|
||||||
for (auto& page : pages) {
|
for (auto& page : pages) {
|
||||||
if (page == nullptr) {
|
if (page == nullptr) {
|
||||||
textures.emplace_back(nullptr);
|
textures.emplace_back(nullptr);
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ public:
|
||||||
const Texture* getPage(int page) const;
|
const Texture* getPage(int page) const;
|
||||||
|
|
||||||
FontMetrics getMetrics() const {
|
FontMetrics getMetrics() const {
|
||||||
return {lineHeight, yoffset, glyphInterval};
|
return {std::nullopt, lineHeight, yoffset, glyphInterval};
|
||||||
}
|
}
|
||||||
|
|
||||||
const Glyph* getGlyph(int codepoint) const {
|
const Glyph* getGlyph(int codepoint) const {
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
class Font;
|
||||||
|
|
||||||
struct FontMetrics {
|
struct FontMetrics {
|
||||||
|
std::optional<std::weak_ptr<Font>> font;
|
||||||
|
|
||||||
int lineHeight;
|
int lineHeight;
|
||||||
int yoffset;
|
int yoffset;
|
||||||
int _glyphInterval = 8;
|
int _glyphInterval = 8;
|
||||||
|
|
||||||
|
~FontMetrics();
|
||||||
|
|
||||||
int calcWidth(std::wstring_view text, size_t offset=0, size_t length=-1) const;
|
int calcWidth(std::wstring_view text, size_t offset=0, size_t length=-1) const;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
5
src/graphics/core/FontMetrics.cpp
Normal file
5
src/graphics/core/FontMetrics.cpp
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#include "FontMetics.hpp"
|
||||||
|
|
||||||
|
#include "Font.hpp"
|
||||||
|
|
||||||
|
FontMetrics::~FontMetrics() = default;
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
|
|
||||||
using namespace gui;
|
using namespace gui;
|
||||||
|
|
||||||
void LabelCache::prepare(std::ptrdiff_t fontId, FontMetrics metrics, size_t wrapWidth) {
|
void LabelCache::prepare(const std::shared_ptr<Font>& font, FontMetrics metrics, size_t wrapWidth) {
|
||||||
if (fontId != this->fontId) {
|
if (!this->metrics.font.has_value() || font.get() != this->metrics.font.value().lock().get()) {
|
||||||
resetFlag = true;
|
resetFlag = true;
|
||||||
this->fontId = fontId;
|
|
||||||
this->metrics = metrics;
|
this->metrics = metrics;
|
||||||
|
this->metrics.font = font;
|
||||||
}
|
}
|
||||||
if (wrapWidth != this->wrapWidth) {
|
if (wrapWidth != this->wrapWidth) {
|
||||||
resetFlag = true;
|
resetFlag = true;
|
||||||
|
|
@ -42,7 +42,8 @@ void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) {
|
||||||
lines.clear();
|
lines.clear();
|
||||||
lines.push_back(LineScheme {0, false});
|
lines.push_back(LineScheme {0, false});
|
||||||
|
|
||||||
if (fontId == 0) {
|
auto font = metrics.font.has_value() ? metrics.font.value().lock() : nullptr;
|
||||||
|
if (font == nullptr) {
|
||||||
wrap = false;
|
wrap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,26 +62,27 @@ void LabelCache::update(std::wstring_view text, bool multiline, bool wrap) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (fontId != 0) {
|
if (font == nullptr) {
|
||||||
int maxWidth = 0;
|
return;
|
||||||
for (int i = 0; i < lines.size() - 1; i++) {
|
}
|
||||||
const auto& next = lines[i + 1];
|
int maxWidth = 0;
|
||||||
const auto& cur = lines[i];
|
for (int i = 0; i < lines.size() - 1; i++) {
|
||||||
maxWidth = std::max(
|
const auto& next = lines[i + 1];
|
||||||
metrics.calcWidth(
|
const auto& cur = lines[i];
|
||||||
text.substr(cur.offset, next.offset - cur.offset)
|
|
||||||
),
|
|
||||||
maxWidth
|
|
||||||
);
|
|
||||||
}
|
|
||||||
maxWidth = std::max(
|
maxWidth = std::max(
|
||||||
metrics.calcWidth(
|
metrics.calcWidth(
|
||||||
text.substr(lines[lines.size() - 1].offset)
|
text.substr(cur.offset, next.offset - cur.offset)
|
||||||
),
|
),
|
||||||
maxWidth
|
maxWidth
|
||||||
);
|
);
|
||||||
multilineWidth = maxWidth;
|
|
||||||
}
|
}
|
||||||
|
maxWidth = std::max(
|
||||||
|
metrics.calcWidth(
|
||||||
|
text.substr(lines[lines.size() - 1].offset)
|
||||||
|
),
|
||||||
|
maxWidth
|
||||||
|
);
|
||||||
|
multilineWidth = maxWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +138,7 @@ void Label::setText(std::wstring text) {
|
||||||
this->text = std::move(text);
|
this->text = std::move(text);
|
||||||
cache.update(this->text, multiline, textWrap);
|
cache.update(this->text, multiline, textWrap);
|
||||||
|
|
||||||
if (cache.fontId != 0 && autoresize) {
|
if (cache.metrics.font.has_value() && !cache.metrics.font->expired() && autoresize) {
|
||||||
setSize(calcSize());
|
setSize(calcSize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -203,9 +205,9 @@ uint Label::getLinesNumber() const {
|
||||||
|
|
||||||
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
auto batch = pctx.getBatch2D();
|
auto batch = pctx.getBatch2D();
|
||||||
auto font = assets.get<Font>(fontName);
|
auto font = assets.getShared<Font>(fontName);
|
||||||
cache.prepare(
|
cache.prepare(
|
||||||
reinterpret_cast<ptrdiff_t>(font),
|
font,
|
||||||
font->getMetrics(),
|
font->getMetrics(),
|
||||||
static_cast<size_t>(glm::abs(getSize().x))
|
static_cast<size_t>(glm::abs(getSize().x))
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ namespace gui {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LabelCache {
|
struct LabelCache {
|
||||||
ptrdiff_t fontId = 0;
|
|
||||||
FontMetrics metrics;
|
FontMetrics metrics;
|
||||||
|
|
||||||
std::vector<LineScheme> lines;
|
std::vector<LineScheme> lines;
|
||||||
|
|
@ -24,7 +23,7 @@ namespace gui {
|
||||||
size_t wrapWidth = -1;
|
size_t wrapWidth = -1;
|
||||||
int multilineWidth = 0;
|
int multilineWidth = 0;
|
||||||
|
|
||||||
void prepare(std::ptrdiff_t fontId, FontMetrics metrics, size_t wrapWidth);
|
void prepare(const std::shared_ptr<Font>& font, FontMetrics metrics, size_t wrapWidth);
|
||||||
void update(std::wstring_view text, bool multiline, bool wrap);
|
void update(std::wstring_view text, bool multiline, bool wrap);
|
||||||
|
|
||||||
size_t getTextLineOffset(size_t line) const;
|
size_t getTextLineOffset(size_t line) const;
|
||||||
|
|
|
||||||
|
|
@ -252,10 +252,12 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
|
|
||||||
float time = gui.getWindow().time();
|
float time = gui.getWindow().time();
|
||||||
|
|
||||||
|
// drawing caret here
|
||||||
if (isFocused() && editable && static_cast<int>((time - caretLastMove) * 2) % 2 == 0) {
|
if (isFocused() && editable && static_cast<int>((time - caretLastMove) * 2) % 2 == 0) {
|
||||||
uint line = label->getLineByTextIndex(caret);
|
uint line = label->getLineByTextIndex(caret);
|
||||||
|
auto linestart = label->getTextLineOffset(line);
|
||||||
uint lcaret = caret - label->getTextLineOffset(line);
|
uint lcaret = caret - label->getTextLineOffset(line);
|
||||||
int width = rawTextCache.metrics.calcWidth(input, 0, lcaret);
|
int width = rawTextCache.metrics.calcWidth(input.substr(linestart), 0, lcaret);
|
||||||
|
|
||||||
batch->rect(
|
batch->rect(
|
||||||
lcoord.x + width,
|
lcoord.x + width,
|
||||||
|
|
@ -264,6 +266,7 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
lineHeight
|
lineHeight
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// drawing selection
|
||||||
if (selectionStart != selectionEnd) {
|
if (selectionStart != selectionEnd) {
|
||||||
auto selectionCtx = subctx.sub(batch);
|
auto selectionCtx = subctx.sub(batch);
|
||||||
selectionCtx.setBlendMode(BlendMode::addition);
|
selectionCtx.setBlendMode(BlendMode::addition);
|
||||||
|
|
@ -349,12 +352,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
|
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
|
||||||
auto font = assets.get<Font>(label->getFontName());
|
auto font = assets.getShared<Font>(label->getFontName());
|
||||||
rawTextCache.prepare(
|
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
|
||||||
reinterpret_cast<ptrdiff_t>(font),
|
|
||||||
font->getMetrics(),
|
|
||||||
label->getSize().x
|
|
||||||
);
|
|
||||||
|
|
||||||
glm::vec2 pos = calcPos();
|
glm::vec2 pos = calcPos();
|
||||||
|
|
||||||
|
|
@ -384,8 +383,11 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextBox::refreshLabel() {
|
void TextBox::refreshLabel() {
|
||||||
|
if (!rawTextCache.metrics.font.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
rawTextCache.prepare(
|
rawTextCache.prepare(
|
||||||
rawTextCache.fontId,
|
rawTextCache.metrics.font.value().lock(),
|
||||||
rawTextCache.metrics,
|
rawTextCache.metrics,
|
||||||
static_cast<size_t>(getSize().x)
|
static_cast<size_t>(getSize().x)
|
||||||
);
|
);
|
||||||
|
|
@ -425,7 +427,7 @@ void TextBox::refreshLabel() {
|
||||||
lineNumbersLabel->setColor(glm::vec4(1, 1, 1, 0.25f));
|
lineNumbersLabel->setColor(glm::vec4(1, 1, 1, 0.25f));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoresize && rawTextCache.fontId) {
|
if (autoresize) {
|
||||||
auto size = getSize();
|
auto size = getSize();
|
||||||
int newy = glm::min(
|
int newy = glm::min(
|
||||||
static_cast<int>(parent->getSize().y),
|
static_cast<int>(parent->getSize().y),
|
||||||
|
|
@ -443,7 +445,7 @@ void TextBox::refreshLabel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (multiline && rawTextCache.fontId) {
|
if (multiline) {
|
||||||
setScrollable(true);
|
setScrollable(true);
|
||||||
uint height = label->getLinesNumber() * rawTextCache.metrics.lineHeight *
|
uint height = label->getLinesNumber() * rawTextCache.metrics.lineHeight *
|
||||||
label->getLineInterval();
|
label->getLineInterval();
|
||||||
|
|
@ -665,7 +667,7 @@ size_t TextBox::normalizeIndex(int index) {
|
||||||
/// @param y screen Y position
|
/// @param y screen Y position
|
||||||
/// @return non-normalized character index
|
/// @return non-normalized character index
|
||||||
int TextBox::calcIndexAt(int x, int y) const {
|
int TextBox::calcIndexAt(int x, int y) const {
|
||||||
if (rawTextCache.fontId == 0) return 0;
|
if (!rawTextCache.metrics.font.has_value()) return 0;
|
||||||
const auto& labelText = label->getText();
|
const auto& labelText = label->getText();
|
||||||
glm::vec2 lcoord = label->calcPos();
|
glm::vec2 lcoord = label->calcPos();
|
||||||
uint line = label->getLineByYOffset(y - lcoord.y);
|
uint line = label->getLineByYOffset(y - lcoord.y);
|
||||||
|
|
@ -682,7 +684,7 @@ int TextBox::calcIndexAt(int x, int y) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
int TextBox::getLineYOffset(int line) const {
|
int TextBox::getLineYOffset(int line) const {
|
||||||
if (rawTextCache.fontId == 0) return 0;
|
if (!rawTextCache.metrics.font.has_value()) return 0;
|
||||||
return label->getLineYOffset(line);
|
return label->getLineYOffset(line);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1205,12 +1207,16 @@ size_t TextBox::getCaret() const {
|
||||||
void TextBox::setCaret(size_t position) {
|
void TextBox::setCaret(size_t position) {
|
||||||
const auto& labelText = label->getText();
|
const auto& labelText = label->getText();
|
||||||
caret = std::min(static_cast<size_t>(position), input.length());
|
caret = std::min(static_cast<size_t>(position), input.length());
|
||||||
if (rawTextCache.fontId == 0) {
|
|
||||||
|
auto font = rawTextCache.metrics.font.has_value()
|
||||||
|
? rawTextCache.metrics.font->lock()
|
||||||
|
: nullptr;
|
||||||
|
if (font == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int width = label->getSize().x;
|
int width = label->getSize().x;
|
||||||
|
|
||||||
rawTextCache.prepare(rawTextCache.fontId, rawTextCache.metrics, width);
|
rawTextCache.prepare(font, rawTextCache.metrics, width);
|
||||||
rawTextCache.update(input, multiline, label->isTextWrapping());
|
rawTextCache.update(input, multiline, label->isTextWrapping());
|
||||||
|
|
||||||
caretLastMove = gui.getWindow().time();
|
caretLastMove = gui.getWindow().time();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue