2026-04-21 20:49:18 +08:00
|
|
|
#include "D3D12UiTextSystem.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <atomic>
|
|
|
|
|
#include <cmath>
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
|
|
|
|
|
namespace XCEngine::UI::Editor::Host {
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
|
|
|
|
constexpr float kDefaultFontSize = 16.0f;
|
2026-04-21 21:37:52 +08:00
|
|
|
constexpr float kTextVisualScaleCompensation = 1.08f;
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
float ResolveDisplayFontSize(float fontSize) {
|
|
|
|
|
return ResolveFontSize(fontSize) * kTextVisualScaleCompensation;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
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;
|
2026-04-21 21:07:06 +08:00
|
|
|
std::uint32_t coverageSum = 0u;
|
2026-04-21 20:49:18 +08:00
|
|
|
for (std::size_t channel = 0u; channel < channelCount; ++channel) {
|
2026-04-21 21:07:06 +08:00
|
|
|
coverageSum += static_cast<std::uint32_t>(alphaTexture[srcOffset + channel]);
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
2026-04-21 21:07:06 +08:00
|
|
|
const std::uint8_t coverage = static_cast<std::uint8_t>(
|
|
|
|
|
(coverageSum + static_cast<std::uint32_t>(channelCount / 2u)) /
|
|
|
|
|
static_cast<std::uint32_t>(channelCount));
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
} // 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);
|
2026-04-21 21:37:52 +08:00
|
|
|
const float resolvedFontSize = ResolveDisplayFontSize(fontSize);
|
|
|
|
|
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
|
2026-04-21 20:49:18 +08:00
|
|
|
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,
|
2026-04-21 21:37:52 +08:00
|
|
|
resolvedFontSize * 2.0f,
|
2026-04-21 20:49:18 +08:00
|
|
|
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;
|
2026-04-21 21:37:52 +08:00
|
|
|
outRun.height = (std::max)(textMetrics.height, resolvedFontSize * 1.6f);
|
2026-04-21 20:49:18 +08:00
|
|
|
|
|
|
|
|
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);
|
2026-04-21 21:37:52 +08:00
|
|
|
const float resolvedFontSize = ResolveDisplayFontSize(request.fontSize);
|
|
|
|
|
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
|
2026-04-21 20:49:18 +08:00
|
|
|
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,
|
2026-04-21 21:37:52 +08:00
|
|
|
resolvedFontSize * 2.0f,
|
2026-04-21 20:49:18 +08:00
|
|
|
textLayout.ReleaseAndGetAddressOf());
|
|
|
|
|
if (FAILED(hr) || textLayout == nullptr) {
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DWRITE_TEXT_METRICS textMetrics = {};
|
|
|
|
|
hr = textLayout->GetMetrics(&textMetrics);
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
return 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
return std::ceil(width * dpiScale) / dpiScale;
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-23 14:11:33 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
bool D3D12UiTextSystem::RasterizeTextMask(
|
|
|
|
|
std::string_view text,
|
|
|
|
|
float fontSize,
|
2026-04-21 21:07:06 +08:00
|
|
|
RasterizedTextRun& outRun,
|
2026-04-21 20:49:18 +08:00
|
|
|
std::string& outError) {
|
2026-04-21 21:07:06 +08:00
|
|
|
outRun = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
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);
|
2026-04-21 21:37:52 +08:00
|
|
|
const float resolvedFontSize = ResolveDisplayFontSize(fontSize);
|
|
|
|
|
IDWriteTextFormat* textFormat = GetTextFormat(resolvedFontSize);
|
2026-04-21 20:49:18 +08:00
|
|
|
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,
|
2026-04-21 21:37:52 +08:00
|
|
|
resolvedFontSize * 2.0f,
|
2026-04-21 20:49:18 +08:00
|
|
|
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,
|
2026-04-21 21:37:52 +08:00
|
|
|
0.0f,
|
|
|
|
|
0.0f);
|
2026-04-21 20:49:18 +08:00
|
|
|
const std::vector<GlyphRunRecord> glyphRuns = collector->GetGlyphRuns();
|
|
|
|
|
collector->Release();
|
|
|
|
|
if (FAILED(hr)) {
|
|
|
|
|
outError = HrToString("IDWriteTextLayout::Draw", hr);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
bool hasUnionBounds = false;
|
|
|
|
|
RECT unionBounds = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
for (const GlyphRunRecord& record : glyphRuns) {
|
2026-04-21 21:37:52 +08:00
|
|
|
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
|
|
|
|
|
RECT bounds = {};
|
|
|
|
|
if (!AnalyzeGlyphRun(
|
|
|
|
|
*m_dwriteFactory.Get(),
|
|
|
|
|
record,
|
|
|
|
|
dpiScale,
|
|
|
|
|
analysis,
|
|
|
|
|
bounds,
|
|
|
|
|
outError)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!HasArea(bounds)) {
|
2026-04-21 20:49:18 +08:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
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;
|
|
|
|
|
}
|
2026-04-21 20:49:18 +08:00
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
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;
|
2026-04-21 20:49:18 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
Microsoft::WRL::ComPtr<IDWriteGlyphRunAnalysis> analysis = {};
|
2026-04-21 20:49:18 +08:00
|
|
|
RECT bounds = {};
|
2026-04-21 21:37:52 +08:00
|
|
|
if (!AnalyzeGlyphRun(
|
|
|
|
|
*m_dwriteFactory.Get(),
|
|
|
|
|
record,
|
|
|
|
|
dpiScale,
|
|
|
|
|
analysis,
|
|
|
|
|
bounds,
|
|
|
|
|
outError)) {
|
2026-04-21 20:49:18 +08:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-21 21:37:52 +08:00
|
|
|
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;
|
|
|
|
|
|
2026-04-21 20:49:18 +08:00
|
|
|
CompositeCoverage(
|
2026-04-21 21:07:06 +08:00
|
|
|
outRun.rgbaPixels,
|
|
|
|
|
outRun.width,
|
|
|
|
|
outRun.height,
|
2026-04-21 21:37:52 +08:00
|
|
|
localBounds,
|
2026-04-21 20:49:18 +08:00
|
|
|
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
|