Files
XCEngine/new_editor/app/Rendering/D3D12/D3D12UiTextSystem.cpp

930 lines
28 KiB
C++
Raw Normal View History

#include "D3D12UiTextSystem.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstdio>
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<unsigned int>(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<IDWriteFontFace> fontFace = {};
std::vector<UINT16> glyphIndices = {};
std::vector<FLOAT> glyphAdvances = {};
std::vector<DWRITE_GLYPH_OFFSET> 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<GlyphRunRecord>& 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<IDWriteTextRenderer*>(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<ULONG> m_refCount = 1u;
float m_pixelsPerDip = 1.0f;
std::vector<GlyphRunRecord> m_glyphRuns = {};
};
void CompositeCoverage(
std::vector<std::uint8_t>& 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<LONG>(targetHeight)) {
continue;
}
for (LONG column = 0; column < width; ++column) {
const LONG dstX = bounds.left + column;
if (dstX < 0 || dstX >= static_cast<LONG>(targetWidth)) {
continue;
}
const std::size_t srcOffset =
static_cast<std::size_t>(row) * textureStride +
static_cast<std::size_t>(column) * channelCount;
std::uint32_t coverageSum = 0u;
for (std::size_t channel = 0u; channel < channelCount; ++channel) {
coverageSum += static_cast<std::uint32_t>(alphaTexture[srcOffset + channel]);
}
const std::uint8_t coverage = static_cast<std::uint8_t>(
(coverageSum + static_cast<std::uint32_t>(channelCount / 2u)) /
static_cast<std::uint32_t>(channelCount));
if (coverage == 0u) {
continue;
}
const std::size_t dstOffset =
(static_cast<std::size_t>(dstY) * static_cast<std::size_t>(targetWidth) +
static_cast<std::size_t>(dstX)) *
4u;
std::uint8_t& dstAlpha = rgbaPixels[dstOffset + 3u];
const std::uint32_t blendedAlpha =
static_cast<std::uint32_t>(dstAlpha) +
((255u - static_cast<std::uint32_t>(dstAlpha)) *
static_cast<std::uint32_t>(coverage) +
127u) /
255u;
dstAlpha = static_cast<std::uint8_t>((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<UINT32>(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<IDWriteGlyphRunAnalysis>& 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<IUnknown**>(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<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
wideText.c_str(),
static_cast<UINT32>(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<GlyphRunRecord> 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<IDWriteGlyphRunAnalysis> 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<std::uint8_t> alphaTexture(
static_cast<std::size_t>(boundsWidth) * static_cast<std::size_t>(boundsHeight) * 3u);
hr = analysis->CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
&bounds,
alphaTexture.data(),
static_cast<UINT32>(alphaTexture.size()));
if (FAILED(hr)) {
outError = HrToString("IDWriteGlyphRunAnalysis::CreateAlphaTexture", hr);
return false;
}
outGlyph.width = static_cast<UINT>(boundsWidth);
outGlyph.height = static_cast<UINT>(boundsHeight);
outGlyph.rgbaPixels.assign(
static_cast<std::size_t>(outGlyph.width) * static_cast<std::size_t>(outGlyph.height) * 4u,
0u);
CompositeCoverage(
outGlyph.rgbaPixels,
outGlyph.width,
outGlyph.height,
RECT{ 0, 0, boundsWidth, boundsHeight },
alphaTexture.data(),
static_cast<std::size_t>(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<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
text.c_str(),
static_cast<UINT32>(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<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
collector->Release();
if (FAILED(hr)) {
return 0.0f;
}
bool hasUnionBounds = false;
RECT unionBounds = {};
for (const GlyphRunRecord& record : glyphRuns) {
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> 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<float>(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<IDWriteTextLayout> textLayout = {};
const HRESULT hr = m_dwriteFactory->CreateTextLayout(
text.c_str(),
static_cast<UINT32>(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<IDWriteTextLayout> textLayout = {};
HRESULT hr = m_dwriteFactory->CreateTextLayout(
wideText.c_str(),
static_cast<UINT32>(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<GlyphRunRecord> 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<IDWriteGlyphRunAnalysis> 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<UINT>(std::ceil(
(textMetrics.widthIncludingTrailingWhitespace + leftPad + rightPad) * dpiScale)));
outRun.height = (std::max)(
1u,
static_cast<UINT>(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<std::size_t>(outRun.width) * static_cast<std::size_t>(outRun.height) * 4u,
0u);
return true;
}
outRun.width = static_cast<UINT>(unionBounds.right - unionBounds.left);
outRun.height = static_cast<UINT>(unionBounds.bottom - unionBounds.top);
outRun.offsetX = static_cast<float>(unionBounds.left);
outRun.offsetY = static_cast<float>(unionBounds.top);
outRun.rgbaPixels.assign(
static_cast<std::size_t>(outRun.width) * static_cast<std::size_t>(outRun.height) * 4u,
0u);
for (const GlyphRunRecord& record : glyphRuns) {
if (record.fontFace == nullptr || record.glyphIndices.empty()) {
continue;
}
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> 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<std::uint8_t> alphaTexture(
static_cast<std::size_t>(boundsWidth) * static_cast<std::size_t>(boundsHeight) * 3u);
hr = analysis->CreateAlphaTexture(
DWRITE_TEXTURE_CLEARTYPE_3x1,
&bounds,
alphaTexture.data(),
static_cast<UINT32>(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<std::size_t>(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<int>(std::lround(resolvedFontSize * 10.0f));
const auto found = m_textFormats.find(key);
if (found != m_textFormats.end()) {
return found->second.Get();
}
Microsoft::WRL::ComPtr<IDWriteTextFormat> 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<int>(text.size()),
nullptr,
0);
if (sizeNeeded <= 0) {
return {};
}
std::wstring wideText(static_cast<std::size_t>(sizeNeeded), L'\0');
MultiByteToWideChar(
CP_UTF8,
0,
text.data(),
static_cast<int>(text.size()),
wideText.data(),
sizeNeeded);
return wideText;
}
} // namespace XCEngine::UI::Editor::Host