@@ -31,11 +31,11 @@
|
|||||||
#include <ft2build.h>
|
#include <ft2build.h>
|
||||||
#include FT_FREETYPE_H
|
#include FT_FREETYPE_H
|
||||||
#include FT_GLYPH_H
|
#include FT_GLYPH_H
|
||||||
#include <hb.h>
|
|
||||||
#include <hb-ft.h>
|
#include <hb-ft.h>
|
||||||
|
#include <hb.h>
|
||||||
|
|
||||||
#include <msdfgen.h>
|
|
||||||
#include <ext/import-font.h>
|
#include <ext/import-font.h>
|
||||||
|
#include <msdfgen.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -81,14 +81,12 @@ auto TextRenderer::flush_font(FontRuntime &rt, FontData &fd) -> void
|
|||||||
UpdateTexture(fd.atlas, fd.atlas_img.data);
|
UpdateTexture(fd.atlas, fd.atlas_img.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TextRenderer::allocate_region(
|
auto TextRenderer::allocate_region(FontRuntime &rt, FontData &fd, int width,
|
||||||
FontRuntime &rt, FontData &fd, int width, int height)
|
int height) -> std::optional<std::pair<int, int>>
|
||||||
-> std::optional<std::pair<int, int>>
|
|
||||||
{
|
{
|
||||||
(void)fd;
|
(void)fd;
|
||||||
int padded_w = width + kAtlasPadding;
|
int padded_w = width + kAtlasPadding;
|
||||||
if (padded_w > rt.atlas_width
|
if (padded_w > rt.atlas_width || height + kAtlasPadding > rt.atlas_height)
|
||||||
|| height + kAtlasPadding > rt.atlas_height)
|
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
if (rt.pen_x + padded_w > rt.atlas_width) {
|
if (rt.pen_x + padded_w > rt.atlas_width) {
|
||||||
rt.pen_x = kAtlasPadding;
|
rt.pen_x = kAtlasPadding;
|
||||||
@@ -108,7 +106,7 @@ auto TextRenderer::upload_region(FontData &fd, int dst_x, int dst_y, int width,
|
|||||||
int height, std::vector<Color> const &buffer) -> void
|
int height, std::vector<Color> const &buffer) -> void
|
||||||
{
|
{
|
||||||
Rectangle rec { static_cast<float>(dst_x), static_cast<float>(dst_y),
|
Rectangle rec { static_cast<float>(dst_x), static_cast<float>(dst_y),
|
||||||
static_cast<float>(width), static_cast<float>(height) };
|
static_cast<float>(width), static_cast<float>(height) };
|
||||||
if (fd.atlas.id != 0)
|
if (fd.atlas.id != 0)
|
||||||
UpdateTextureRec(fd.atlas, rec, buffer.data());
|
UpdateTextureRec(fd.atlas, rec, buffer.data());
|
||||||
if (!fd.atlas_img.data)
|
if (!fd.atlas_img.data)
|
||||||
@@ -129,7 +127,7 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
msdfgen::GlyphIndex const index(glyph_index);
|
msdfgen::GlyphIndex const index(glyph_index);
|
||||||
if (!rt.msdf_font
|
if (!rt.msdf_font
|
||||||
|| !msdfgen::loadGlyph(shape, rt.msdf_font, index,
|
|| !msdfgen::loadGlyph(shape, rt.msdf_font, index,
|
||||||
msdfgen::FONT_SCALING_EM_NORMALIZED, &advance_em))
|
msdfgen::FONT_SCALING_EM_NORMALIZED, &advance_em))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
shape.normalize();
|
shape.normalize();
|
||||||
msdfgen::edgeColoringSimple(shape, 3.0);
|
msdfgen::edgeColoringSimple(shape, 3.0);
|
||||||
@@ -137,16 +135,15 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
float const width_em = static_cast<float>(bounds.r - bounds.l);
|
float const width_em = static_cast<float>(bounds.r - bounds.l);
|
||||||
float const height_em = static_cast<float>(bounds.t - bounds.b);
|
float const height_em = static_cast<float>(bounds.t - bounds.b);
|
||||||
double const scale = rt.em_scale;
|
double const scale = rt.em_scale;
|
||||||
int bmp_w = std::max(1, static_cast<int>(std::ceil(
|
int bmp_w = std::max(
|
||||||
width_em * scale + 2.0 * rt.px_range)));
|
1, static_cast<int>(std::ceil(width_em * scale + 2.0 * rt.px_range)));
|
||||||
int bmp_h = std::max(1, static_cast<int>(std::ceil(
|
int bmp_h = std::max(
|
||||||
height_em * scale + 2.0 * rt.px_range)));
|
1, static_cast<int>(std::ceil(height_em * scale + 2.0 * rt.px_range)));
|
||||||
|
|
||||||
if (bmp_w + kAtlasPadding > rt.atlas_width
|
if (bmp_w + kAtlasPadding > rt.atlas_width
|
||||||
|| bmp_h + kAtlasPadding > rt.atlas_height) {
|
|| bmp_h + kAtlasPadding > rt.atlas_height) {
|
||||||
TraceLog(LOG_WARNING,
|
TraceLog(LOG_WARNING, "Glyph %u bitmap %dx%d exceeds atlas %dx%d",
|
||||||
"Glyph %u bitmap %dx%d exceeds atlas %dx%d", glyph_index, bmp_w,
|
glyph_index, bmp_w, bmp_h, rt.atlas_width, rt.atlas_height);
|
||||||
bmp_h, rt.atlas_width, rt.atlas_height);
|
|
||||||
GlyphCacheEntry too_large {};
|
GlyphCacheEntry too_large {};
|
||||||
too_large.width = 0;
|
too_large.width = 0;
|
||||||
too_large.height = 0;
|
too_large.height = 0;
|
||||||
@@ -165,21 +162,24 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
msdfgen::Bitmap<float, 3> msdf_bitmap(bmp_w, bmp_h);
|
msdfgen::Bitmap<float, 3> msdf_bitmap(bmp_w, bmp_h);
|
||||||
msdfgen::Vector2 scale_vec(scale, scale);
|
msdfgen::Vector2 scale_vec(scale, scale);
|
||||||
double const inv_scale = 1.0 / scale;
|
double const inv_scale = 1.0 / scale;
|
||||||
msdfgen::Vector2 translate(
|
msdfgen::Vector2 translate(-bounds.l + rt.px_range * inv_scale,
|
||||||
-bounds.l + rt.px_range * inv_scale,
|
|
||||||
-bounds.b + rt.px_range * inv_scale);
|
-bounds.b + rt.px_range * inv_scale);
|
||||||
msdfgen::generateMSDF(msdf_bitmap, shape, rt.px_range, scale_vec, translate);
|
msdfgen::generateMSDF(
|
||||||
|
msdf_bitmap, shape, rt.px_range, scale_vec, translate);
|
||||||
|
|
||||||
std::vector<Color> buffer(static_cast<size_t>(bmp_w) * bmp_h);
|
std::vector<Color> buffer(static_cast<size_t>(bmp_w) * bmp_h);
|
||||||
for (int y = 0; y < bmp_h; ++y) {
|
for (int y = 0; y < bmp_h; ++y) {
|
||||||
|
int const dst_y = bmp_h - 1 - y;
|
||||||
for (int x = 0; x < bmp_w; ++x) {
|
for (int x = 0; x < bmp_w; ++x) {
|
||||||
float const *px = msdf_bitmap(x, y);
|
float const *px = msdf_bitmap(x, y);
|
||||||
auto const clamp = [](float v) {
|
auto const clamp = [](float v) {
|
||||||
|
printf("%.2f ", v);
|
||||||
return static_cast<unsigned char>(
|
return static_cast<unsigned char>(
|
||||||
std::lround(std::clamp(v, 0.0f, 1.0f) * 255.0f));
|
std::lround(std::clamp(v, 0.0f, 1.0f) * 255.0f));
|
||||||
};
|
};
|
||||||
buffer[static_cast<size_t>(y) * bmp_w + x]
|
buffer[static_cast<size_t>(dst_y) * bmp_w + x]
|
||||||
= Color { clamp(px[0]), clamp(px[1]), clamp(px[2]), 255 };
|
= Color { clamp(px[0]), clamp(px[1]), clamp(px[2]), 255 };
|
||||||
|
printf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,11 +205,11 @@ auto TextRenderer::generate_glyph(FontRuntime &rt, FontData &fd,
|
|||||||
|
|
||||||
auto const gen_end = std::chrono::steady_clock::now();
|
auto const gen_end = std::chrono::steady_clock::now();
|
||||||
auto const gen_ms
|
auto const gen_ms
|
||||||
= std::chrono::duration<double, std::milli>(gen_end - gen_start).count();
|
= std::chrono::duration<double, std::milli>(gen_end - gen_start)
|
||||||
|
.count();
|
||||||
if (gen_ms > 2.0)
|
if (gen_ms > 2.0)
|
||||||
TraceLog(LOG_INFO,
|
TraceLog(LOG_INFO, "Generated glyph %u in %.2f ms (%dx%d texels)",
|
||||||
"Generated glyph %u in %.2f ms (%dx%d texels)", glyph_index, gen_ms,
|
glyph_index, gen_ms, entry.width, entry.height);
|
||||||
entry.width, entry.height);
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +229,8 @@ auto TextRenderer::ensure_glyph(FontRuntime &rt, FontData &fd, u32 glyph_index,
|
|||||||
= rt.glyph_cache.emplace(glyph_index, std::move(*entry));
|
= rt.glyph_cache.emplace(glyph_index, std::move(*entry));
|
||||||
if (!ok)
|
if (!ok)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
inserted_it->second.stamp = mark_usage ? rt.frame_stamp : inserted_it->second.stamp;
|
inserted_it->second.stamp
|
||||||
|
= mark_usage ? rt.frame_stamp : inserted_it->second.stamp;
|
||||||
fd.glyphs[glyph_index] = inserted_it->second.glyph;
|
fd.glyphs[glyph_index] = inserted_it->second.glyph;
|
||||||
return &inserted_it->second;
|
return &inserted_it->second;
|
||||||
}
|
}
|
||||||
@@ -289,9 +290,12 @@ auto TextRenderer::measure_text(FontHandle const font,
|
|||||||
auto *entry = ensure_glyph(rt, fd, glyph_index, false);
|
auto *entry = ensure_glyph(rt, fd, glyph_index, false);
|
||||||
if (!entry || entry->width == 0 || entry->height == 0)
|
if (!entry || entry->width == 0 || entry->height == 0)
|
||||||
continue;
|
continue;
|
||||||
float const x_offset_em = hb_to_em(positions[i].x_offset, rt.units_per_em);
|
float const x_offset_em
|
||||||
float const left = advance_em + x_offset_em + entry->glyph.plane_bounds.left;
|
= hb_to_em(positions[i].x_offset, rt.units_per_em);
|
||||||
float const right = advance_em + x_offset_em + entry->glyph.plane_bounds.right;
|
float const left
|
||||||
|
= advance_em + x_offset_em + entry->glyph.plane_bounds.left;
|
||||||
|
float const right
|
||||||
|
= advance_em + x_offset_em + entry->glyph.plane_bounds.right;
|
||||||
if (first) {
|
if (first) {
|
||||||
min_x_em = left;
|
min_x_em = left;
|
||||||
max_x_em = right;
|
max_x_em = right;
|
||||||
@@ -306,12 +310,13 @@ auto TextRenderer::measure_text(FontHandle const font,
|
|||||||
hb_buffer_destroy(buffer);
|
hb_buffer_destroy(buffer);
|
||||||
|
|
||||||
if (first)
|
if (first)
|
||||||
return Vector2 { 0.0f, (rt.ascent - rt.descent) * static_cast<float>(size) };
|
return Vector2 { 0.0f,
|
||||||
|
(rt.ascent - rt.descent) * static_cast<float>(size) };
|
||||||
|
|
||||||
float width_em = std::max(max_x_em, advance_em) - min_x_em;
|
float width_em = std::max(max_x_em, advance_em) - min_x_em;
|
||||||
float height_em = rt.ascent - rt.descent;
|
float height_em = rt.ascent - rt.descent;
|
||||||
return Vector2 { width_em * static_cast<float>(size),
|
return Vector2 { width_em * static_cast<float>(size),
|
||||||
height_em * static_cast<float>(size) };
|
height_em * static_cast<float>(size) };
|
||||||
}
|
}
|
||||||
|
|
||||||
auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
||||||
@@ -343,12 +348,12 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
|||||||
rt.frame_stamp++;
|
rt.frame_stamp++;
|
||||||
|
|
||||||
// BeginShaderMode(m_msdf_shader);
|
// BeginShaderMode(m_msdf_shader);
|
||||||
// if (m_px_range_uniform >= 0) {
|
if (m_px_range_uniform >= 0) {
|
||||||
// float shader_px_range = rt.px_range;
|
float shader_px_range = rt.px_range;
|
||||||
// SetShaderValue(
|
// SetShaderValue(
|
||||||
// m_msdf_shader, m_px_range_uniform, &shader_px_range,
|
// m_msdf_shader, m_px_range_uniform, &shader_px_range,
|
||||||
// SHADER_UNIFORM_FLOAT);
|
// SHADER_UNIFORM_FLOAT);
|
||||||
// }
|
}
|
||||||
|
|
||||||
for (unsigned i = 0; i < length; ++i) {
|
for (unsigned i = 0; i < length; ++i) {
|
||||||
u32 glyph_index = infos[i].codepoint;
|
u32 glyph_index = infos[i].codepoint;
|
||||||
@@ -359,9 +364,12 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
|||||||
continue;
|
continue;
|
||||||
if (entry->width == 0 || entry->height == 0)
|
if (entry->width == 0 || entry->height == 0)
|
||||||
continue;
|
continue;
|
||||||
float const advance_em = hb_to_em(positions[i].x_advance, rt.units_per_em);
|
float const advance_em
|
||||||
float const x_offset_em = hb_to_em(positions[i].x_offset, rt.units_per_em);
|
= hb_to_em(positions[i].x_advance, rt.units_per_em);
|
||||||
float const y_offset_em = hb_to_em(positions[i].y_offset, rt.units_per_em);
|
float const x_offset_em
|
||||||
|
= hb_to_em(positions[i].x_offset, rt.units_per_em);
|
||||||
|
float const y_offset_em
|
||||||
|
= hb_to_em(positions[i].y_offset, rt.units_per_em);
|
||||||
float const x_base_em = pen_x_em + x_offset_em;
|
float const x_base_em = pen_x_em + x_offset_em;
|
||||||
float const y_base_em = pen_y_em + y_offset_em;
|
float const y_base_em = pen_y_em + y_offset_em;
|
||||||
float const scale_px = size_f / static_cast<float>(rt.em_scale);
|
float const scale_px = size_f / static_cast<float>(rt.em_scale);
|
||||||
@@ -374,14 +382,14 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
|||||||
float const dest_h = static_cast<float>(entry->height) * scale_px;
|
float const dest_h = static_cast<float>(entry->height) * scale_px;
|
||||||
|
|
||||||
Rectangle source {
|
Rectangle source {
|
||||||
entry->glyph.glyph_bounds.left,
|
entry->glyph.glyph_bounds.left,
|
||||||
entry->glyph.glyph_bounds.top,
|
entry->glyph.glyph_bounds.top,
|
||||||
static_cast<float>(entry->width),
|
static_cast<float>(entry->width),
|
||||||
static_cast<float>(entry->height),
|
static_cast<float>(entry->height),
|
||||||
};
|
};
|
||||||
Rectangle dest { dest_x, dest_y, dest_w, dest_h };
|
Rectangle dest { dest_x, dest_y, dest_w, dest_h };
|
||||||
DrawTexturePro(fd.atlas, source, dest, Vector2 { 0.0f, 0.0f }, 0.0f,
|
DrawTexturePro(
|
||||||
color);
|
fd.atlas, source, dest, Vector2 { 0.0f, 0.0f }, 0.0f, color);
|
||||||
|
|
||||||
pen_x_em += advance_em;
|
pen_x_em += advance_em;
|
||||||
pen_y_em += hb_to_em(positions[i].y_advance, rt.units_per_em);
|
pen_y_em += hb_to_em(positions[i].y_advance, rt.units_per_em);
|
||||||
@@ -392,7 +400,8 @@ auto TextRenderer::draw_text(FontHandle const font, std::string_view const text,
|
|||||||
|
|
||||||
auto const draw_end = std::chrono::steady_clock::now();
|
auto const draw_end = std::chrono::steady_clock::now();
|
||||||
auto const draw_ms
|
auto const draw_ms
|
||||||
= std::chrono::duration<double, std::milli>(draw_end - draw_start).count();
|
= std::chrono::duration<double, std::milli>(draw_end - draw_start)
|
||||||
|
.count();
|
||||||
if (draw_ms > 5.0)
|
if (draw_ms > 5.0)
|
||||||
TraceLog(LOG_INFO, "draw_text took %.2f ms for %zu glyphs", draw_ms,
|
TraceLog(LOG_INFO, "draw_text took %.2f ms for %zu glyphs", draw_ms,
|
||||||
static_cast<size_t>(length));
|
static_cast<size_t>(length));
|
||||||
@@ -439,7 +448,8 @@ auto TextRenderer::load_font(std::filesystem::path const &path)
|
|||||||
FT_Done_Face(face);
|
FT_Done_Face(face);
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
hb_font_set_scale(runtime->hb_font, static_cast<int>(runtime->units_per_em) << 6,
|
hb_font_set_scale(runtime->hb_font,
|
||||||
|
static_cast<int>(runtime->units_per_em) << 6,
|
||||||
static_cast<int>(runtime->units_per_em) << 6);
|
static_cast<int>(runtime->units_per_em) << 6);
|
||||||
hb_ft_font_set_funcs(runtime->hb_font);
|
hb_ft_font_set_funcs(runtime->hb_font);
|
||||||
|
|
||||||
|
|||||||
BIN
tools/dump_msdf
BIN
tools/dump_msdf
Binary file not shown.
@@ -1,85 +0,0 @@
|
|||||||
#include <cstdio>
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cmath>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <msdfgen.h>
|
|
||||||
#include <ext/import-font.h>
|
|
||||||
|
|
||||||
static unsigned char clamp_channel(float v) {
|
|
||||||
if (v <= 0.0f) return 0;
|
|
||||||
if (v >= 1.0f) return 255;
|
|
||||||
return static_cast<unsigned char>(std::lround(v * 255.0f));
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
if (argc < 3) {
|
|
||||||
std::fprintf(stderr, "Usage: %s <font-file> <codepoint>\n", argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
const char *fontPath = argv[1];
|
|
||||||
unsigned codepoint = std::strtoul(argv[2], nullptr, 0);
|
|
||||||
auto *ft = msdfgen::initializeFreetype();
|
|
||||||
if (!ft) return 1;
|
|
||||||
auto *font = msdfgen::loadFont(ft, fontPath);
|
|
||||||
if (!font) return 1;
|
|
||||||
msdfgen::Shape shape;
|
|
||||||
double advance = 0.0;
|
|
||||||
if (!msdfgen::loadGlyph(shape, font, msdfgen::GlyphIndex(codepoint), msdfgen::FONT_SCALING_EM_NORMALIZED, &advance)) {
|
|
||||||
std::fprintf(stderr, "Failed to load glyph %u\n", codepoint);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
shape.normalize();
|
|
||||||
msdfgen::edgeColoringSimple(shape, 3.0);
|
|
||||||
auto bounds = shape.getBounds();
|
|
||||||
|
|
||||||
double emScale = 48.0;
|
|
||||||
double pxRange = 4.0;
|
|
||||||
int bmp_w = std::max(1, (int)std::ceil((bounds.r - bounds.l) * emScale + 2.0 * pxRange));
|
|
||||||
int bmp_h = std::max(1, (int)std::ceil((bounds.t - bounds.b) * emScale + 2.0 * pxRange));
|
|
||||||
|
|
||||||
msdfgen::Bitmap<float, 3> bitmap(bmp_w, bmp_h);
|
|
||||||
msdfgen::Vector2 scale(emScale, emScale);
|
|
||||||
msdfgen::Vector2 translate(-bounds.l * emScale + pxRange, -bounds.b * emScale + pxRange);
|
|
||||||
msdfgen::generateMSDF(bitmap, shape, pxRange, scale, translate);
|
|
||||||
|
|
||||||
std::vector<unsigned char> buffer(static_cast<size_t>(bmp_w) * bmp_h * 4);
|
|
||||||
for (int y = 0; y < bmp_h; ++y) {
|
|
||||||
int dst_y = bmp_h - 1 - y;
|
|
||||||
for (int x = 0; x < bmp_w; ++x) {
|
|
||||||
const float *px = bitmap(x, y);
|
|
||||||
size_t idx = (static_cast<size_t>(dst_y) * bmp_w + x) * 4;
|
|
||||||
buffer[idx + 0] = clamp_channel(px[0]);
|
|
||||||
buffer[idx + 1] = clamp_channel(px[1]);
|
|
||||||
buffer[idx + 2] = clamp_channel(px[2]);
|
|
||||||
buffer[idx + 3] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int minX = bmp_w, minY = bmp_h, maxX = -1, maxY = -1;
|
|
||||||
for (int y = 0; y < bmp_h; ++y) {
|
|
||||||
for (int x = 0; x < bmp_w; ++x) {
|
|
||||||
size_t idx = (static_cast<size_t>(y) * bmp_w + x) * 4;
|
|
||||||
unsigned char r = buffer[idx];
|
|
||||||
unsigned char g = buffer[idx + 1];
|
|
||||||
unsigned char b = buffer[idx + 2];
|
|
||||||
bool interesting = !(r == 0 && g == 0 && b == 0) && !(r == 255 && g == 255 && b == 255);
|
|
||||||
if (interesting) {
|
|
||||||
if (x < minX) minX = x;
|
|
||||||
if (x > maxX) maxX = x;
|
|
||||||
if (y < minY) minY = y;
|
|
||||||
if (y > maxY) maxY = y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::printf("bmp %dx%d\n", bmp_w, bmp_h);
|
|
||||||
if (maxX >= minX && maxY >= minY) {
|
|
||||||
std::printf("interesting bbox: x=[%d,%d] y=[%d,%d]\n", minX, maxX, minY, maxY);
|
|
||||||
}
|
|
||||||
|
|
||||||
msdfgen::destroyFont(font);
|
|
||||||
msdfgen::deinitializeFreetype(ft);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user