#include "D3D12UiTextSystem.h" #include #include #include #include namespace XCEngine::UI::Editor::Host { namespace { constexpr float kDefaultFontSize = 16.0f; constexpr float kTextVisualScaleCompensation = 1.08f; std::string HrToString(const char* operation, HRESULT hr) { char buffer[128] = {}; sprintf_s(buffer, "%s failed with hr=0x%08X.", operation, static_cast(hr)); return buffer; } float ClampDpiScale(float dpiScale) { return dpiScale > 0.0f ? dpiScale : 1.0f; } float ResolveFontSize(float fontSize) { return fontSize > 0.0f ? fontSize : kDefaultFontSize; } float ResolveDisplayFontSize(float fontSize) { return ResolveFontSize(fontSize) * kTextVisualScaleCompensation; } struct GlyphRunRecord { Microsoft::WRL::ComPtr fontFace = {}; std::vector glyphIndices = {}; std::vector glyphAdvances = {}; std::vector glyphOffsets = {}; FLOAT fontEmSize = 0.0f; FLOAT baselineOriginX = 0.0f; FLOAT baselineOriginY = 0.0f; DWRITE_MEASURING_MODE measuringMode = DWRITE_MEASURING_MODE_NATURAL; BOOL isSideways = FALSE; UINT32 bidiLevel = 0u; }; class GlyphRunCollector final : public IDWriteTextRenderer { public: explicit GlyphRunCollector(float pixelsPerDip) : m_pixelsPerDip(ClampDpiScale(pixelsPerDip)) { } const std::vector& GetGlyphRuns() const { return m_glyphRuns; } HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** object) override { if (object == nullptr) { return E_INVALIDARG; } *object = nullptr; if (riid == __uuidof(IUnknown) || riid == __uuidof(IDWritePixelSnapping) || riid == __uuidof(IDWriteTextRenderer)) { *object = static_cast(this); AddRef(); return S_OK; } return E_NOINTERFACE; } ULONG STDMETHODCALLTYPE AddRef() override { return ++m_refCount; } ULONG STDMETHODCALLTYPE Release() override { const ULONG refCount = --m_refCount; if (refCount == 0u) { delete this; } return refCount; } HRESULT STDMETHODCALLTYPE IsPixelSnappingDisabled( void*, BOOL* isDisabled) override { if (isDisabled == nullptr) { return E_INVALIDARG; } *isDisabled = FALSE; return S_OK; } HRESULT STDMETHODCALLTYPE GetCurrentTransform( void*, DWRITE_MATRIX* transform) override { if (transform == nullptr) { return E_INVALIDARG; } *transform = DWRITE_MATRIX{ 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f }; return S_OK; } HRESULT STDMETHODCALLTYPE GetPixelsPerDip( void*, FLOAT* pixelsPerDip) override { if (pixelsPerDip == nullptr) { return E_INVALIDARG; } *pixelsPerDip = m_pixelsPerDip; return S_OK; } HRESULT STDMETHODCALLTYPE DrawGlyphRun( void*, FLOAT baselineOriginX, FLOAT baselineOriginY, DWRITE_MEASURING_MODE measuringMode, const DWRITE_GLYPH_RUN* glyphRun, const DWRITE_GLYPH_RUN_DESCRIPTION*, IUnknown*) override { if (glyphRun == nullptr || glyphRun->fontFace == nullptr || glyphRun->glyphCount == 0u || glyphRun->glyphIndices == nullptr) { return S_OK; } GlyphRunRecord record = {}; record.fontFace = glyphRun->fontFace; record.glyphIndices.assign( glyphRun->glyphIndices, glyphRun->glyphIndices + glyphRun->glyphCount); record.glyphAdvances.resize(glyphRun->glyphCount, 0.0f); record.glyphOffsets.resize(glyphRun->glyphCount, DWRITE_GLYPH_OFFSET{}); if (glyphRun->glyphAdvances != nullptr) { std::copy( glyphRun->glyphAdvances, glyphRun->glyphAdvances + glyphRun->glyphCount, record.glyphAdvances.begin()); } if (glyphRun->glyphOffsets != nullptr) { std::copy( glyphRun->glyphOffsets, glyphRun->glyphOffsets + glyphRun->glyphCount, record.glyphOffsets.begin()); } record.fontEmSize = glyphRun->fontEmSize; record.baselineOriginX = baselineOriginX; record.baselineOriginY = baselineOriginY; record.measuringMode = measuringMode; record.isSideways = glyphRun->isSideways; record.bidiLevel = glyphRun->bidiLevel; m_glyphRuns.push_back(std::move(record)); return S_OK; } HRESULT STDMETHODCALLTYPE DrawUnderline( void*, FLOAT, FLOAT, const DWRITE_UNDERLINE*, IUnknown*) override { return S_OK; } HRESULT STDMETHODCALLTYPE DrawStrikethrough( void*, FLOAT, FLOAT, const DWRITE_STRIKETHROUGH*, IUnknown*) override { return S_OK; } HRESULT STDMETHODCALLTYPE DrawInlineObject( void*, FLOAT, FLOAT, IDWriteInlineObject*, BOOL, BOOL, IUnknown*) override { return S_OK; } private: std::atomic m_refCount = 1u; float m_pixelsPerDip = 1.0f; std::vector m_glyphRuns = {}; }; void CompositeCoverage( std::vector& rgbaPixels, UINT targetWidth, UINT targetHeight, const RECT& bounds, const std::uint8_t* alphaTexture, std::size_t textureStride, std::size_t channelCount) { if (alphaTexture == nullptr || channelCount == 0u) { return; } const LONG width = bounds.right - bounds.left; const LONG height = bounds.bottom - bounds.top; if (width <= 0 || height <= 0) { return; } for (LONG row = 0; row < height; ++row) { const LONG dstY = bounds.top + row; if (dstY < 0 || dstY >= static_cast(targetHeight)) { continue; } for (LONG column = 0; column < width; ++column) { const LONG dstX = bounds.left + column; if (dstX < 0 || dstX >= static_cast(targetWidth)) { continue; } const std::size_t srcOffset = static_cast(row) * textureStride + static_cast(column) * channelCount; std::uint32_t coverageSum = 0u; for (std::size_t channel = 0u; channel < channelCount; ++channel) { coverageSum += static_cast(alphaTexture[srcOffset + channel]); } const std::uint8_t coverage = static_cast( (coverageSum + static_cast(channelCount / 2u)) / static_cast(channelCount)); if (coverage == 0u) { continue; } const std::size_t dstOffset = (static_cast(dstY) * static_cast(targetWidth) + static_cast(dstX)) * 4u; std::uint8_t& dstAlpha = rgbaPixels[dstOffset + 3u]; const std::uint32_t blendedAlpha = static_cast(dstAlpha) + ((255u - static_cast(dstAlpha)) * static_cast(coverage) + 127u) / 255u; dstAlpha = static_cast((std::min)(blendedAlpha, 255u)); rgbaPixels[dstOffset + 0u] = 255u; rgbaPixels[dstOffset + 1u] = 255u; rgbaPixels[dstOffset + 2u] = 255u; } } } bool CreateGlyphRunFromRecord( const GlyphRunRecord& record, DWRITE_GLYPH_RUN& outGlyphRun) { if (record.fontFace == nullptr || record.glyphIndices.empty()) { outGlyphRun = {}; return false; } outGlyphRun = {}; outGlyphRun.fontFace = record.fontFace.Get(); outGlyphRun.fontEmSize = record.fontEmSize; outGlyphRun.glyphCount = static_cast(record.glyphIndices.size()); outGlyphRun.glyphIndices = record.glyphIndices.data(); outGlyphRun.glyphAdvances = record.glyphAdvances.data(); outGlyphRun.glyphOffsets = record.glyphOffsets.empty() ? nullptr : record.glyphOffsets.data(); outGlyphRun.isSideways = record.isSideways; outGlyphRun.bidiLevel = record.bidiLevel; return true; } bool AnalyzeGlyphRun( IDWriteFactory& dwriteFactory, const GlyphRunRecord& record, float dpiScale, Microsoft::WRL::ComPtr& outAnalysis, RECT& outBounds, std::string& outError) { outAnalysis.Reset(); outBounds = {}; outError.clear(); DWRITE_GLYPH_RUN glyphRun = {}; if (!CreateGlyphRunFromRecord(record, glyphRun)) { return true; } HRESULT hr = dwriteFactory.CreateGlyphRunAnalysis( &glyphRun, dpiScale, nullptr, DWRITE_RENDERING_MODE_NATURAL, record.measuringMode, record.baselineOriginX, record.baselineOriginY, outAnalysis.ReleaseAndGetAddressOf()); if (FAILED(hr) || outAnalysis == nullptr) { outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr); return false; } hr = outAnalysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &outBounds); if (FAILED(hr)) { outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr); return false; } return true; } bool HasArea(const RECT& rect) { return rect.right > rect.left && rect.bottom > rect.top; } RECT UnionBounds(const RECT& lhs, const RECT& rhs) { RECT result = {}; result.left = (std::min)(lhs.left, rhs.left); result.top = (std::min)(lhs.top, rhs.top); result.right = (std::max)(lhs.right, rhs.right); result.bottom = (std::max)(lhs.bottom, rhs.bottom); return result; } } // namespace bool D3D12UiTextSystem::Initialize() { Shutdown(); const HRESULT hr = DWriteCreateFactory( DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(m_dwriteFactory.ReleaseAndGetAddressOf())); if (FAILED(hr)) { const std::string error = HrToString("DWriteCreateFactory", hr); Shutdown(); m_lastError = error; return false; } m_lastError.clear(); return true; } void D3D12UiTextSystem::Shutdown() { m_textFormats.clear(); m_dwriteFactory.Reset(); m_lastError.clear(); } void D3D12UiTextSystem::SetDpiScale(float dpiScale) { m_dpiScale = ClampDpiScale(dpiScale); } float D3D12UiTextSystem::GetDpiScale() const { return m_dpiScale; } const std::string& D3D12UiTextSystem::GetLastError() const { return m_lastError; } bool D3D12UiTextSystem::ShapeTextRun( std::string_view text, float fontSize, ShapedTextRun& outRun, std::string& outError) const { outRun = {}; outError.clear(); if (!m_dwriteFactory) { outError = "ShapeTextRun requires an initialized DirectWrite factory."; return false; } if (text.empty()) { return true; } const std::wstring wideText = Utf8ToWide(text); if (wideText.empty()) { outError = "ShapeTextRun could not convert UTF-8 text to UTF-16."; return false; } const float dpiScale = ClampDpiScale(m_dpiScale); const float resolvedFontSize = ResolveDisplayFontSize(fontSize); IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize); if (textFormat == nullptr) { outError = "ShapeTextRun could not resolve the DirectWrite text format."; return false; } Microsoft::WRL::ComPtr textLayout = {}; HRESULT hr = m_dwriteFactory->CreateTextLayout( wideText.c_str(), static_cast(wideText.size()), textFormat, 4096.0f, resolvedFontSize * 2.0f, textLayout.ReleaseAndGetAddressOf()); if (FAILED(hr) || textLayout == nullptr) { outError = HrToString("IDWriteFactory::CreateTextLayout", hr); return false; } DWRITE_TEXT_METRICS textMetrics = {}; hr = textLayout->GetMetrics(&textMetrics); if (FAILED(hr)) { outError = HrToString("IDWriteTextLayout::GetMetrics", hr); return false; } outRun.width = textMetrics.widthIncludingTrailingWhitespace; outRun.height = (std::max)(textMetrics.height, resolvedFontSize * 1.6f); GlyphRunCollector* collector = new GlyphRunCollector(dpiScale); hr = textLayout->Draw( nullptr, collector, 0.0f, 0.0f); const std::vector glyphRuns = collector->GetGlyphRuns(); collector->Release(); if (FAILED(hr)) { outError = HrToString("IDWriteTextLayout::Draw", hr); return false; } for (const GlyphRunRecord& record : glyphRuns) { if (record.fontFace == nullptr || record.glyphIndices.empty()) { continue; } float advanceCursor = 0.0f; for (std::size_t glyphIndex = 0u; glyphIndex < record.glyphIndices.size(); ++glyphIndex) { const DWRITE_GLYPH_OFFSET glyphOffset = glyphIndex < record.glyphOffsets.size() ? record.glyphOffsets[glyphIndex] : DWRITE_GLYPH_OFFSET{}; ShapedGlyph shapedGlyph = {}; shapedGlyph.fontFace = record.fontFace; shapedGlyph.glyphIndex = record.glyphIndices[glyphIndex]; shapedGlyph.fontEmSize = record.fontEmSize; shapedGlyph.originX = record.baselineOriginX + advanceCursor + glyphOffset.advanceOffset; shapedGlyph.originY = record.baselineOriginY + glyphOffset.ascenderOffset; shapedGlyph.measuringMode = record.measuringMode; shapedGlyph.isSideways = record.isSideways != FALSE; outRun.glyphs.push_back(std::move(shapedGlyph)); if (glyphIndex < record.glyphAdvances.size()) { advanceCursor += record.glyphAdvances[glyphIndex]; } } } return true; } bool D3D12UiTextSystem::RasterizeGlyph( const ShapedGlyph& glyph, RasterizedGlyph& outGlyph, std::string& outError) const { outGlyph = {}; outError.clear(); if (!m_dwriteFactory) { outError = "RasterizeGlyph requires an initialized DirectWrite factory."; return false; } if (glyph.fontFace == nullptr) { outError = "RasterizeGlyph requires a valid font face."; return false; } const UINT16 glyphIndices[] = { glyph.glyphIndex }; const FLOAT glyphAdvances[] = { 0.0f }; const DWRITE_GLYPH_OFFSET glyphOffsets[] = { DWRITE_GLYPH_OFFSET{} }; DWRITE_GLYPH_RUN glyphRun = {}; glyphRun.fontFace = glyph.fontFace.Get(); glyphRun.fontEmSize = glyph.fontEmSize; glyphRun.glyphCount = 1u; glyphRun.glyphIndices = glyphIndices; glyphRun.glyphAdvances = glyphAdvances; glyphRun.glyphOffsets = glyphOffsets; glyphRun.isSideways = glyph.isSideways ? TRUE : FALSE; glyphRun.bidiLevel = 0u; Microsoft::WRL::ComPtr analysis = {}; const float dpiScale = ClampDpiScale(m_dpiScale); HRESULT hr = m_dwriteFactory->CreateGlyphRunAnalysis( &glyphRun, dpiScale, nullptr, DWRITE_RENDERING_MODE_NATURAL, glyph.measuringMode, 0.0f, 0.0f, analysis.ReleaseAndGetAddressOf()); if (FAILED(hr) || analysis == nullptr) { outError = HrToString("IDWriteFactory::CreateGlyphRunAnalysis", hr); return false; } RECT bounds = {}; hr = analysis->GetAlphaTextureBounds(DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds); if (FAILED(hr)) { outError = HrToString("IDWriteGlyphRunAnalysis::GetAlphaTextureBounds", hr); return false; } outGlyph.boundsLeft = bounds.left; outGlyph.boundsTop = bounds.top; outGlyph.boundsRight = bounds.right; outGlyph.boundsBottom = bounds.bottom; const LONG boundsWidth = bounds.right - bounds.left; const LONG boundsHeight = bounds.bottom - bounds.top; if (boundsWidth <= 0 || boundsHeight <= 0) { return true; } std::vector alphaTexture( static_cast(boundsWidth) * static_cast(boundsHeight) * 3u); hr = analysis->CreateAlphaTexture( DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds, alphaTexture.data(), static_cast(alphaTexture.size())); if (FAILED(hr)) { outError = HrToString("IDWriteGlyphRunAnalysis::CreateAlphaTexture", hr); return false; } outGlyph.width = static_cast(boundsWidth); outGlyph.height = static_cast(boundsHeight); outGlyph.rgbaPixels.assign( static_cast(outGlyph.width) * static_cast(outGlyph.height) * 4u, 0u); CompositeCoverage( outGlyph.rgbaPixels, outGlyph.width, outGlyph.height, RECT{ 0, 0, boundsWidth, boundsHeight }, alphaTexture.data(), static_cast(boundsWidth) * 3u, 3u); return true; } float D3D12UiTextSystem::MeasureTextWidth( const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const { if (!m_dwriteFactory || request.text.empty()) { return 0.0f; } const std::wstring text = Utf8ToWide(request.text); if (text.empty()) { return 0.0f; } const float dpiScale = ClampDpiScale(m_dpiScale); const float resolvedFontSize = ResolveDisplayFontSize(request.fontSize); IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize); if (textFormat == nullptr) { return 0.0f; } Microsoft::WRL::ComPtr textLayout = {}; HRESULT hr = m_dwriteFactory->CreateTextLayout( text.c_str(), static_cast(text.size()), textFormat, 4096.0f, resolvedFontSize * 2.0f, textLayout.ReleaseAndGetAddressOf()); if (FAILED(hr) || textLayout == nullptr) { return 0.0f; } DWRITE_TEXT_METRICS textMetrics = {}; hr = textLayout->GetMetrics(&textMetrics); if (FAILED(hr)) { return 0.0f; } GlyphRunCollector* collector = new GlyphRunCollector(dpiScale); hr = textLayout->Draw( nullptr, collector, 0.0f, 0.0f); const std::vector glyphRuns = collector->GetGlyphRuns(); collector->Release(); if (FAILED(hr)) { return 0.0f; } bool hasUnionBounds = false; RECT unionBounds = {}; for (const GlyphRunRecord& record : glyphRuns) { Microsoft::WRL::ComPtr analysis = {}; RECT bounds = {}; std::string error = {}; if (!AnalyzeGlyphRun( *m_dwriteFactory.Get(), record, dpiScale, analysis, bounds, error)) { return 0.0f; } if (!HasArea(bounds)) { continue; } unionBounds = hasUnionBounds ? UnionBounds(unionBounds, bounds) : bounds; hasUnionBounds = true; } if (hasUnionBounds) { return static_cast(unionBounds.right - unionBounds.left) / dpiScale; } DWRITE_OVERHANG_METRICS overhangMetrics = {}; float width = textMetrics.widthIncludingTrailingWhitespace; if (SUCCEEDED(textLayout->GetOverhangMetrics(&overhangMetrics))) { width += (std::max)(overhangMetrics.left, 0.0f); width += (std::max)(overhangMetrics.right, 0.0f); } return std::ceil(width * dpiScale) / dpiScale; } float D3D12UiTextSystem::MeasureTextAdvance( const ::XCEngine::UI::Editor::UIEditorTextMeasureRequest& request) const { if (!m_dwriteFactory || request.text.empty()) { return 0.0f; } const std::wstring text = Utf8ToWide(request.text); if (text.empty()) { return 0.0f; } const float dpiScale = ClampDpiScale(m_dpiScale); const float resolvedFontSize = ResolveDisplayFontSize(request.fontSize); IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize); if (textFormat == nullptr) { return 0.0f; } Microsoft::WRL::ComPtr textLayout = {}; const HRESULT hr = m_dwriteFactory->CreateTextLayout( text.c_str(), static_cast(text.size()), textFormat, 4096.0f, resolvedFontSize * 2.0f, textLayout.ReleaseAndGetAddressOf()); if (FAILED(hr) || textLayout == nullptr) { return 0.0f; } DWRITE_TEXT_METRICS textMetrics = {}; if (FAILED(textLayout->GetMetrics(&textMetrics))) { return 0.0f; } return std::ceil(textMetrics.widthIncludingTrailingWhitespace * dpiScale) / dpiScale; } bool D3D12UiTextSystem::RasterizeTextMask( std::string_view text, float fontSize, RasterizedTextRun& outRun, std::string& outError) { outRun = {}; outError.clear(); if (!m_dwriteFactory) { outError = "RasterizeTextMask requires an initialized DirectWrite factory."; return false; } if (text.empty()) { outError = "RasterizeTextMask rejected an empty text payload."; return false; } const std::wstring wideText = Utf8ToWide(text); if (wideText.empty()) { outError = "RasterizeTextMask could not convert UTF-8 text to UTF-16."; return false; } const float dpiScale = ClampDpiScale(m_dpiScale); const float resolvedFontSize = ResolveDisplayFontSize(fontSize); IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize); if (textFormat == nullptr) { outError = "RasterizeTextMask could not resolve the DirectWrite text format."; return false; } Microsoft::WRL::ComPtr textLayout = {}; HRESULT hr = m_dwriteFactory->CreateTextLayout( wideText.c_str(), static_cast(wideText.size()), textFormat, 4096.0f, resolvedFontSize * 2.0f, textLayout.ReleaseAndGetAddressOf()); if (FAILED(hr) || textLayout == nullptr) { outError = HrToString("IDWriteFactory::CreateTextLayout", hr); return false; } DWRITE_TEXT_METRICS textMetrics = {}; hr = textLayout->GetMetrics(&textMetrics); if (FAILED(hr)) { outError = HrToString("IDWriteTextLayout::GetMetrics", hr); return false; } GlyphRunCollector* collector = new GlyphRunCollector(dpiScale); hr = textLayout->Draw( nullptr, collector, 0.0f, 0.0f); const std::vector glyphRuns = collector->GetGlyphRuns(); collector->Release(); if (FAILED(hr)) { outError = HrToString("IDWriteTextLayout::Draw", hr); return false; } bool hasUnionBounds = false; RECT unionBounds = {}; for (const GlyphRunRecord& record : glyphRuns) { Microsoft::WRL::ComPtr analysis = {}; RECT bounds = {}; if (!AnalyzeGlyphRun( *m_dwriteFactory.Get(), record, dpiScale, analysis, bounds, outError)) { return false; } if (!HasArea(bounds)) { continue; } unionBounds = hasUnionBounds ? UnionBounds(unionBounds, bounds) : bounds; hasUnionBounds = true; } if (!hasUnionBounds) { DWRITE_OVERHANG_METRICS overhangMetrics = {}; textLayout->GetOverhangMetrics(&overhangMetrics); const float leftPad = (std::max)(overhangMetrics.left, 0.0f); const float topPad = (std::max)(overhangMetrics.top, 0.0f); const float rightPad = (std::max)(overhangMetrics.right, 0.0f); const float bottomPad = (std::max)(overhangMetrics.bottom, 0.0f); outRun.width = (std::max)( 1u, static_cast(std::ceil( (textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad) * dpiScale))); outRun.height = (std::max)( 1u, static_cast(std::ceil( ((std::max)(textMetrics.height, resolvedFontSize * 1.6f) + topPad + bottomPad) * dpiScale))); outRun.offsetX = -leftPad * dpiScale; outRun.offsetY = -topPad * dpiScale; outRun.rgbaPixels.assign( static_cast(outRun.width) * static_cast(outRun.height) * 4u, 0u); return true; } outRun.width = static_cast(unionBounds.right - unionBounds.left); outRun.height = static_cast(unionBounds.bottom - unionBounds.top); outRun.offsetX = static_cast(unionBounds.left); outRun.offsetY = static_cast(unionBounds.top); outRun.rgbaPixels.assign( static_cast(outRun.width) * static_cast(outRun.height) * 4u, 0u); for (const GlyphRunRecord& record : glyphRuns) { if (record.fontFace == nullptr || record.glyphIndices.empty()) { continue; } Microsoft::WRL::ComPtr analysis = {}; RECT bounds = {}; if (!AnalyzeGlyphRun( *m_dwriteFactory.Get(), record, dpiScale, analysis, bounds, outError)) { return false; } const LONG boundsWidth = bounds.right - bounds.left; const LONG boundsHeight = bounds.bottom - bounds.top; if (boundsWidth <= 0 || boundsHeight <= 0) { continue; } std::vector alphaTexture( static_cast(boundsWidth) * static_cast(boundsHeight) * 3u); hr = analysis->CreateAlphaTexture( DWRITE_TEXTURE_CLEARTYPE_3x1, &bounds, alphaTexture.data(), static_cast(alphaTexture.size())); if (FAILED(hr)) { outError = HrToString("IDWriteGlyphRunAnalysis::CreateAlphaTexture", hr); return false; } RECT localBounds = {}; localBounds.left = bounds.left - unionBounds.left; localBounds.top = bounds.top - unionBounds.top; localBounds.right = bounds.right - unionBounds.left; localBounds.bottom = bounds.bottom - unionBounds.top; CompositeCoverage( outRun.rgbaPixels, outRun.width, outRun.height, localBounds, alphaTexture.data(), static_cast(boundsWidth) * 3u, 3u); } return true; } IDWriteTextFormat* D3D12UiTextSystem::GetTextFormat(float fontSize) const { if (!m_dwriteFactory) { return nullptr; } const float resolvedFontSize = ResolveFontSize(fontSize); const int key = static_cast(std::lround(resolvedFontSize * 10.0f)); const auto found = m_textFormats.find(key); if (found != m_textFormats.end()) { return found->second.Get(); } Microsoft::WRL::ComPtr textFormat = {}; const HRESULT hr = m_dwriteFactory->CreateTextFormat( L"Segoe UI", nullptr, DWRITE_FONT_WEIGHT_REGULAR, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, resolvedFontSize, L"", textFormat.ReleaseAndGetAddressOf()); if (FAILED(hr) || textFormat == nullptr) { return nullptr; } textFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP); IDWriteTextFormat* result = textFormat.Get(); m_textFormats.emplace(key, std::move(textFormat)); return result; } std::wstring D3D12UiTextSystem::Utf8ToWide(std::string_view text) { if (text.empty()) { return {}; } const int sizeNeeded = MultiByteToWideChar( CP_UTF8, 0, text.data(), static_cast(text.size()), nullptr, 0); if (sizeNeeded <= 0) { return {}; } std::wstring wideText(static_cast(sizeNeeded), L'\0'); MultiByteToWideChar( CP_UTF8, 0, text.data(), static_cast(text.size()), wideText.data(), sizeNeeded); return wideText; } } // namespace XCEngine::UI::Editor::Host