De-ImGui XCUI standalone text atlas provider

This commit is contained in:
2026-04-05 15:16:15 +08:00
parent b05e76de0c
commit 6fd3ed434d
5 changed files with 879 additions and 133 deletions

View File

@@ -1,6 +1,21 @@
#include "XCUIBackend/XCUIStandaloneTextAtlasProvider.h"
#include <imgui_internal.h>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <wingdi.h>
#include <algorithm>
#include <array>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
namespace XCEngine {
namespace Editor {
@@ -8,65 +23,688 @@ namespace XCUIBackend {
namespace {
IXCUITextAtlasProvider::FontHandle MakeFontHandle(const ImFont* font) {
constexpr float kDefaultNominalFontSize = 18.0f;
constexpr int kAtlasPadding = 1;
constexpr int kMaxAtlasDimension = 4096;
constexpr std::size_t kInvalidFontIndex = (std::numeric_limits<std::size_t>::max)();
constexpr wchar_t kPrimaryFontFace[] = L"Segoe UI";
constexpr wchar_t kFallbackFontFace[] = L"Microsoft YaHei";
constexpr std::array<int, 5> kSupportedFontSizes = { 13, 14, 16, 18, 20 };
constexpr std::array<std::pair<std::uint32_t, std::uint32_t>, 2> kPrebakedCodepointRanges = {{
{ 0x0020u, 0x007Eu },
{ 0x00A0u, 0x00FFu },
}};
struct CachedPixelBuffer {
std::vector<unsigned char> bytes = {};
int width = 0;
int height = 0;
int bytesPerPixel = 0;
void Clear() {
bytes.clear();
width = 0;
height = 0;
bytesPerPixel = 0;
}
bool IsValid() const {
return !bytes.empty() && width > 0 && height > 0 && bytesPerPixel > 0;
}
};
struct GlyphRecord {
int sizeKey = 0;
std::uint32_t codepoint = 0u;
bool visible = false;
bool colored = false;
float advanceX = 0.0f;
float x0 = 0.0f;
float y0 = 0.0f;
float x1 = 0.0f;
float y1 = 0.0f;
int bitmapWidth = 0;
int bitmapHeight = 0;
int atlasX = 0;
int atlasY = 0;
std::vector<unsigned char> alpha = {};
};
struct BakedFontRecord {
int sizeKey = 0;
IXCUITextAtlasProvider::BakedFontInfo metrics = {};
std::unordered_map<std::uint32_t, IXCUITextAtlasProvider::GlyphInfo> glyphs = {};
};
IXCUITextAtlasProvider::FontHandle MakeDefaultFontHandle() {
IXCUITextAtlasProvider::FontHandle handle = {};
handle.value = reinterpret_cast<std::uintptr_t>(font);
handle.value = 1u;
return handle;
}
ImFont* ResolveFontHandle(IXCUITextAtlasProvider::FontHandle handle) {
return reinterpret_cast<ImFont*>(handle.value);
bool IsDefaultFontHandle(IXCUITextAtlasProvider::FontHandle handle) {
return handle.value == MakeDefaultFontHandle().value;
}
float ResolveNominalFontSize(const ImFont& font) {
return font.LegacySize > 0.0f ? font.LegacySize : 16.0f;
std::size_t ResolveFontIndex(IXCUITextAtlasProvider::FontHandle handle) {
return IsDefaultFontHandle(handle) ? 0u : kInvalidFontIndex;
}
float ResolveRequestedFontSize(const ImFont& font, float requestedFontSize) {
return requestedFontSize > 0.0f ? requestedFontSize : ResolveNominalFontSize(font);
std::uint64_t MakeGlyphKey(int sizeKey, std::uint32_t codepoint) {
return (static_cast<std::uint64_t>(static_cast<std::uint32_t>(sizeKey)) << 32u) |
static_cast<std::uint64_t>(codepoint);
}
bool IsFontOwnedByAtlas(const ImFont* font, const ImFontAtlas* atlas) {
return font != nullptr && atlas != nullptr && font->OwnerAtlas == atlas;
void AppendCodepointRange(
std::vector<std::uint32_t>& outCodepoints,
std::uint32_t first,
std::uint32_t last) {
if (first > last) {
return;
}
outCodepoints.reserve(outCodepoints.size() + static_cast<std::size_t>(last - first + 1u));
for (std::uint32_t codepoint = first; codepoint <= last; ++codepoint) {
outCodepoints.push_back(codepoint);
}
}
ImFontBaked* ResolveBakedFont(
IXCUITextAtlasProvider::FontHandle fontHandle,
ImFontAtlas* atlas,
float requestedFontSize) {
ImFont* font = ResolveFontHandle(fontHandle);
if (!IsFontOwnedByAtlas(font, atlas)) {
std::vector<std::uint32_t> BuildPrebakedCodepointSet() {
std::vector<std::uint32_t> codepoints = {};
codepoints.reserve(768u);
for (const auto& range : kPrebakedCodepointRanges) {
AppendCodepointRange(codepoints, range.first, range.second);
}
codepoints.push_back(0xFFFDu);
std::sort(codepoints.begin(), codepoints.end());
codepoints.erase(std::unique(codepoints.begin(), codepoints.end()), codepoints.end());
return codepoints;
}
HFONT CreateEditorFont(int pixelHeight, const wchar_t* faceName) {
return ::CreateFontW(
-pixelHeight,
0,
0,
0,
FW_NORMAL,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_TT_PRECIS,
CLIP_DEFAULT_PRECIS,
ANTIALIASED_QUALITY,
DEFAULT_PITCH | FF_DONTCARE,
faceName);
}
bool QueryTextMetrics(HDC dc, HFONT font, TEXTMETRICW& outMetrics) {
outMetrics = {};
if (dc == nullptr || font == nullptr) {
return false;
}
HGDIOBJ previous = ::SelectObject(dc, font);
const BOOL result = ::GetTextMetricsW(dc, &outMetrics);
::SelectObject(dc, previous);
return result != FALSE;
}
bool FontHasGlyph(HDC dc, HFONT font, std::uint32_t codepoint) {
if (dc == nullptr || font == nullptr || codepoint > 0xFFFFu) {
return false;
}
WORD glyphIndex = 0xFFFFu;
WCHAR wideCodepoint = static_cast<WCHAR>(codepoint);
HGDIOBJ previous = ::SelectObject(dc, font);
const DWORD result = ::GetGlyphIndicesW(
dc,
&wideCodepoint,
1u,
&glyphIndex,
GGI_MARK_NONEXISTING_GLYPHS);
::SelectObject(dc, previous);
return result != GDI_ERROR && glyphIndex != 0xFFFFu;
}
bool IsAdvanceOnlyWhitespace(std::uint32_t codepoint) {
return codepoint == 0x0020u ||
codepoint == 0x00A0u ||
(codepoint >= 0x2000u && codepoint <= 0x200Au) ||
codepoint == 0x202Fu ||
codepoint == 0x205Fu ||
codepoint == 0x3000u;
}
TEXTMETRICW MergeTextMetrics(
const TEXTMETRICW& primaryMetrics,
bool hasPrimaryMetrics,
const TEXTMETRICW& fallbackMetrics,
bool hasFallbackMetrics) {
TEXTMETRICW merged = {};
if (hasPrimaryMetrics) {
merged = primaryMetrics;
}
if (!hasFallbackMetrics) {
return merged;
}
if (!hasPrimaryMetrics) {
return fallbackMetrics;
}
merged.tmHeight = (std::max)(primaryMetrics.tmHeight, fallbackMetrics.tmHeight);
merged.tmAscent = (std::max)(primaryMetrics.tmAscent, fallbackMetrics.tmAscent);
merged.tmDescent = (std::max)(primaryMetrics.tmDescent, fallbackMetrics.tmDescent);
merged.tmExternalLeading = (std::max)(primaryMetrics.tmExternalLeading, fallbackMetrics.tmExternalLeading);
merged.tmAveCharWidth = (std::max)(primaryMetrics.tmAveCharWidth, fallbackMetrics.tmAveCharWidth);
merged.tmMaxCharWidth = (std::max)(primaryMetrics.tmMaxCharWidth, fallbackMetrics.tmMaxCharWidth);
return merged;
}
class GdiFontContext {
public:
explicit GdiFontContext(int sizeKey)
: m_sizeKey(sizeKey) {
m_dc = ::CreateCompatibleDC(nullptr);
if (m_dc == nullptr) {
return;
}
m_primaryFont = CreateEditorFont(sizeKey, kPrimaryFontFace);
m_fallbackFont = CreateEditorFont(sizeKey, kFallbackFontFace);
m_hasPrimaryMetrics = QueryTextMetrics(m_dc, m_primaryFont, m_primaryMetrics);
m_hasFallbackMetrics = QueryTextMetrics(m_dc, m_fallbackFont, m_fallbackMetrics);
m_lineMetrics = MergeTextMetrics(
m_primaryMetrics,
m_hasPrimaryMetrics,
m_fallbackMetrics,
m_hasFallbackMetrics);
}
~GdiFontContext() {
if (m_primaryFont != nullptr) {
::DeleteObject(m_primaryFont);
m_primaryFont = nullptr;
}
if (m_fallbackFont != nullptr) {
::DeleteObject(m_fallbackFont);
m_fallbackFont = nullptr;
}
if (m_dc != nullptr) {
::DeleteDC(m_dc);
m_dc = nullptr;
}
}
bool IsValid() const {
return m_dc != nullptr && (m_hasPrimaryMetrics || m_hasFallbackMetrics);
}
IXCUITextAtlasProvider::BakedFontInfo BuildBakedFontInfo() const {
IXCUITextAtlasProvider::BakedFontInfo info = {};
info.lineHeight = static_cast<float>(m_lineMetrics.tmHeight);
info.ascent = static_cast<float>(m_lineMetrics.tmAscent);
info.descent = static_cast<float>(m_lineMetrics.tmDescent);
info.rasterizerDensity = 1.0f;
return info;
}
bool RasterizeGlyph(std::uint32_t codepoint, GlyphRecord& outRecord) const {
outRecord = {};
outRecord.sizeKey = m_sizeKey;
outRecord.codepoint = codepoint;
outRecord.colored = false;
if (!IsValid()) {
return false;
}
HFONT resolvedFont = ResolveFontForCodepoint(codepoint);
if (resolvedFont == nullptr) {
return false;
}
if (IsAdvanceOnlyWhitespace(codepoint)) {
HGDIOBJ previous = ::SelectObject(m_dc, resolvedFont);
SIZE textExtent = {};
WCHAR wideCodepoint = static_cast<WCHAR>(codepoint);
const BOOL extentResult = ::GetTextExtentPoint32W(m_dc, &wideCodepoint, 1, &textExtent);
::SelectObject(m_dc, previous);
outRecord.visible = false;
outRecord.advanceX = extentResult != FALSE
? static_cast<float>(textExtent.cx)
: static_cast<float>(m_lineMetrics.tmAveCharWidth);
return true;
}
HGDIOBJ previous = ::SelectObject(m_dc, resolvedFont);
MAT2 transform = {};
transform.eM11.value = 1;
transform.eM22.value = 1;
GLYPHMETRICS glyphMetrics = {};
const DWORD bufferSize = ::GetGlyphOutlineW(
m_dc,
static_cast<UINT>(codepoint),
GGO_GRAY8_BITMAP,
&glyphMetrics,
0u,
nullptr,
&transform);
::SelectObject(m_dc, previous);
if (bufferSize == GDI_ERROR) {
return false;
}
outRecord.visible = glyphMetrics.gmBlackBoxX > 0u && glyphMetrics.gmBlackBoxY > 0u;
outRecord.advanceX = static_cast<float>(glyphMetrics.gmCellIncX);
outRecord.bitmapWidth = static_cast<int>(glyphMetrics.gmBlackBoxX);
outRecord.bitmapHeight = static_cast<int>(glyphMetrics.gmBlackBoxY);
outRecord.x0 = static_cast<float>(glyphMetrics.gmptGlyphOrigin.x);
outRecord.y0 = static_cast<float>(m_lineMetrics.tmAscent - glyphMetrics.gmptGlyphOrigin.y);
outRecord.x1 = outRecord.x0 + static_cast<float>(glyphMetrics.gmBlackBoxX);
outRecord.y1 = outRecord.y0 + static_cast<float>(glyphMetrics.gmBlackBoxY);
if (!outRecord.visible) {
return true;
}
std::vector<unsigned char> bitmapBuffer(bufferSize);
previous = ::SelectObject(m_dc, resolvedFont);
const DWORD writeResult = ::GetGlyphOutlineW(
m_dc,
static_cast<UINT>(codepoint),
GGO_GRAY8_BITMAP,
&glyphMetrics,
bufferSize,
bitmapBuffer.data(),
&transform);
::SelectObject(m_dc, previous);
if (writeResult == GDI_ERROR) {
return false;
}
const int sourceStride = (static_cast<int>(glyphMetrics.gmBlackBoxX) + 3) & ~3;
const std::size_t requiredBytes =
static_cast<std::size_t>(sourceStride) *
static_cast<std::size_t>((std::max)(0, outRecord.bitmapHeight));
if (bitmapBuffer.size() < requiredBytes) {
return false;
}
outRecord.alpha.resize(
static_cast<std::size_t>(outRecord.bitmapWidth) *
static_cast<std::size_t>(outRecord.bitmapHeight));
for (int y = 0; y < outRecord.bitmapHeight; ++y) {
const unsigned char* sourceRow = bitmapBuffer.data() + static_cast<std::size_t>(y * sourceStride);
unsigned char* destRow = outRecord.alpha.data() +
static_cast<std::size_t>(y * outRecord.bitmapWidth);
for (int x = 0; x < outRecord.bitmapWidth; ++x) {
const unsigned int value = static_cast<unsigned int>(sourceRow[x]);
destRow[x] = static_cast<unsigned char>((value * 255u + 32u) / 64u);
}
}
return true;
}
private:
HFONT ResolveFontForCodepoint(std::uint32_t codepoint) const {
if (FontHasGlyph(m_dc, m_primaryFont, codepoint)) {
return m_primaryFont;
}
if (FontHasGlyph(m_dc, m_fallbackFont, codepoint)) {
return m_fallbackFont;
}
return nullptr;
}
const float resolvedFontSize = ResolveRequestedFontSize(*font, requestedFontSize);
if (resolvedFontSize <= 0.0f) {
return nullptr;
int m_sizeKey = 0;
HDC m_dc = nullptr;
HFONT m_primaryFont = nullptr;
HFONT m_fallbackFont = nullptr;
TEXTMETRICW m_primaryMetrics = {};
TEXTMETRICW m_fallbackMetrics = {};
TEXTMETRICW m_lineMetrics = {};
bool m_hasPrimaryMetrics = false;
bool m_hasFallbackMetrics = false;
};
std::uintptr_t MakePixelDataKey(const CachedPixelBuffer& pixels, std::uint64_t generation) {
return reinterpret_cast<std::uintptr_t>(pixels.bytes.data()) ^
static_cast<std::uintptr_t>(generation * 1315423911ull);
}
int ResolveSupportedSizeKey(float requestedSize, float nominalSize) {
const float targetSize = requestedSize > 0.0f ? requestedSize : nominalSize;
auto bestIt = std::min_element(
kSupportedFontSizes.begin(),
kSupportedFontSizes.end(),
[targetSize](int lhs, int rhs) {
const float lhsDistance = std::fabs(targetSize - static_cast<float>(lhs));
const float rhsDistance = std::fabs(targetSize - static_cast<float>(rhs));
return lhsDistance < rhsDistance;
});
return bestIt != kSupportedFontSizes.end() ? *bestIt : static_cast<int>(std::lround(targetSize));
}
bool PopulateGlyphInfo(
const GlyphRecord& glyph,
int atlasWidth,
int atlasHeight,
IXCUITextAtlasProvider::GlyphInfo& outInfo) {
outInfo = {};
outInfo.requestedCodepoint = glyph.codepoint;
outInfo.resolvedCodepoint = glyph.codepoint;
outInfo.visible = glyph.visible;
outInfo.colored = glyph.colored;
outInfo.advanceX = glyph.advanceX;
outInfo.x0 = glyph.x0;
outInfo.y0 = glyph.y0;
outInfo.x1 = glyph.x1;
outInfo.y1 = glyph.y1;
if (!glyph.visible) {
return true;
}
if (atlasWidth <= 0 || atlasHeight <= 0) {
return false;
}
return font->GetFontBaked(resolvedFontSize);
outInfo.u0 = static_cast<float>(glyph.atlasX) / static_cast<float>(atlasWidth);
outInfo.v0 = static_cast<float>(glyph.atlasY) / static_cast<float>(atlasHeight);
outInfo.u1 = static_cast<float>(glyph.atlasX + glyph.bitmapWidth) / static_cast<float>(atlasWidth);
outInfo.v1 = static_cast<float>(glyph.atlasY + glyph.bitmapHeight) / static_cast<float>(atlasHeight);
return true;
}
std::vector<std::size_t> BuildVisiblePackOrder(const std::vector<GlyphRecord>& glyphs) {
std::vector<std::size_t> order = {};
order.reserve(glyphs.size());
for (std::size_t index = 0; index < glyphs.size(); ++index) {
if (glyphs[index].visible) {
order.push_back(index);
}
}
std::sort(
order.begin(),
order.end(),
[&glyphs](std::size_t lhs, std::size_t rhs) {
const GlyphRecord& left = glyphs[lhs];
const GlyphRecord& right = glyphs[rhs];
if (left.bitmapHeight != right.bitmapHeight) {
return left.bitmapHeight > right.bitmapHeight;
}
if (left.bitmapWidth != right.bitmapWidth) {
return left.bitmapWidth > right.bitmapWidth;
}
if (left.sizeKey != right.sizeKey) {
return left.sizeKey < right.sizeKey;
}
return left.codepoint < right.codepoint;
});
return order;
}
bool TryPackVisibleGlyphs(
std::vector<GlyphRecord>& glyphs,
int atlasWidth,
int& outAtlasHeight) {
outAtlasHeight = 1;
if (atlasWidth <= 0) {
return false;
}
for (GlyphRecord& glyph : glyphs) {
glyph.atlasX = 0;
glyph.atlasY = 0;
}
const std::vector<std::size_t> packOrder = BuildVisiblePackOrder(glyphs);
int cursorX = kAtlasPadding;
int cursorY = kAtlasPadding;
int rowHeight = 0;
for (std::size_t index : packOrder) {
GlyphRecord& glyph = glyphs[index];
const int requiredWidth = glyph.bitmapWidth + kAtlasPadding * 2;
const int requiredHeight = glyph.bitmapHeight + kAtlasPadding * 2;
if (requiredWidth > atlasWidth) {
return false;
}
if (cursorX + requiredWidth > atlasWidth) {
cursorX = kAtlasPadding;
cursorY += rowHeight;
rowHeight = 0;
}
glyph.atlasX = cursorX + kAtlasPadding;
glyph.atlasY = cursorY + kAtlasPadding;
cursorX += requiredWidth;
rowHeight = (std::max)(rowHeight, requiredHeight);
outAtlasHeight = (std::max)(outAtlasHeight, cursorY + rowHeight + kAtlasPadding);
if (outAtlasHeight > kMaxAtlasDimension) {
return false;
}
}
return true;
}
} // namespace
XCUIStandaloneTextAtlasProvider::XCUIStandaloneTextAtlasProvider() {
struct XCUIStandaloneTextAtlasProvider::Impl {
bool ready = false;
float nominalSize = kDefaultNominalFontSize;
CachedPixelBuffer alpha8Pixels = {};
CachedPixelBuffer rgba32Pixels = {};
std::uint64_t pixelGeneration = 0u;
std::unordered_map<int, BakedFontRecord> bakedFonts = {};
std::vector<GlyphRecord> glyphRecords = {};
std::unordered_map<std::uint64_t, std::size_t> glyphIndices = {};
};
namespace {
void ResetImpl(XCUIStandaloneTextAtlasProvider::Impl& impl) {
impl.ready = false;
impl.nominalSize = kDefaultNominalFontSize;
impl.alpha8Pixels.Clear();
impl.rgba32Pixels.Clear();
impl.bakedFonts.clear();
impl.glyphRecords.clear();
impl.glyphIndices.clear();
++impl.pixelGeneration;
}
bool RebuildAtlasPixels(XCUIStandaloneTextAtlasProvider::Impl& impl) {
int chosenAtlasWidth = 0;
int chosenAtlasHeight = 1;
for (const int candidateWidth : { 1024, 2048, 4096 }) {
int packedHeight = 1;
if (TryPackVisibleGlyphs(impl.glyphRecords, candidateWidth, packedHeight)) {
chosenAtlasWidth = candidateWidth;
chosenAtlasHeight = packedHeight;
break;
}
}
if (chosenAtlasWidth == 0 || chosenAtlasHeight <= 0) {
return false;
}
impl.alpha8Pixels.width = chosenAtlasWidth;
impl.alpha8Pixels.height = chosenAtlasHeight;
impl.alpha8Pixels.bytesPerPixel = 1;
impl.alpha8Pixels.bytes.assign(
static_cast<std::size_t>(chosenAtlasWidth) * static_cast<std::size_t>(chosenAtlasHeight),
0u);
for (const GlyphRecord& glyph : impl.glyphRecords) {
if (!glyph.visible || glyph.bitmapWidth <= 0 || glyph.bitmapHeight <= 0) {
continue;
}
for (int y = 0; y < glyph.bitmapHeight; ++y) {
const unsigned char* sourceRow = glyph.alpha.data() +
static_cast<std::size_t>(y * glyph.bitmapWidth);
unsigned char* destRow = impl.alpha8Pixels.bytes.data() +
static_cast<std::size_t>((glyph.atlasY + y) * impl.alpha8Pixels.width + glyph.atlasX);
std::copy(sourceRow, sourceRow + glyph.bitmapWidth, destRow);
}
}
impl.rgba32Pixels.width = chosenAtlasWidth;
impl.rgba32Pixels.height = chosenAtlasHeight;
impl.rgba32Pixels.bytesPerPixel = 4;
impl.rgba32Pixels.bytes.assign(
static_cast<std::size_t>(chosenAtlasWidth) *
static_cast<std::size_t>(chosenAtlasHeight) *
4u,
0u);
for (std::size_t pixelIndex = 0; pixelIndex < impl.alpha8Pixels.bytes.size(); ++pixelIndex) {
const unsigned char alpha = impl.alpha8Pixels.bytes[pixelIndex];
unsigned char* rgba = impl.rgba32Pixels.bytes.data() + pixelIndex * 4u;
rgba[0] = 255u;
rgba[1] = 255u;
rgba[2] = 255u;
rgba[3] = alpha;
}
for (auto& [sizeKey, bakedFont] : impl.bakedFonts) {
(void)sizeKey;
bakedFont.glyphs.clear();
}
for (const GlyphRecord& glyph : impl.glyphRecords) {
IXCUITextAtlasProvider::GlyphInfo glyphInfo = {};
if (!PopulateGlyphInfo(
glyph,
impl.alpha8Pixels.width,
impl.alpha8Pixels.height,
glyphInfo)) {
return false;
}
impl.bakedFonts[glyph.sizeKey].glyphs.insert_or_assign(glyph.codepoint, glyphInfo);
}
++impl.pixelGeneration;
return true;
}
bool AddGlyphRecord(
XCUIStandaloneTextAtlasProvider::Impl& impl,
int sizeKey,
std::uint32_t codepoint) {
const std::uint64_t glyphKey = MakeGlyphKey(sizeKey, codepoint);
if (impl.glyphIndices.find(glyphKey) != impl.glyphIndices.end()) {
return true;
}
GdiFontContext context(sizeKey);
if (!context.IsValid()) {
return false;
}
if (impl.bakedFonts.find(sizeKey) == impl.bakedFonts.end()) {
BakedFontRecord bakedFont = {};
bakedFont.sizeKey = sizeKey;
bakedFont.metrics = context.BuildBakedFontInfo();
impl.bakedFonts.insert_or_assign(sizeKey, std::move(bakedFont));
}
GlyphRecord glyph = {};
if (!context.RasterizeGlyph(codepoint, glyph)) {
return false;
}
const std::size_t previousGlyphCount = impl.glyphRecords.size();
impl.glyphRecords.push_back(std::move(glyph));
impl.glyphIndices.insert_or_assign(glyphKey, previousGlyphCount);
if (!RebuildAtlasPixels(impl)) {
impl.glyphIndices.erase(glyphKey);
impl.glyphRecords.pop_back();
RebuildAtlasPixels(impl);
return false;
}
return true;
}
const BakedFontRecord* FindBakedFont(
const XCUIStandaloneTextAtlasProvider::Impl& impl,
int sizeKey) {
const auto it = impl.bakedFonts.find(sizeKey);
return it != impl.bakedFonts.end() ? &it->second : nullptr;
}
} // namespace
XCUIStandaloneTextAtlasProvider::XCUIStandaloneTextAtlasProvider()
: m_impl(std::make_unique<Impl>()) {
RebuildDefaultEditorAtlas();
}
XCUIStandaloneTextAtlasProvider::~XCUIStandaloneTextAtlasProvider() = default;
void XCUIStandaloneTextAtlasProvider::Reset() {
m_atlas.Clear();
m_defaultFont = nullptr;
m_ready = false;
if (m_impl == nullptr) {
m_impl = std::make_unique<Impl>();
return;
}
ResetImpl(*m_impl);
}
bool XCUIStandaloneTextAtlasProvider::RebuildDefaultEditorAtlas() {
Reset();
m_ready = BuildDefaultXCUIEditorFontAtlas(m_atlas, m_defaultFont);
return m_ready;
if (m_impl == nullptr) {
return false;
}
const std::vector<std::uint32_t> prebakedCodepoints = BuildPrebakedCodepointSet();
for (const int sizeKey : kSupportedFontSizes) {
GdiFontContext context(sizeKey);
if (!context.IsValid()) {
ResetImpl(*m_impl);
return false;
}
BakedFontRecord bakedFont = {};
bakedFont.sizeKey = sizeKey;
bakedFont.metrics = context.BuildBakedFontInfo();
m_impl->bakedFonts.insert_or_assign(sizeKey, std::move(bakedFont));
for (std::uint32_t codepoint : prebakedCodepoints) {
GlyphRecord glyph = {};
if (!context.RasterizeGlyph(codepoint, glyph)) {
continue;
}
const std::uint64_t glyphKey = MakeGlyphKey(sizeKey, codepoint);
m_impl->glyphIndices.insert_or_assign(glyphKey, m_impl->glyphRecords.size());
m_impl->glyphRecords.push_back(std::move(glyph));
}
}
if (m_impl->bakedFonts.empty() || !RebuildAtlasPixels(*m_impl)) {
ResetImpl(*m_impl);
return false;
}
m_impl->ready = true;
return true;
}
bool XCUIStandaloneTextAtlasProvider::IsReady() const {
return m_ready && ResolveDefaultFont() != nullptr;
return m_impl != nullptr &&
m_impl->ready &&
!m_impl->bakedFonts.empty() &&
m_impl->rgba32Pixels.IsValid();
}
bool XCUIStandaloneTextAtlasProvider::GetAtlasTextureView(
@@ -74,74 +712,59 @@ bool XCUIStandaloneTextAtlasProvider::GetAtlasTextureView(
AtlasTextureView& outView) const {
outView = {};
ImFontAtlas* atlas = ResolveAtlas();
if (atlas == nullptr) {
if (!IsReady() || m_impl == nullptr) {
return false;
}
unsigned char* pixels = nullptr;
int width = 0;
int height = 0;
int bytesPerPixel = 0;
PixelFormat resolvedFormat = preferredFormat;
switch (preferredFormat) {
case PixelFormat::Alpha8:
atlas->GetTexDataAsAlpha8(&pixels, &width, &height, &bytesPerPixel);
const CachedPixelBuffer* resolvedPixels = nullptr;
PixelFormat resolvedFormat = PixelFormat::Unknown;
if (preferredFormat == PixelFormat::Alpha8 && m_impl->alpha8Pixels.IsValid()) {
resolvedPixels = &m_impl->alpha8Pixels;
resolvedFormat = PixelFormat::Alpha8;
break;
case PixelFormat::RGBA32:
case PixelFormat::Unknown:
default:
atlas->GetTexDataAsRGBA32(&pixels, &width, &height, &bytesPerPixel);
} else if (m_impl->rgba32Pixels.IsValid()) {
resolvedPixels = &m_impl->rgba32Pixels;
resolvedFormat = PixelFormat::RGBA32;
break;
} else if (m_impl->alpha8Pixels.IsValid()) {
resolvedPixels = &m_impl->alpha8Pixels;
resolvedFormat = PixelFormat::Alpha8;
}
if (pixels == nullptr || width <= 0 || height <= 0 || bytesPerPixel <= 0) {
if (resolvedPixels == nullptr || !resolvedPixels->IsValid()) {
return false;
}
outView.pixels = pixels;
outView.width = width;
outView.height = height;
outView.stride = width * bytesPerPixel;
outView.bytesPerPixel = bytesPerPixel;
outView.pixels = resolvedPixels->bytes.data();
outView.width = resolvedPixels->width;
outView.height = resolvedPixels->height;
outView.stride = resolvedPixels->width * resolvedPixels->bytesPerPixel;
outView.bytesPerPixel = resolvedPixels->bytesPerPixel;
outView.format = resolvedFormat;
outView.atlasStorageKey = reinterpret_cast<std::uintptr_t>(atlas);
outView.pixelDataKey = reinterpret_cast<std::uintptr_t>(pixels);
outView.atlasStorageKey = reinterpret_cast<std::uintptr_t>(m_impl.get());
outView.pixelDataKey = MakePixelDataKey(*resolvedPixels, m_impl->pixelGeneration);
return true;
}
std::size_t XCUIStandaloneTextAtlasProvider::GetFontCount() const {
const ImFontAtlas* atlas = ResolveAtlas();
return atlas != nullptr ? static_cast<std::size_t>(atlas->Fonts.Size) : 0u;
return IsReady() ? 1u : 0u;
}
IXCUITextAtlasProvider::FontHandle XCUIStandaloneTextAtlasProvider::GetFont(std::size_t index) const {
const ImFontAtlas* atlas = ResolveAtlas();
if (atlas == nullptr || index >= static_cast<std::size_t>(atlas->Fonts.Size)) {
return {};
}
return MakeFontHandle(atlas->Fonts[static_cast<int>(index)]);
return IsReady() && index == 0u ? MakeDefaultFontHandle() : FontHandle{};
}
IXCUITextAtlasProvider::FontHandle XCUIStandaloneTextAtlasProvider::GetDefaultFont() const {
return MakeFontHandle(ResolveDefaultFont());
return IsReady() ? MakeDefaultFontHandle() : FontHandle{};
}
bool XCUIStandaloneTextAtlasProvider::GetFontInfo(FontHandle fontHandle, FontInfo& outInfo) const {
outInfo = {};
ImFontAtlas* atlas = ResolveAtlas();
ImFont* font = ResolveFontHandle(fontHandle);
if (!IsFontOwnedByAtlas(font, atlas)) {
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
return false;
}
outInfo.handle = fontHandle;
outInfo.nominalSize = ResolveNominalFontSize(*font);
outInfo.nominalSize = m_impl->nominalSize;
return true;
}
@@ -151,15 +774,17 @@ bool XCUIStandaloneTextAtlasProvider::GetBakedFontInfo(
BakedFontInfo& outInfo) const {
outInfo = {};
ImFontBaked* bakedFont = ResolveBakedFont(fontHandle, ResolveAtlas(), fontSize);
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
return false;
}
const int sizeKey = ResolveSupportedSizeKey(fontSize, m_impl->nominalSize);
const BakedFontRecord* bakedFont = FindBakedFont(*m_impl, sizeKey);
if (bakedFont == nullptr) {
return false;
}
outInfo.lineHeight = bakedFont->Size;
outInfo.ascent = bakedFont->Ascent;
outInfo.descent = bakedFont->Descent;
outInfo.rasterizerDensity = bakedFont->RasterizerDensity;
outInfo = bakedFont->metrics;
return true;
}
@@ -170,53 +795,48 @@ bool XCUIStandaloneTextAtlasProvider::FindGlyph(
GlyphInfo& outInfo) const {
outInfo = {};
if (codepoint > IM_UNICODE_CODEPOINT_MAX) {
if (!IsReady() || ResolveFontIndex(fontHandle) == kInvalidFontIndex || m_impl == nullptr) {
return false;
}
ImFontBaked* bakedFont = ResolveBakedFont(fontHandle, ResolveAtlas(), fontSize);
const int sizeKey = ResolveSupportedSizeKey(fontSize, m_impl->nominalSize);
const BakedFontRecord* bakedFont = FindBakedFont(*m_impl, sizeKey);
if (bakedFont == nullptr) {
return false;
}
ImFontGlyph* glyph = bakedFont->FindGlyph(static_cast<ImWchar>(codepoint));
if (glyph == nullptr) {
const auto glyphIt = bakedFont->glyphs.find(codepoint);
if (glyphIt != bakedFont->glyphs.end()) {
outInfo = glyphIt->second;
return true;
}
if (codepoint <= 0xFFFFu && AddGlyphRecord(*m_impl, sizeKey, codepoint)) {
const BakedFontRecord* refreshedBakedFont = FindBakedFont(*m_impl, sizeKey);
if (refreshedBakedFont != nullptr) {
const auto refreshedGlyphIt = refreshedBakedFont->glyphs.find(codepoint);
if (refreshedGlyphIt != refreshedBakedFont->glyphs.end()) {
outInfo = refreshedGlyphIt->second;
return true;
}
}
}
const BakedFontRecord* fallbackBakedFont = FindBakedFont(*m_impl, sizeKey);
if (fallbackBakedFont == nullptr) {
return false;
}
const auto fallbackGlyphIt = fallbackBakedFont->glyphs.find(static_cast<std::uint32_t>('?'));
if (fallbackGlyphIt == fallbackBakedFont->glyphs.end()) {
return false;
}
outInfo = fallbackGlyphIt->second;
outInfo.requestedCodepoint = codepoint;
outInfo.resolvedCodepoint = glyph->Codepoint;
outInfo.visible = glyph->Visible != 0;
outInfo.colored = glyph->Colored != 0;
outInfo.advanceX = glyph->AdvanceX;
outInfo.x0 = glyph->X0;
outInfo.y0 = glyph->Y0;
outInfo.x1 = glyph->X1;
outInfo.y1 = glyph->Y1;
outInfo.u0 = glyph->U0;
outInfo.v0 = glyph->V0;
outInfo.u1 = glyph->U1;
outInfo.v1 = glyph->V1;
return true;
}
::ImFontAtlas* XCUIStandaloneTextAtlasProvider::ResolveAtlas() const {
return m_ready ? &m_atlas : nullptr;
}
::ImFont* XCUIStandaloneTextAtlasProvider::ResolveDefaultFont() const {
ImFontAtlas* atlas = ResolveAtlas();
if (atlas == nullptr) {
return nullptr;
}
if (IsFontOwnedByAtlas(m_defaultFont, atlas)) {
return m_defaultFont;
}
return atlas->Fonts.empty() ? nullptr : atlas->Fonts[0];
}
} // namespace XCUIBackend
} // namespace Editor
} // namespace XCEngine

View File

@@ -3,7 +3,7 @@
#include "IXCUITextAtlasProvider.h"
#include "XCUIEditorFontSetup.h"
#include <imgui.h>
#include <memory>
namespace XCEngine {
namespace Editor {
@@ -12,6 +12,7 @@ namespace XCUIBackend {
class XCUIStandaloneTextAtlasProvider final : public IXCUITextAtlasProvider {
public:
XCUIStandaloneTextAtlasProvider();
~XCUIStandaloneTextAtlasProvider();
XCUIStandaloneTextAtlasProvider(const XCUIStandaloneTextAtlasProvider&) = delete;
XCUIStandaloneTextAtlasProvider& operator=(const XCUIStandaloneTextAtlasProvider&) = delete;
@@ -28,13 +29,10 @@ public:
bool GetBakedFontInfo(FontHandle font, float fontSize, BakedFontInfo& outInfo) const override;
bool FindGlyph(FontHandle font, float fontSize, std::uint32_t codepoint, GlyphInfo& outInfo) const override;
private:
::ImFontAtlas* ResolveAtlas() const;
::ImFont* ResolveDefaultFont() const;
struct Impl;
mutable ::ImFontAtlas m_atlas = {};
::ImFont* m_defaultFont = nullptr;
bool m_ready = false;
private:
std::unique_ptr<Impl> m_impl;
};
} // namespace XCUIBackend