2022-08-28 09:40:27 -04:00
|
|
|
#include "Graphics.h"
|
2022-08-18 12:17:43 -04:00
|
|
|
#include <ft2build.h>
|
|
|
|
#include FT_FREETYPE_H
|
2022-05-09 01:50:09 -04:00
|
|
|
|
2022-08-20 22:12:11 -04:00
|
|
|
#include <bx/math.h>
|
|
|
|
|
2022-05-09 01:50:09 -04:00
|
|
|
#include <iostream>
|
2022-08-27 03:04:39 -04:00
|
|
|
#include <optional>
|
2023-04-06 19:56:40 -04:00
|
|
|
#include <algorithm>
|
2022-05-09 01:50:09 -04:00
|
|
|
|
2022-08-22 02:15:25 -04:00
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
|
|
#include <stb_image.h>
|
|
|
|
|
2022-05-09 01:50:09 -04:00
|
|
|
namespace {
|
2022-08-27 03:04:39 -04:00
|
|
|
using namespace ADVect::Graphics;
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
bgfx::ShaderHandle loadShader(const std::string& FILENAME)
|
2022-08-20 22:12:11 -04:00
|
|
|
{
|
|
|
|
std::string shaderPath{};
|
|
|
|
|
|
|
|
switch (bgfx::getRendererType()) {
|
|
|
|
case bgfx::RendererType::Noop:
|
|
|
|
case bgfx::RendererType::Direct3D9: shaderPath = "shaders/dx9/"; break;
|
|
|
|
case bgfx::RendererType::Direct3D11:
|
|
|
|
case bgfx::RendererType::Direct3D12: shaderPath = "shaders/dx11/"; break;
|
|
|
|
case bgfx::RendererType::Gnm: shaderPath = "shaders/pssl/"; break;
|
|
|
|
case bgfx::RendererType::Metal: shaderPath = "shaders/metal/"; break;
|
|
|
|
case bgfx::RendererType::OpenGL: shaderPath = "shaders/glsl/"; break;
|
|
|
|
case bgfx::RendererType::OpenGLES: shaderPath = "shaders/essl/"; break;
|
|
|
|
case bgfx::RendererType::Vulkan: shaderPath = "shaders/spirv/"; break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
std::string filePath = shaderPath + FILENAME;
|
|
|
|
|
|
|
|
FILE* file = fopen(filePath.c_str(), "rb");
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
|
|
long fileSize = ftell(file);
|
|
|
|
fseek(file, 0, SEEK_SET);
|
|
|
|
|
|
|
|
const bgfx::Memory* mem = bgfx::alloc(fileSize + 1);
|
|
|
|
fread(mem->data, 1, fileSize, file);
|
|
|
|
mem->data[mem->size - 1] = '\0';
|
|
|
|
fclose(file);
|
|
|
|
|
|
|
|
return bgfx::createShader(mem);
|
|
|
|
}
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
struct PosUVVertex {
|
|
|
|
f32 x;
|
|
|
|
f32 y;
|
|
|
|
f32 z;
|
|
|
|
f32 u;
|
|
|
|
f32 v;
|
|
|
|
};
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
static PosUVVertex quad_vert[] =
|
|
|
|
{
|
|
|
|
{ 0.f, 0.f, 0.0f, 0.0f, 0.0f },
|
|
|
|
{ 0.f, 1.f, 0.0f, 0.0f, 1.0f },
|
|
|
|
{ 1.f, 0.f, 0.0f, 1.0f, 0.0f },
|
|
|
|
{ 1.f, 1.f, 0.0f, 1.0f, 1.0f }
|
|
|
|
};
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
static const u16 quad_indices[] =
|
2022-08-27 03:04:39 -04:00
|
|
|
{
|
|
|
|
0, 2, 1,
|
|
|
|
1, 2, 3
|
|
|
|
};
|
2022-05-09 01:50:09 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
FT_Library library;
|
|
|
|
FT_Error error;
|
|
|
|
|
|
|
|
struct Font {
|
|
|
|
FT_Face face = nullptr;
|
2023-04-06 19:56:40 -04:00
|
|
|
std::unordered_map<NVL::Char, std::tuple<std::optional<bgfx::TextureHandle>, u32, u32, i32, i32, i32, i32>> cache{};
|
2022-08-27 03:04:39 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
std::tuple<std::optional<bgfx::TextureHandle>, u32, u32, i32, i32, i32, i32>& get_char(NVL::Char c) {
|
|
|
|
if (cache.find(c) == cache.end()) {
|
|
|
|
FT_GlyphSlot slot = face->glyph;
|
2022-08-27 03:04:39 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
error = FT_Load_Char(face, c, FT_LOAD_RENDER);
|
|
|
|
if (error) {
|
|
|
|
std::cerr << "ADV: Failed to load character" << std::endl;
|
|
|
|
}
|
2022-08-27 03:04:39 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
if (slot->bitmap.width != 0) {
|
|
|
|
const bgfx::Memory* buf = bgfx::copy(slot->bitmap.buffer, slot->bitmap.pitch * slot->bitmap.rows);
|
|
|
|
|
|
|
|
cache.emplace(c, std::make_tuple(
|
|
|
|
bgfx::createTexture2D(
|
|
|
|
slot->bitmap.width,
|
|
|
|
slot->bitmap.rows,
|
|
|
|
false,
|
|
|
|
1,
|
|
|
|
bgfx::TextureFormat::A8,
|
|
|
|
BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP,
|
|
|
|
buf
|
|
|
|
),
|
|
|
|
slot->bitmap.width,
|
|
|
|
slot->bitmap.rows,
|
|
|
|
slot->bitmap_left,
|
|
|
|
slot->bitmap_top,
|
|
|
|
slot->advance.x,
|
|
|
|
slot->advance.y
|
|
|
|
));
|
|
|
|
}
|
|
|
|
else cache.emplace(c, std::make_tuple(
|
|
|
|
std::nullopt,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
0,
|
|
|
|
slot->advance.x,
|
|
|
|
slot->advance.y));
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
return cache[c];
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
2022-08-27 03:04:39 -04:00
|
|
|
};
|
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
std::optional<Font> get_font(const char* path) {
|
2022-08-27 03:04:39 -04:00
|
|
|
Font f;
|
2022-05-09 01:50:09 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
error = FT_New_Face(library, path, 0, &f.face);
|
2022-05-09 01:50:09 -04:00
|
|
|
if (error == FT_Err_Unknown_File_Format) {
|
2022-08-22 02:15:25 -04:00
|
|
|
std::cerr << "ADV: FreeType Unknown_File_Format: " << error << std::endl;
|
2022-08-27 03:04:39 -04:00
|
|
|
return std::nullopt;
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
2022-08-18 12:17:43 -04:00
|
|
|
else if (error) {
|
2022-08-22 02:15:25 -04:00
|
|
|
std::cerr << "ADV: FreeType font loading error: " << error << std::endl;
|
2022-08-27 03:04:39 -04:00
|
|
|
return std::nullopt;
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
error = FT_Set_Char_Size(f.face, 0, 20 * 64, 72, 72);
|
|
|
|
if (error) {
|
2022-08-22 02:15:25 -04:00
|
|
|
std::cerr << "ADV: FreeType Set_Char_Size error: " << error << std::endl;
|
2022-08-27 03:04:39 -04:00
|
|
|
return std::nullopt;
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
void destroy_font(Font& f) {
|
|
|
|
FT_Done_Face(f.face);
|
|
|
|
for (auto it = f.cache.begin(); it != f.cache.end(); it++) {
|
2023-04-06 19:56:40 -04:00
|
|
|
if(auto& tx = std::get<0>(it->second))
|
|
|
|
bgfx::destroy(*tx);
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inline bgfx::ProgramHandle load_shader_program(const std::string& shader) {
|
|
|
|
return bgfx::createProgram(loadShader(shader + ".vert.bin"), loadShader(shader + ".frag.bin"), true);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::optional<ImageTexture> get_image_texture(const std::string& file) {
|
|
|
|
ImageTexture i;
|
2022-08-27 04:40:06 -04:00
|
|
|
i.buffer = stbi_load(file.c_str(), &i.w, &i.h, &i.channels, 0);
|
2022-08-27 03:04:39 -04:00
|
|
|
if (i.buffer == NULL) {
|
2022-08-28 09:40:27 -04:00
|
|
|
std::cerr << "ADV: STB IMAGE loading failed for " << file << std::endl;
|
2022-08-27 03:04:39 -04:00
|
|
|
return std::nullopt;
|
|
|
|
}
|
|
|
|
const bgfx::Memory* buf = bgfx::makeRef(i.buffer, i.w * i.h * i.channels * sizeof(u8));
|
|
|
|
i.tx = bgfx::createTexture2D(
|
|
|
|
i.w,
|
|
|
|
i.h,
|
|
|
|
false,
|
|
|
|
1,
|
2022-08-27 04:40:06 -04:00
|
|
|
i.channels == 4 ? bgfx::TextureFormat::RGBA8 : bgfx::TextureFormat::RGB8,
|
2022-08-27 03:04:39 -04:00
|
|
|
BGFX_TEXTURE_NONE | BGFX_SAMPLER_UVW_CLAMP,
|
|
|
|
buf
|
|
|
|
);
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
void destroy_image_texture(ImageTexture& i) {
|
|
|
|
stbi_image_free(i.buffer);
|
|
|
|
bgfx::destroy(i.tx);
|
|
|
|
}
|
|
|
|
|
|
|
|
Font regular, bold;
|
2022-08-28 09:40:27 -04:00
|
|
|
bgfx::ProgramHandle a_program, img_program, imga_program;
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
bgfx::UniformHandle s_texColor;
|
2022-08-28 09:40:27 -04:00
|
|
|
bgfx::UniformHandle imga_opacity;
|
2022-08-27 03:04:39 -04:00
|
|
|
|
|
|
|
bgfx::IndexBufferHandle ibh;
|
|
|
|
bgfx::VertexBufferHandle vbh;
|
|
|
|
bgfx::VertexLayout pcvDecl;
|
|
|
|
|
|
|
|
std::unordered_map<std::string, ImageTexture> imgs;
|
2022-08-30 05:37:45 -04:00
|
|
|
|
|
|
|
inline Font* ResolveStyleFlags(u32 style_flags) {
|
|
|
|
Font* f = ®ular;
|
|
|
|
switch (style_flags) {
|
|
|
|
case TEXT_NONE: break;
|
|
|
|
case TEXT_BOLD: f = &bold; break;
|
|
|
|
case TEXT_ITALIC: break;
|
|
|
|
case TEXT_ITALIC | TEXT_BOLD: break;
|
|
|
|
}
|
|
|
|
return f;
|
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
|
|
|
|
inline void HandleMarkupFlags(const NVL::Environment::MarkupSegment& s, u32& style_flags, const NVL::String*& ruby) {
|
|
|
|
for (const auto& e : s.efs) {
|
|
|
|
if (e.first == u"b") { style_flags |= TEXT_BOLD; }
|
|
|
|
else if (e.first == u"i") { style_flags |= TEXT_ITALIC; }
|
|
|
|
else if (e.first == u"u") { style_flags |= TEXT_UNDERLINE; }
|
|
|
|
else if (e.first == u"s") { style_flags |= TEXT_STRIKETHROUGH; }
|
|
|
|
else if (e.first == u"o") { style_flags |= TEXT_OVERLINE; }
|
|
|
|
else if (e.first == u"rb") { ruby = &e.second[0]; }
|
|
|
|
else {
|
|
|
|
std::cerr << "ADV: Unrecognized text markup: " << NVL::to_std_string(e.first) << std::endl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
namespace ADVect::Graphics {
|
|
|
|
bool Init(u16 width, u16 height) {
|
|
|
|
error = FT_Init_FreeType(&library);
|
|
|
|
if (error) {
|
|
|
|
std::cerr << "ADV: FreeType init error: " << error << std::endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
if (auto f = get_font("SourceHanSerif-Regular.ttc")) regular = *f; else return false;
|
|
|
|
if (auto f = get_font("SourceHanSerif-Heavy.ttc")) bold = *f; else return false;
|
2022-08-20 22:12:11 -04:00
|
|
|
|
|
|
|
pcvDecl.begin()
|
|
|
|
.add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float)
|
|
|
|
.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
|
|
|
|
.end();
|
|
|
|
ibh = bgfx::createIndexBuffer(bgfx::makeRef(quad_indices, sizeof(quad_indices)));
|
|
|
|
vbh = bgfx::createVertexBuffer(bgfx::makeRef(quad_vert, sizeof(quad_vert)), pcvDecl);
|
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
img_program = load_shader_program("test"); // RGBA
|
|
|
|
imga_program = load_shader_program("ImageAlpha"); // RGBA + Opacity
|
|
|
|
a_program = load_shader_program("AlphaStencil"); // A -> FFFA
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
imga_opacity = bgfx::createUniform("f_opacity", bgfx::UniformType::Vec4); // x -> alpha, yzw are dummies
|
2022-08-20 22:12:11 -04:00
|
|
|
s_texColor = bgfx::createUniform("s_texColor", bgfx::UniformType::Sampler);
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
return true;
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
|
|
|
|
2022-08-21 16:24:13 -04:00
|
|
|
void Shutdown() {
|
2022-08-27 03:04:39 -04:00
|
|
|
destroy_font(regular);
|
|
|
|
destroy_font(bold);
|
2022-05-09 01:50:09 -04:00
|
|
|
FT_Done_FreeType(library);
|
2022-08-18 12:17:43 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
for (auto it = imgs.begin(); it != imgs.end(); it++) {
|
|
|
|
destroy_image_texture(it->second);
|
|
|
|
}
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2022-08-22 02:15:25 -04:00
|
|
|
bgfx::destroy(a_program);
|
|
|
|
bgfx::destroy(img_program);
|
2022-08-28 09:40:27 -04:00
|
|
|
bgfx::destroy(imga_program);
|
2022-08-27 03:04:39 -04:00
|
|
|
|
2022-08-20 22:12:11 -04:00
|
|
|
bgfx::destroy(ibh);
|
|
|
|
bgfx::destroy(vbh);
|
2022-08-27 03:04:39 -04:00
|
|
|
|
2022-08-20 22:12:11 -04:00
|
|
|
bgfx::destroy(s_texColor);
|
2022-08-28 09:40:27 -04:00
|
|
|
bgfx::destroy(imga_opacity);
|
2022-08-22 02:15:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draws quad with texture on Z = 0
|
2022-08-27 03:04:39 -04:00
|
|
|
void DrawTexture(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h, u64 state, const bgfx::ProgramHandle& pg) {
|
2022-08-28 09:40:27 -04:00
|
|
|
f32 mtx1[16], mtx2[16], mtx3[16];
|
2022-08-22 02:15:25 -04:00
|
|
|
bx::mtxTranslate(mtx1, pos_x, pos_y, 0.0f);
|
|
|
|
bx::mtxScale(mtx2, w, h, 1.0f);
|
|
|
|
bx::mtxMul(mtx3, mtx2, mtx1);
|
|
|
|
|
|
|
|
bgfx::setTransform(mtx3);
|
|
|
|
|
|
|
|
bgfx::setVertexBuffer(0, vbh);
|
|
|
|
bgfx::setIndexBuffer(ibh);
|
|
|
|
|
|
|
|
bgfx::setTexture(0, s_texColor, tex);
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
bgfx::setState(state);
|
|
|
|
bgfx::submit(0, pg);
|
2022-08-22 02:15:25 -04:00
|
|
|
}
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
void DrawTextureImage(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h) {
|
2022-08-27 04:40:06 -04:00
|
|
|
DrawTexture(tex, pos_x, pos_y, w, h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), img_program);
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
void DrawTextureImage(const ImageTexture& img, i32 pos_x, i32 pos_y) {
|
|
|
|
DrawTextureImage(img.tx, pos_x, pos_y, img.w, img.h);
|
|
|
|
}
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2022-08-28 09:40:27 -04:00
|
|
|
void DrawTextureImageAlpha(const ImageTexture& img, i32 pos_x, i32 pos_y, f32 alpha) {
|
|
|
|
f32 pack[4]{};
|
|
|
|
pack[0] = alpha;
|
|
|
|
bgfx::setUniform(imga_opacity, pack);
|
|
|
|
DrawTexture(img.tx, pos_x, pos_y, img.w, img.h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), imga_program);
|
|
|
|
}
|
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
ImageTexture* GetImageTextureFromFile(const std::string& file) {
|
|
|
|
if (imgs.find(file) == imgs.end()) {
|
|
|
|
if (auto i = get_image_texture(file)) {
|
2022-08-28 20:54:30 -04:00
|
|
|
imgs[file] = std::move(*i);
|
2022-08-27 03:04:39 -04:00
|
|
|
return &imgs[file];
|
|
|
|
}
|
2022-08-28 20:54:30 -04:00
|
|
|
else return nullptr;
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
2022-08-28 20:54:30 -04:00
|
|
|
else
|
|
|
|
return &imgs[file];
|
2022-08-27 03:04:39 -04:00
|
|
|
}
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2022-08-27 03:04:39 -04:00
|
|
|
void DrawTextureStencilAlpha(const bgfx::TextureHandle& tex, i32 pos_x, i32 pos_y, u32 w, u32 h) {
|
|
|
|
DrawTexture(tex, pos_x, pos_y, w, h, (BGFX_STATE_DEFAULT | BGFX_STATE_BLEND_ALPHA) & ~(BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS), a_program);
|
2022-08-20 22:12:11 -04:00
|
|
|
}
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
template <bool DoRender, bool Walk>
|
|
|
|
void RenderGlyph(NVL::Char c, std::conditional_t<Walk, i32&, i32> pos_x, std::conditional_t<Walk, i32&, i32> pos_y, u32 col, u32 style_flags = TEXT_NONE) {
|
2022-08-30 05:37:45 -04:00
|
|
|
Font* f = ResolveStyleFlags(style_flags);
|
2022-08-22 02:15:25 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
auto& [tx, w, h, l, t, x, y] = f->get_char(c);
|
2022-08-18 12:17:43 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
pos_x += l;
|
2022-08-20 22:12:11 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
if (auto& buf = tx; DoRender && tx && w != 0) {
|
|
|
|
DrawTextureStencilAlpha(*buf, pos_x, pos_y - (h - t), w, h);
|
2022-05-09 01:50:09 -04:00
|
|
|
}
|
2022-08-21 16:24:13 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
pos_x += x >> 6;
|
|
|
|
pos_y += y >> 6;
|
|
|
|
}
|
2022-08-30 05:37:45 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
template <bool DoRender, bool Walk>
|
|
|
|
void RenderString(const NVL::String& s, std::conditional_t<Walk, i32&, i32> pos_x, std::conditional_t<Walk, i32&, i32> pos_y, u32 col, u32 style_flags = TEXT_NONE) {
|
2022-08-30 05:37:45 -04:00
|
|
|
for (const auto& c : s) {
|
2023-04-06 19:56:40 -04:00
|
|
|
RenderGlyph<DoRender>(c, pos_x, pos_y, col, style_flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <bool DoRender, bool Walk>
|
|
|
|
void RenderString(NVL::String::const_iterator cbegin, NVL::String::const_iterator cend, std::conditional_t<Walk, i32&, i32> pos_x, std::conditional_t<Walk, i32&, i32> pos_y, u32 col, u32 style_flags = TEXT_NONE) {
|
|
|
|
while (cbegin < cend) {
|
|
|
|
RenderGlyph<DoRender>(*cbegin++, pos_x, pos_y, col, style_flags);
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
void RenderStringCentered(const NVL::String& s, i32 pos_x, i32 pos_y, u32 col, u32 style_flags = TEXT_NONE) {
|
|
|
|
i32 copy_x = 0, copy_y = 0;
|
|
|
|
RenderString<false, true>(s, copy_x, copy_y, col, style_flags);
|
|
|
|
RenderString<true, false>(s, pos_x - copy_x / 2, pos_y, col, style_flags);
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
// I cannot reason this function, returns where the string would end if it was not halted
|
|
|
|
i32 RenderSubstringBox(const NVL::String& s, i32& pos_x, i32& pos_y, i32 reset_x, u32 w, u32 col, u32 style_flags, size_t s_end) {
|
2022-08-30 05:37:45 -04:00
|
|
|
Font* f = ResolveStyleFlags(style_flags);
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
NVL::String::const_iterator last = s.cbegin(), end = s.cend(), pos = std::find(last, end, u' '),
|
|
|
|
halt = s_end > s.length() ? end : last + s_end;
|
|
|
|
if (pos == end) { // Render the entire thing with hard break if we don't split at all
|
|
|
|
NVL::String::const_iterator last_copy = last;
|
|
|
|
i32 copy_x = pos_x, copy_y = pos_y;
|
|
|
|
while (last != std::min(pos, halt)) {
|
|
|
|
RenderGlyph(*last++, pos_x, pos_y, col, style_flags);
|
|
|
|
if (pos_x - reset_x > w) {
|
|
|
|
pos_x = reset_x;
|
2022-08-30 05:37:45 -04:00
|
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
if (last >= halt) {
|
|
|
|
RenderString<false>(last_copy, pos, copy_x, copy_y, col, style_flags);
|
|
|
|
return copy_x;
|
|
|
|
};
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
else while (last != end) { // Loop through all segments
|
|
|
|
i32 copy_x = pos_x, old_x = pos_x, copy_y = pos_y;
|
|
|
|
RenderString<false>(last, pos, copy_x, copy_y, col, style_flags);
|
|
|
|
if (copy_x - reset_x > w) { // if walk exceeded max w
|
|
|
|
copy_x = reset_x, copy_y = pos_y; // try walk again from leftmost of box, see if a soft break works; reset y from last walk
|
|
|
|
RenderString<false>(last, pos, copy_x, copy_y, col, style_flags);
|
|
|
|
if (copy_x - reset_x > w) { // soft break won't work, go back to old x and hard break
|
|
|
|
pos_x = old_x;
|
|
|
|
while (last != pos) {
|
|
|
|
RenderGlyph(*last++, pos_x, pos_y, col, style_flags);
|
|
|
|
if (pos_x - reset_x > w) {
|
|
|
|
pos_x = reset_x;
|
|
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
|
|
}
|
|
|
|
if (last >= halt) return copy_x;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else { // soft break and kill beginning space
|
|
|
|
pos_x = reset_x;
|
|
|
|
pos_y -= f->face->size->metrics.height / 64;
|
|
|
|
last++;
|
|
|
|
RenderString(last, std::min(pos, halt), pos_x, pos_y, col, style_flags);
|
|
|
|
if (last >= halt) return copy_x;
|
2022-08-22 02:15:25 -04:00
|
|
|
}
|
2022-08-21 16:24:13 -04:00
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
else {
|
|
|
|
RenderString(last, std::min(pos, halt), pos_x, pos_y, col, style_flags);
|
|
|
|
if (last >= halt) return copy_x;
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
last = pos;
|
|
|
|
if (pos != end)
|
|
|
|
pos = std::find(std::next(pos), end, u' ');
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
return pos_x;
|
2022-08-30 05:37:45 -04:00
|
|
|
}
|
2023-04-06 19:56:40 -04:00
|
|
|
|
|
|
|
void RenderSubstringMarkupBox(const NVL::Environment::MarkupString& ms, i32 pos_x, i32 pos_y, u32 w, u32 col, size_t end) {
|
|
|
|
i32 init_x = pos_x;
|
|
|
|
size_t acc_length = 0;
|
2022-08-30 05:37:45 -04:00
|
|
|
// We assume markups are already sorted
|
|
|
|
for (const NVL::Environment::MarkupSegment& s : ms.segments) {
|
|
|
|
u32 style_flags = TEXT_NONE;
|
|
|
|
const NVL::String* ruby = nullptr;
|
2023-04-06 19:56:40 -04:00
|
|
|
HandleMarkupFlags(s, style_flags, ruby);
|
2022-08-30 05:37:45 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
i32 last_x = pos_x,
|
|
|
|
|
|
|
|
segment_end = RenderSubstringBox(s.str, pos_x, pos_y, init_x, w, col, style_flags, end - acc_length);
|
2022-08-30 05:37:45 -04:00
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
if (ruby != nullptr && segment_end != last_x) {
|
|
|
|
RenderStringCentered(*ruby, segment_end / 2 + last_x / 2, i32(pos_y + 20), col);
|
2022-08-21 16:24:13 -04:00
|
|
|
}
|
|
|
|
|
2023-04-06 19:56:40 -04:00
|
|
|
acc_length += s.str.length();
|
|
|
|
if (acc_length > end) return;
|
2022-08-21 16:24:13 -04:00
|
|
|
}
|
2022-08-22 02:15:25 -04:00
|
|
|
}
|
2022-08-30 05:37:45 -04:00
|
|
|
|
2022-08-22 02:15:25 -04:00
|
|
|
}
|