Files
2024-09-18 07:46:03 +02:00

861 lines
25 KiB
C++

#include "wiFont.h"
#include "wiRenderer.h"
#include "wiResourceManager.h"
#include "wiHelper.h"
#include "shaders/ShaderInterop_Font.h"
#include "wiBacklog.h"
#include "wiTextureHelper.h"
#include "wiRectPacker.h"
#include "wiSpinLock.h"
#include "wiPlatform.h"
#include "wiEventHandler.h"
#include "wiTimer.h"
#include "wiUnorderedMap.h"
#include "wiUnorderedSet.h"
#include "wiVector.h"
#include "Utility/liberation_sans.h"
#include "Utility/stb_truetype.h"
#include <fstream>
#include <mutex>
using namespace wi::enums;
using namespace wi::graphics;
namespace wi::font
{
namespace font_internal
{
enum DEPTH_TEST_MODE
{
DEPTH_TEST_OFF,
DEPTH_TEST_ON,
DEPTH_TEST_MODE_COUNT
};
static BlendState blendState;
static RasterizerState rasterizerState;
static DepthStencilState depthStencilStates[DEPTH_TEST_MODE_COUNT];
static Shader vertexShader;
static Shader pixelShader;
static PipelineState PSO[DEPTH_TEST_MODE_COUNT];
static thread_local wi::Canvas canvas;
static Texture texture;
struct FontStyle
{
std::string name;
wi::vector<uint8_t> fontBuffer; // only used if loaded from file, need to keep alive
stbtt_fontinfo fontInfo;
int ascent, descent, lineGap;
void Create(const std::string& newName, const uint8_t* data, size_t size)
{
name = newName;
int offset = stbtt_GetFontOffsetForIndex(data, 0);
if (!stbtt_InitFont(&fontInfo, data, offset))
{
wi::backlog::post("Failed to load font: " + name + " (file was unrecognized, it must be a .ttf file)");
}
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
}
void Create(const std::string& newName)
{
if (wi::helper::FileRead(newName, fontBuffer))
{
Create(newName, fontBuffer.data(), fontBuffer.size());
}
else
{
wi::backlog::post("Failed to load font: " + name + " (file could not be opened)");
}
}
};
static wi::vector<std::unique_ptr<FontStyle>> fontStyles;
struct Glyph
{
float x;
float y;
float width;
float height;
float tc_left;
float tc_right;
float tc_top;
float tc_bottom;
const FontStyle* fontStyle = nullptr;
};
static wi::unordered_map<int32_t, Glyph> glyph_lookup;
static wi::unordered_map<int32_t, wi::rectpacker::Rect> rect_lookup;
struct Bitmap
{
int width;
int height;
int xoff;
int yoff;
wi::vector<uint8_t> data;
};
static wi::unordered_map<int32_t, Bitmap> bitmap_lookup;
union GlyphHash
{
struct
{
uint32_t code : 16; // character code range supported: 0 - 65535
uint32_t height : 10; // height supported: 0 - 1023
uint32_t style : 5; // number of font styles supported: 0 - 31
uint32_t sdf : 1; // true or false
} bits;
uint32_t raw;
};
static_assert(sizeof(GlyphHash) == sizeof(uint32_t));
static wi::unordered_set<uint32_t> pendingGlyphs;
static std::mutex locker;
struct ParseStatus
{
Cursor cursor;
uint32_t quadCount = 0;
size_t last_word_begin = 0;
bool start_new_word = false;
};
static thread_local wi::vector<FontVertex> vertexList;
ParseStatus ParseText(const wchar_t* text, size_t text_length, const Params& params)
{
ParseStatus status;
status.cursor = params.cursor;
vertexList.clear();
const float whitespace_size = (float(params.size) + params.spacingX) * 0.25f;
const float tab_size = whitespace_size * 4;
const float linebreak_size = (float(params.size) + params.spacingY);
auto word_wrap = [&] {
status.start_new_word = true;
if (status.last_word_begin > 0 && params.h_wrap >= 0 && status.cursor.position.x >= params.h_wrap - 1)
{
// Word ended and wrap detected, push down last word by one line:
const float word_offset = vertexList[status.last_word_begin].pos.x;
for (size_t i = status.last_word_begin; i < status.quadCount * 4; ++i)
{
vertexList[i].pos.x -= word_offset;
vertexList[i].pos.y += linebreak_size;
}
status.cursor.position.x -= word_offset;
status.cursor.position.y += linebreak_size;
status.cursor.size.x = std::max(status.cursor.size.x, status.cursor.position.x);
status.cursor.size.y = std::max(status.cursor.size.y, status.cursor.position.y + linebreak_size);
}
};
status.cursor.size.y = status.cursor.position.y + linebreak_size;
for (size_t i = 0; i < text_length; ++i)
{
int code = (int)text[i];
GlyphHash hash;
hash.bits.code = text[i];
hash.bits.height = params.size;
hash.bits.style = (uint32_t)params.style;
hash.bits.sdf = params.isSDFRenderingEnabled() ? 1 : 0;
if (glyph_lookup.count(hash.raw) == 0)
{
// glyph not packed yet, so add to pending list:
std::scoped_lock lck(locker);
pendingGlyphs.insert(hash.raw);
continue;
}
if (code == '\n')
{
word_wrap();
status.cursor.position.x = 0;
status.cursor.position.y += linebreak_size;
}
else if (code == ' ')
{
word_wrap();
status.cursor.position.x += whitespace_size;
}
else if (code == '\t')
{
word_wrap();
status.cursor.position.x += tab_size;
}
else if (code == '\r')
{
}
else
{
const Glyph& glyph = glyph_lookup.at(hash.raw);
const float glyphWidth = glyph.width;
const float glyphHeight = glyph.height;
const float glyphOffsetX = glyph.x;
const float glyphOffsetY = glyph.y;
const float fontScale = stbtt_ScaleForPixelHeight(&glyph.fontStyle->fontInfo, (float)params.size);
const size_t vertexID = size_t(status.quadCount) * 4;
vertexList.resize(vertexID + 4);
status.quadCount++;
if (status.start_new_word)
{
status.last_word_begin = vertexID;
}
status.start_new_word = false;
const float left = status.cursor.position.x + glyphOffsetX;
const float right = left + glyphWidth;
const float top = status.cursor.position.y + glyphOffsetY;
const float bottom = top + glyphHeight;
vertexList[vertexID + 0].pos = float2(left, top);
vertexList[vertexID + 1].pos = float2(right, top);
vertexList[vertexID + 2].pos = float2(left, bottom);
vertexList[vertexID + 3].pos = float2(right, bottom);
float tc_left = glyph.tc_left;
float tc_right = glyph.tc_right;
float tc_top = glyph.tc_top;
float tc_bottom = glyph.tc_bottom;
if (params.isFlippedHorizontally())
{
std::swap(tc_left, tc_right);
}
if (params.isFlippedVertically())
{
std::swap(tc_top, tc_bottom);
}
vertexList[vertexID + 0].uv = float2(tc_left, tc_top);
vertexList[vertexID + 1].uv = float2(tc_right, tc_top);
vertexList[vertexID + 2].uv = float2(tc_left, tc_bottom);
vertexList[vertexID + 3].uv = float2(tc_right, tc_bottom);
int advance, lsb;
stbtt_GetCodepointHMetrics(&glyph.fontStyle->fontInfo, code, &advance, &lsb);
status.cursor.position.x += advance * fontScale;
status.cursor.position.x += params.spacingX;
if (text_length > 1 && i < text_length - 1 && text[i + 1])
{
int code_next = (int)text[i + 1];
int kern = stbtt_GetCodepointKernAdvance(&glyph.fontStyle->fontInfo, code, code_next);
status.cursor.position.x += kern * fontScale;
}
}
status.cursor.size.x = std::max(status.cursor.size.x, status.cursor.position.x);
status.cursor.size.y = std::max(status.cursor.size.y, status.cursor.position.y + linebreak_size);
}
word_wrap();
return status;
}
thread_local static std::string char_temp_buffer;
thread_local static std::wstring wchar_temp_buffer;
ParseStatus ParseText(const char* text, size_t text_length, const Params& params)
{
// the temp buffers are used to avoid allocations of string objects:
char_temp_buffer = text;
wi::helper::StringConvert(char_temp_buffer, wchar_temp_buffer);
return ParseText(wchar_temp_buffer.c_str(), wchar_temp_buffer.length(), params);
}
void CommitText(void* vertexList_GPU)
{
std::memcpy(vertexList_GPU, vertexList.data(), sizeof(FontVertex) * vertexList.size());
}
}
using namespace font_internal;
void LoadShaders()
{
wi::renderer::LoadShader(ShaderStage::VS, vertexShader, "fontVS.cso");
wi::renderer::LoadShader(ShaderStage::PS, pixelShader, "fontPS.cso");
for (int d = 0; d < DEPTH_TEST_MODE_COUNT; ++d)
{
PipelineStateDesc desc;
desc.vs = &vertexShader;
desc.ps = &pixelShader;
desc.bs = &blendState;
desc.dss = &depthStencilStates[d];
desc.rs = &rasterizerState;
desc.pt = PrimitiveTopology::TRIANGLESTRIP;
wi::graphics::GetDevice()->CreatePipelineState(&desc, &PSO[d]);
}
}
void Initialize()
{
wi::Timer timer;
// add default font if there is none yet:
if (fontStyles.empty())
{
AddFontStyle("Liberation Sans", liberation_sans, sizeof(liberation_sans));
}
RasterizerState rs;
rs.fill_mode = FillMode::SOLID;
rs.cull_mode = CullMode::NONE;
rs.front_counter_clockwise = true;
rs.depth_bias = 0;
rs.depth_bias_clamp = 0;
rs.slope_scaled_depth_bias = 0;
rs.depth_clip_enable = false;
rs.multisample_enable = false;
rs.antialiased_line_enable = false;
rasterizerState = rs;
BlendState bd;
bd.render_target[0].blend_enable = true;
bd.render_target[0].src_blend = Blend::SRC_ALPHA;
bd.render_target[0].dest_blend = Blend::INV_SRC_ALPHA;
bd.render_target[0].blend_op = BlendOp::ADD;
bd.render_target[0].src_blend_alpha = Blend::ONE;
bd.render_target[0].dest_blend_alpha = Blend::INV_SRC_ALPHA;
bd.render_target[0].blend_op_alpha = BlendOp::ADD;
bd.render_target[0].render_target_write_mask = ColorWrite::ENABLE_ALL;
bd.independent_blend_enable = false;
blendState = bd;
DepthStencilState dsd;
dsd.depth_enable = false;
dsd.stencil_enable = false;
depthStencilStates[DEPTH_TEST_OFF] = dsd;
dsd.depth_enable = true;
dsd.depth_write_mask = DepthWriteMask::ZERO;
dsd.depth_func = ComparisonFunc::GREATER_EQUAL;
depthStencilStates[DEPTH_TEST_ON] = dsd;
static wi::eventhandler::Handle handle1 = wi::eventhandler::Subscribe(wi::eventhandler::EVENT_RELOAD_SHADERS, [](uint64_t userdata) { LoadShaders(); });
LoadShaders();
wi::backlog::post("wi::font Initialized (" + std::to_string((int)std::round(timer.elapsed())) + " ms)");
}
void InvalidateAtlas()
{
texture = {};
glyph_lookup.clear();
rect_lookup.clear();
bitmap_lookup.clear();
}
void UpdateAtlas(float upscaling)
{
std::scoped_lock lck(locker);
upscaling = std::max(1.5f, upscaling); // add some minimum upscaling, especially for SDF
static float upscaling_prev = 1;
const float upscaling_rcp = 1.0f / upscaling;
if (upscaling_prev != upscaling)
{
// If upscaling changed (DPI change), clear glyph caches, they will need to be re-rendered:
InvalidateAtlas();
upscaling_prev = upscaling;
}
// If there are pending glyphs, render them and repack the atlas:
if (!pendingGlyphs.empty())
{
for (int32_t raw : pendingGlyphs)
{
GlyphHash hash;
hash.raw = raw;
const int code = (int)hash.bits.code;
const float height = (float)hash.bits.height;
const bool is_sdf = hash.bits.sdf ? true : false;
uint32_t style = hash.bits.style;
FontStyle* fontStyle = fontStyles[style].get();
int glyphIndex = stbtt_FindGlyphIndex(&fontStyle->fontInfo, code);
if (glyphIndex == 0)
{
// Try fallback to an other font style that has this character:
style = 0;
while (glyphIndex == 0 && style < fontStyles.size())
{
fontStyle = fontStyles[style].get();
glyphIndex = stbtt_FindGlyphIndex(&fontStyle->fontInfo, code);
style++;
}
}
float fontScaling = stbtt_ScaleForPixelHeight(&fontStyle->fontInfo, height * upscaling);
Bitmap& bitmap = bitmap_lookup[hash.raw];
bitmap.width = 0;
bitmap.height = 0;
bitmap.xoff = 0;
bitmap.yoff = 0;
if (is_sdf)
{
unsigned char* data = stbtt_GetGlyphSDF(
&fontStyle->fontInfo,
fontScaling,
glyphIndex,
(int)SDF::padding,
(unsigned char)SDF::onedge_value,
SDF::pixel_dist_scale,
&bitmap.width,
&bitmap.height,
&bitmap.xoff,
&bitmap.yoff
);
bitmap.data.resize(bitmap.width * bitmap.height);
std::memcpy(bitmap.data.data(), data, bitmap.data.size());
stbtt_FreeSDF(data, nullptr);
}
else
{
unsigned char* data = stbtt_GetGlyphBitmap(
&fontStyle->fontInfo,
fontScaling,
fontScaling,
glyphIndex,
&bitmap.width,
&bitmap.height,
&bitmap.xoff,
&bitmap.yoff
);
bitmap.data.resize(bitmap.width * bitmap.height);
std::memcpy(bitmap.data.data(), data, bitmap.data.size());
stbtt_FreeBitmap(data, nullptr);
}
wi::rectpacker::Rect rect = {};
rect.w = bitmap.width + 2;
rect.h = bitmap.height + 2;
rect.id = hash.raw;
rect_lookup[hash.raw] = rect;
Glyph& glyph = glyph_lookup[hash.raw];
glyph.x = float(bitmap.xoff) * upscaling_rcp;
glyph.y = (float(bitmap.yoff) + float(fontStyle->ascent) * fontScaling) * upscaling_rcp;
glyph.width = float(bitmap.width) * upscaling_rcp;
glyph.height = float(bitmap.height) * upscaling_rcp;
glyph.fontStyle = fontStyle;
}
pendingGlyphs.clear();
// Setup packer, this will allocate memory if needed:
static thread_local wi::rectpacker::State packer;
packer.clear();
for (auto& it : rect_lookup)
{
packer.add_rect(it.second);
}
// Perform packing and process the result if successful:
if (packer.pack(4096))
{
// Retrieve texture atlas dimensions:
const int atlasWidth = packer.width;
const int atlasHeight = packer.height;
const float inv_width = 1.0f / atlasWidth;
const float inv_height = 1.0f / atlasHeight;
// Create the CPU-side texture atlas and fill with transparency (0):
wi::vector<uint8_t> atlas(size_t(atlasWidth) * size_t(atlasHeight));
std::fill(atlas.begin(), atlas.end(), 0);
// Iterate all packed glyph rectangles:
for (auto& rect : packer.rects)
{
rect.x += 1;
rect.y += 1;
rect.w -= 2;
rect.h -= 2;
const int32_t hash = rect.id;
//const wchar_t code = codefromhash(hash);
//const int style = stylefromhash(hash);
//const float height = (float)heightfromhash(hash);
Glyph& glyph = glyph_lookup[hash];
Bitmap& bitmap = bitmap_lookup[hash];
for (int row = 0; row < bitmap.height; ++row)
{
uint8_t* dst = atlas.data() + rect.x + (rect.y + row) * atlasWidth;
uint8_t* src = bitmap.data.data() + row * bitmap.width;
std::memcpy(dst, src, bitmap.width);
}
// Compute texture coordinates for the glyph:
glyph.tc_left = float(rect.x);
glyph.tc_right = glyph.tc_left + float(rect.w);
glyph.tc_top = float(rect.y);
glyph.tc_bottom = glyph.tc_top + float(rect.h);
glyph.tc_left *= inv_width;
glyph.tc_right *= inv_width;
glyph.tc_top *= inv_height;
glyph.tc_bottom *= inv_height;
}
// Upload the CPU-side texture atlas bitmap to the GPU:
wi::texturehelper::CreateTexture(texture, atlas.data(), atlasWidth, atlasHeight, Format::R8_UNORM);
GetDevice()->SetName(&texture, "wi::font::texture");
}
else
{
assert(0); // rect packing failure
}
}
}
const Texture* GetAtlas()
{
return &texture;
}
int AddFontStyle(const std::string& fontName)
{
std::scoped_lock lck(locker);
for (size_t i = 0; i < fontStyles.size(); i++)
{
const FontStyle& fontStyle = *fontStyles[i];
if (fontStyle.name.compare(fontName) == 0)
{
return int(i);
}
}
fontStyles.push_back(std::make_unique<FontStyle>());
fontStyles.back()->Create(fontName);
InvalidateAtlas(); // invalidate atlas, in case there were missing glyphs, upon adding new font style they could become valid
return int(fontStyles.size() - 1);
}
int AddFontStyle(const std::string& fontName, const uint8_t* data, size_t size, bool copyData)
{
std::scoped_lock lck(locker);
for (size_t i = 0; i < fontStyles.size(); i++)
{
const FontStyle& fontStyle = *fontStyles[i];
if (fontStyle.name.compare(fontName) == 0)
{
return int(i);
}
}
fontStyles.push_back(std::make_unique<FontStyle>());
if (copyData)
{
fontStyles.back()->fontBuffer.resize(size);
std::memcpy(fontStyles.back()->fontBuffer.data(), data, size);
data = fontStyles.back()->fontBuffer.data();
}
fontStyles.back()->Create(fontName, data, size);
InvalidateAtlas(); // invalidate atlas, in case there were missing glyphs, upon adding new font style they could become valid
return int(fontStyles.size() - 1);
}
template<typename T>
Cursor Draw_internal(const T* text, size_t text_length, const Params& params, CommandList cmd)
{
if (text_length <= 0)
{
return Cursor();
}
ParseStatus status = ParseText(text, text_length, params);
if (status.quadCount > 0)
{
GraphicsDevice* device = wi::graphics::GetDevice();
GraphicsDevice::GPUAllocation mem = device->AllocateGPU(sizeof(FontVertex) * status.quadCount * 4, cmd);
if (!mem.IsValid())
{
return status.cursor;
}
CommitText(mem.data);
FontConstants font = {};
font.buffer_index = device->GetDescriptorIndex(&mem.buffer, SubresourceType::SRV);
font.buffer_offset = (uint32_t)mem.offset;
font.texture_index = device->GetDescriptorIndex(&texture, SubresourceType::SRV);
if (font.buffer_index < 0 || font.texture_index < 0)
{
return status.cursor;
}
device->EventBegin("Font", cmd);
device->BindPipelineState(&PSO[params.isDepthTestEnabled()], cmd);
font.flags = 0;
if (params.isSDFRenderingEnabled())
{
font.flags |= FONT_FLAG_SDF_RENDERING;
}
if (params.isHDR10OutputMappingEnabled())
{
font.flags |= FONT_FLAG_OUTPUT_COLOR_SPACE_HDR10_ST2084;
}
if (params.isLinearOutputMappingEnabled())
{
font.flags |= FONT_FLAG_OUTPUT_COLOR_SPACE_LINEAR;
font.hdr_scaling = params.hdr_scaling;
}
XMFLOAT3 offset = XMFLOAT3(0, 0, 0);
float vertical_flip = params.customProjection == nullptr ? 1.0f : -1.0f;
if (params.h_align == WIFALIGN_CENTER)
offset.x -= status.cursor.size.x / 2;
else if (params.h_align == WIFALIGN_RIGHT)
offset.x -= status.cursor.size.x;
if (params.v_align == WIFALIGN_CENTER)
offset.y -= status.cursor.size.y / 2 * vertical_flip;
else if (params.v_align == WIFALIGN_BOTTOM)
offset.y -= status.cursor.size.y * vertical_flip;
XMMATRIX M = XMMatrixTranslation(offset.x, offset.y, offset.z);
M = M * XMMatrixScaling(params.scaling, params.scaling, params.scaling);
M = M * XMMatrixRotationZ(params.rotation);
if (params.customRotation != nullptr)
{
M = M * (*params.customRotation);
}
M = M * XMMatrixTranslation(params.position.x, params.position.y, params.position.z);
if (params.customProjection != nullptr)
{
M = XMMatrixScaling(1, -1, 1) * M; // reason: screen projection is Y down (like UV-space) and that is the common case for image rendering. But custom projections will use the "world space"
M = M * (*params.customProjection);
}
else
{
// Asserts will check that a proper canvas was set for this cmd with wi::image::SetCanvas()
// The canvas must be set to have dpi aware rendering
assert(canvas.width > 0);
assert(canvas.height > 0);
assert(canvas.dpi > 0);
M = M * canvas.GetProjection();
}
if (params.shadowColor.getA() > 0)
{
// font shadow render:
XMStoreFloat4x4(&font.transform, XMMatrixTranslation(params.shadow_offset_x, params.shadow_offset_y, 0) * M);
font.color = params.shadowColor;
font.color.x *= params.shadow_intensity;
font.color.y *= params.shadow_intensity;
font.color.z *= params.shadow_intensity;
font.bolden = params.shadow_bolden;
font.softness = params.shadow_softness * 0.5f;
device->BindDynamicConstantBuffer(font, CBSLOT_FONT, cmd);
device->DrawInstanced(4, status.quadCount, 0, 0, cmd);
}
// font base render:
XMStoreFloat4x4(&font.transform, M);
font.color = params.color;
font.color.x *= params.intensity;
font.color.y *= params.intensity;
font.color.z *= params.intensity;
font.bolden = params.bolden;
font.softness = params.softness * 0.5f;
device->BindDynamicConstantBuffer(font, CBSLOT_FONT, cmd);
device->DrawInstanced(4, status.quadCount, 0, 0, cmd);
device->EventEnd(cmd);
}
return status.cursor;
}
void SetCanvas(const wi::Canvas& current_canvas)
{
canvas = current_canvas;
}
Cursor Draw(const char* text, size_t text_length, const Params& params, CommandList cmd)
{
return Draw_internal(text, text_length, params, cmd);
}
Cursor Draw(const wchar_t* text, size_t text_length, const Params& params, CommandList cmd)
{
return Draw_internal(text, text_length, params, cmd);
}
Cursor Draw(const char* text, const Params& params, CommandList cmd)
{
return Draw_internal(text, strlen(text), params, cmd);
}
Cursor Draw(const wchar_t* text, const Params& params, CommandList cmd)
{
return Draw_internal(text, wcslen(text), params, cmd);
}
Cursor Draw(const std::string& text, const Params& params, CommandList cmd)
{
return Draw_internal(text.c_str(), text.length(), params, cmd);
}
Cursor Draw(const std::wstring& text, const Params& params, CommandList cmd)
{
return Draw_internal(text.c_str(), text.length(), params, cmd);
}
XMFLOAT2 TextSize(const char* text, size_t text_length, const Params& params)
{
if (text_length == 0)
{
return XMFLOAT2(0, 0);
}
return ParseText(text, text_length, params).cursor.size;
}
XMFLOAT2 TextSize(const wchar_t* text, size_t text_length, const Params& params)
{
if (text_length == 0)
{
return XMFLOAT2(0, 0);
}
return ParseText(text, text_length, params).cursor.size;
}
XMFLOAT2 TextSize(const char* text, const Params& params)
{
size_t text_length = strlen(text);
if (text_length == 0)
{
return XMFLOAT2(0, 0);
}
return ParseText(text, text_length, params).cursor.size;
}
XMFLOAT2 TextSize(const wchar_t* text, const Params& params)
{
size_t text_length = wcslen(text);
if (text_length == 0)
{
return XMFLOAT2(0, 0);
}
return ParseText(text, text_length, params).cursor.size;
}
XMFLOAT2 TextSize(const std::string& text, const Params& params)
{
if (text.empty())
{
return XMFLOAT2(0, 0);
}
return ParseText(text.c_str(), text.length(), params).cursor.size;
}
XMFLOAT2 TextSize(const std::wstring& text, const Params& params)
{
if (text.empty())
{
return XMFLOAT2(0, 0);
}
return ParseText(text.c_str(), text.length(), params).cursor.size;
}
Cursor TextCursor(const char* text, size_t text_length, const Params& params)
{
if (text_length == 0)
{
return {};
}
return ParseText(text, text_length, params).cursor;
}
Cursor TextCursor(const wchar_t* text, size_t text_length, const Params& params)
{
if (text_length == 0)
{
return {};
}
return ParseText(text, text_length, params).cursor;
}
Cursor TextCursor(const char* text, const Params& params)
{
size_t text_length = strlen(text);
if (text_length == 0)
{
return {};
}
return ParseText(text, text_length, params).cursor;
}
Cursor TextCursor(const wchar_t* text, const Params& params)
{
size_t text_length = wcslen(text);
if (text_length == 0)
{
return {};
}
return ParseText(text, text_length, params).cursor;
}
Cursor TextCursor(const std::string& text, const Params& params)
{
if (text.empty())
{
return {};
}
return ParseText(text.c_str(), text.length(), params).cursor;
}
Cursor TextCursor(const std::wstring& text, const Params& params)
{
if (text.empty())
{
return {};
}
return ParseText(text.c_str(), text.length(), params).cursor;
}
float TextWidth(const char* text, size_t text_length, const Params& params)
{
return TextSize(text, text_length, params).x;
}
float TextWidth(const wchar_t* text, size_t text_length, const Params& params)
{
return TextSize(text, text_length, params).x;
}
float TextWidth(const char* text, const Params& params)
{
return TextSize(text, params).x;
}
float TextWidth(const wchar_t* text, const Params& params)
{
return TextSize(text, params).x;
}
float TextWidth(const std::string& text, const Params& params)
{
return TextSize(text, params).x;
}
float TextWidth(const std::wstring& text, const Params& params)
{
return TextSize(text, params).x;
}
float TextHeight(const char* text, size_t text_length, const Params& params)
{
return TextSize(text, text_length, params).y;
}
float TextHeight(const wchar_t* text, size_t text_length, const Params& params)
{
return TextSize(text, text_length, params).y;
}
float TextHeight(const char* text, const Params& params)
{
return TextSize(text, params).y;
}
float TextHeight(const wchar_t* text, const Params& params)
{
return TextSize(text, params).y;
}
float TextHeight(const std::string& text, const Params& params)
{
return TextSize(text, params).y;
}
float TextHeight(const std::wstring& text, const Params& params)
{
return TextSize(text, params).y;
}
}