1
0
mirror of https://github.com/ncblakely/GiantsTools synced 2024-12-23 15:57:22 +01:00
GiantsTools/Sdk/External/DirectXTK/Src/SpriteFont.cpp

655 lines
20 KiB
C++
Raw Normal View History

2021-01-24 00:40:09 +01:00
//--------------------------------------------------------------------------------------
// File: SpriteFont.cpp
//
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
//
// http://go.microsoft.com/fwlink/?LinkId=248929
//--------------------------------------------------------------------------------------
#include "pch.h"
#include <algorithm>
#include <vector>
#include "SpriteFont.h"
#include "DirectXHelpers.h"
#include "BinaryReader.h"
#include "LoaderHelpers.h"
using namespace DirectX;
using Microsoft::WRL::ComPtr;
// Internal SpriteFont implementation class.
class SpriteFont::Impl
{
public:
Impl(_In_ ID3D11Device* device,
_In_ BinaryReader* reader,
bool forceSRGB) noexcept(false);
Impl(_In_ ID3D11ShaderResourceView* texture,
_In_reads_(glyphCount) Glyph const* glyphs,
size_t glyphCount,
float lineSpacing) noexcept(false);
Glyph const* FindGlyph(wchar_t character) const;
void SetDefaultCharacter(wchar_t character);
template<typename TAction>
void ForEachGlyph(_In_z_ wchar_t const* text, TAction action, bool ignoreWhitespace) const;
void CreateTextureResource(_In_ ID3D11Device* device,
uint32_t width, uint32_t height,
DXGI_FORMAT format,
uint32_t stride, uint32_t rows,
_In_reads_(stride * rows) const uint8_t* data) noexcept(false);
const wchar_t* ConvertUTF8(_In_z_ const char *text) noexcept(false);
// Fields.
ComPtr<ID3D11ShaderResourceView> texture;
std::vector<Glyph> glyphs;
std::vector<uint32_t> glyphsIndex;
Glyph const* defaultGlyph;
float lineSpacing;
private:
size_t utfBufferSize;
std::unique_ptr<wchar_t[]> utfBuffer;
};
// Constants.
const XMFLOAT2 SpriteFont::Float2Zero(0, 0);
static const char spriteFontMagic[] = "DXTKfont";
// Comparison operators make our sorted glyph vector work with std::binary_search and lower_bound.
namespace DirectX
{
static inline bool operator< (SpriteFont::Glyph const& left, SpriteFont::Glyph const& right) noexcept
{
return left.Character < right.Character;
}
static inline bool operator< (wchar_t left, SpriteFont::Glyph const& right) noexcept
{
return left < right.Character;
}
static inline bool operator< (SpriteFont::Glyph const& left, wchar_t right) noexcept
{
return left.Character < right;
}
}
// Reads a SpriteFont from the binary format created by the MakeSpriteFont utility.
_Use_decl_annotations_
SpriteFont::Impl::Impl(
ID3D11Device* device,
BinaryReader* reader,
bool forceSRGB) noexcept(false) :
defaultGlyph(nullptr),
lineSpacing(0),
utfBufferSize(0)
{
// Validate the header.
for (char const* magic = spriteFontMagic; *magic; magic++)
{
if (reader->Read<uint8_t>() != *magic)
{
DebugTrace("ERROR: SpriteFont provided with an invalid .spritefont file\n");
throw std::exception("Not a MakeSpriteFont output binary");
}
}
// Read the glyph data.
auto glyphCount = reader->Read<uint32_t>();
auto glyphData = reader->ReadArray<Glyph>(glyphCount);
glyphs.assign(glyphData, glyphData + glyphCount);
glyphsIndex.reserve(glyphs.size());
for (auto& glyph : glyphs)
{
glyphsIndex.emplace_back(glyph.Character);
}
// Read font properties.
lineSpacing = reader->Read<float>();
SetDefaultCharacter(static_cast<wchar_t>(reader->Read<uint32_t>()));
// Read the texture data.
auto textureWidth = reader->Read<uint32_t>();
auto textureHeight = reader->Read<uint32_t>();
auto textureFormat = reader->Read<DXGI_FORMAT>();
auto textureStride = reader->Read<uint32_t>();
auto textureRows = reader->Read<uint32_t>();
uint64_t dataSize = uint64_t(textureStride) * uint64_t(textureRows);
if (dataSize > UINT32_MAX)
{
DebugTrace("ERROR: SpriteFont provided with an invalid .spritefont file\n");
throw std::overflow_error("Invalid .spritefont file");
}
auto textureData = reader->ReadArray<uint8_t>(static_cast<size_t>(dataSize));
if (forceSRGB)
{
textureFormat = LoaderHelpers::MakeSRGB(textureFormat);
}
// Create the D3D texture.
CreateTextureResource(
device,
textureWidth, textureHeight,
textureFormat,
textureStride, textureRows,
textureData);
}
// Constructs a SpriteFont from arbitrary user specified glyph data.
_Use_decl_annotations_
SpriteFont::Impl::Impl(
ID3D11ShaderResourceView* itexture,
Glyph const* iglyphs,
size_t glyphCount,
float ilineSpacing) noexcept(false) :
texture(itexture),
glyphs(iglyphs, iglyphs + glyphCount),
defaultGlyph(nullptr),
lineSpacing(ilineSpacing),
utfBufferSize(0)
{
if (!std::is_sorted(iglyphs, iglyphs + glyphCount))
{
throw std::exception("Glyphs must be in ascending codepoint order");
}
glyphsIndex.reserve(glyphs.size());
for (auto& glyph : glyphs)
{
glyphsIndex.emplace_back(glyph.Character);
}
}
// Looks up the requested glyph, falling back to the default character if it is not in the font.
SpriteFont::Glyph const* SpriteFont::Impl::FindGlyph(wchar_t character) const
{
// Rather than use std::lower_bound (which includes a slow debug path when built for _DEBUG),
// we implement a binary search inline to ensure sufficient Debug build performance to be useful
// for text-heavy applications.
size_t lower = 0;
size_t higher = glyphs.size() - 1;
size_t index = higher / 2;
const size_t size = glyphs.size();
while (index < size)
{
const auto curChar = glyphsIndex[index];
if (curChar == character) { return &glyphs[index]; }
if (curChar < character)
{
lower = index + 1;
}
else
{
higher = index - 1;
}
if (higher < lower) { break; }
else if (higher - lower <= 4)
{
for (index = lower; index <= higher; index++)
{
if (glyphsIndex[index] == character)
{
return &glyphs[index];
}
}
}
index = lower + ((higher - lower) / 2);
}
if (defaultGlyph)
{
return defaultGlyph;
}
DebugTrace("ERROR: SpriteFont encountered a character not in the font (%u, %C), and no default glyph was provided\n", character, character);
throw std::exception("Character not in font");
}
// Sets the missing-character fallback glyph.
void SpriteFont::Impl::SetDefaultCharacter(wchar_t character)
{
defaultGlyph = nullptr;
if (character)
{
defaultGlyph = FindGlyph(character);
}
}
// The core glyph layout algorithm, shared between DrawString and MeasureString.
template<typename TAction>
void SpriteFont::Impl::ForEachGlyph(_In_z_ wchar_t const* text, TAction action, bool ignoreWhitespace) const
{
float x = 0;
float y = 0;
for (; *text; text++)
{
wchar_t character = *text;
switch (character)
{
case '\r':
// Skip carriage returns.
continue;
case '\n':
// New line.
x = 0;
y += lineSpacing;
break;
default:
// Output this character.
auto glyph = FindGlyph(character);
x += glyph->XOffset;
if (x < 0)
x = 0;
float advance = float(glyph->Subrect.right) - float(glyph->Subrect.left) + glyph->XAdvance;
if (!ignoreWhitespace
|| !iswspace(character)
|| ((glyph->Subrect.right - glyph->Subrect.left) > 1)
|| ((glyph->Subrect.bottom - glyph->Subrect.top) > 1))
{
action(glyph, x, y, advance);
}
x += advance;
break;
}
}
}
_Use_decl_annotations_
void SpriteFont::Impl::CreateTextureResource(
ID3D11Device* device,
uint32_t width, uint32_t height,
DXGI_FORMAT format,
uint32_t stride, uint32_t rows,
const uint8_t* data) noexcept(false)
{
uint64_t sliceBytes = uint64_t(stride) * uint64_t(rows);
if (sliceBytes > UINT32_MAX)
{
DebugTrace("ERROR: SpriteFont provided with an invalid .spritefont file\n");
throw std::overflow_error("Invalid .spritefont file");
}
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = format;
desc.SampleDesc.Count = 1;
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
D3D11_SUBRESOURCE_DATA initData = { data, stride, static_cast<UINT>(sliceBytes) };
ComPtr<ID3D11Texture2D> texture2D;
ThrowIfFailed(
device->CreateTexture2D(&desc, &initData, &texture2D)
);
CD3D11_SHADER_RESOURCE_VIEW_DESC viewDesc(D3D11_SRV_DIMENSION_TEXTURE2D, format);
ThrowIfFailed(
device->CreateShaderResourceView(texture2D.Get(), &viewDesc, texture.ReleaseAndGetAddressOf())
);
SetDebugObjectName(texture.Get(), "DirectXTK:SpriteFont");
SetDebugObjectName(texture2D.Get(), "DirectXTK:SpriteFont");
}
const wchar_t* SpriteFont::Impl::ConvertUTF8(_In_z_ const char *text) noexcept(false)
{
if (!utfBuffer)
{
utfBufferSize = 1024;
utfBuffer.reset(new wchar_t[1024]);
}
int result = MultiByteToWideChar(CP_UTF8, 0, text, -1, utfBuffer.get(), static_cast<int>(utfBufferSize));
if (!result && (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
// Compute required buffer size
result = MultiByteToWideChar(CP_UTF8, 0, text, -1, nullptr, 0);
utfBufferSize = AlignUp(static_cast<size_t>(result), 1024u);
utfBuffer.reset(new wchar_t[utfBufferSize]);
// Retry conversion
result = MultiByteToWideChar(CP_UTF8, 0, text, -1, utfBuffer.get(), static_cast<int>(utfBufferSize));
}
if (!result)
{
DebugTrace("ERROR: MultiByteToWideChar failed with error %u.\n", GetLastError());
throw std::exception("MultiByteToWideChar");
}
return utfBuffer.get();
}
// Construct from a binary file created by the MakeSpriteFont utility.
_Use_decl_annotations_
SpriteFont::SpriteFont(ID3D11Device* device, wchar_t const* fileName, bool forceSRGB)
{
BinaryReader reader(fileName);
pImpl = std::make_unique<Impl>(device, &reader, forceSRGB);
}
// Construct from a binary blob created by the MakeSpriteFont utility and already loaded into memory.
_Use_decl_annotations_
SpriteFont::SpriteFont(ID3D11Device* device, uint8_t const* dataBlob, size_t dataSize, bool forceSRGB)
{
BinaryReader reader(dataBlob, dataSize);
pImpl = std::make_unique<Impl>(device, &reader, forceSRGB);
}
// Construct from arbitrary user specified glyph data (for those not using the MakeSpriteFont utility).
_Use_decl_annotations_
SpriteFont::SpriteFont(ID3D11ShaderResourceView* texture, Glyph const* glyphs, size_t glyphCount, float lineSpacing)
: pImpl(std::make_unique<Impl>(texture, glyphs, glyphCount, lineSpacing))
{
}
// Move constructor.
SpriteFont::SpriteFont(SpriteFont&& moveFrom) noexcept
: pImpl(std::move(moveFrom.pImpl))
{
}
// Move assignment.
SpriteFont& SpriteFont::operator= (SpriteFont&& moveFrom) noexcept
{
pImpl = std::move(moveFrom.pImpl);
return *this;
}
// Public destructor.
SpriteFont::~SpriteFont()
{
}
// Wide-character / UTF-16LE
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ wchar_t const* text, XMFLOAT2 const& position, FXMVECTOR color, float rotation, XMFLOAT2 const& origin, float scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, text, XMLoadFloat2(&position), color, rotation, XMLoadFloat2(&origin), XMVectorReplicate(scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ wchar_t const* text, XMFLOAT2 const& position, FXMVECTOR color, float rotation, XMFLOAT2 const& origin, XMFLOAT2 const& scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, text, XMLoadFloat2(&position), color, rotation, XMLoadFloat2(&origin), XMLoadFloat2(&scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ wchar_t const* text, FXMVECTOR position, FXMVECTOR color, float rotation, FXMVECTOR origin, float scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, text, position, color, rotation, origin, XMVectorReplicate(scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ wchar_t const* text, FXMVECTOR position, FXMVECTOR color, float rotation, FXMVECTOR origin, GXMVECTOR scale, SpriteEffects effects, float layerDepth) const
{
static_assert(SpriteEffects_FlipHorizontally == 1 &&
SpriteEffects_FlipVertically == 2, "If you change these enum values, the following tables must be updated to match");
// Lookup table indicates which way to move along each axis per SpriteEffects enum value.
static XMVECTORF32 axisDirectionTable[4] =
{
{ { { -1, -1, 0, 0 } } },
{ { { 1, -1, 0, 0 } } },
{ { { -1, 1, 0, 0 } } },
{ { { 1, 1, 0, 0 } } },
};
// Lookup table indicates which axes are mirrored for each SpriteEffects enum value.
static XMVECTORF32 axisIsMirroredTable[4] =
{
{ { { 0, 0, 0, 0 } } },
{ { { 1, 0, 0, 0 } } },
{ { { 0, 1, 0, 0 } } },
{ { { 1, 1, 0, 0 } } },
};
XMVECTOR baseOffset = origin;
// If the text is mirrored, offset the start position accordingly.
if (effects)
{
baseOffset = XMVectorNegativeMultiplySubtract(
MeasureString(text),
axisIsMirroredTable[effects & 3],
baseOffset);
}
// Draw each character in turn.
pImpl->ForEachGlyph(text, [&](Glyph const* glyph, float x, float y, float advance)
{
UNREFERENCED_PARAMETER(advance);
XMVECTOR offset = XMVectorMultiplyAdd(XMVectorSet(x, y + glyph->YOffset, 0, 0), axisDirectionTable[effects & 3], baseOffset);
if (effects)
{
// For mirrored characters, specify bottom and/or right instead of top left.
XMVECTOR glyphRect = XMConvertVectorIntToFloat(XMLoadInt4(reinterpret_cast<uint32_t const*>(&glyph->Subrect)), 0);
// xy = glyph width/height.
glyphRect = XMVectorSubtract(XMVectorSwizzle<2, 3, 0, 1>(glyphRect), glyphRect);
offset = XMVectorMultiplyAdd(glyphRect, axisIsMirroredTable[effects & 3], offset);
}
spriteBatch->Draw(pImpl->texture.Get(), position, &glyph->Subrect, color, rotation, offset, scale, effects, layerDepth);
}, true);
}
XMVECTOR XM_CALLCONV SpriteFont::MeasureString(_In_z_ wchar_t const* text, bool ignoreWhitespace) const
{
XMVECTOR result = XMVectorZero();
pImpl->ForEachGlyph(text, [&](Glyph const* glyph, float x, float y, float advance)
{
UNREFERENCED_PARAMETER(advance);
auto w = static_cast<float>(glyph->Subrect.right - glyph->Subrect.left);
auto h = static_cast<float>(glyph->Subrect.bottom - glyph->Subrect.top) + glyph->YOffset;
h = iswspace(wchar_t(glyph->Character)) ?
pImpl->lineSpacing :
std::max(h, pImpl->lineSpacing);
result = XMVectorMax(result, XMVectorSet(x + w, y + h, 0, 0));
}, ignoreWhitespace);
return result;
}
RECT SpriteFont::MeasureDrawBounds(_In_z_ wchar_t const* text, XMFLOAT2 const& position, bool ignoreWhitespace) const
{
RECT result = { LONG_MAX, LONG_MAX, 0, 0 };
pImpl->ForEachGlyph(text, [&](Glyph const* glyph, float x, float y, float advance) noexcept
{
auto isWhitespace = iswspace(wchar_t(glyph->Character));
auto w = static_cast<float>(glyph->Subrect.right - glyph->Subrect.left);
auto h = isWhitespace ?
pImpl->lineSpacing :
static_cast<float>(glyph->Subrect.bottom - glyph->Subrect.top);
float minX = position.x + x;
float minY = position.y + y + (isWhitespace ? 0.0f : glyph->YOffset);
float maxX = std::max(minX + advance, minX + w);
float maxY = minY + h;
if (minX < float(result.left))
result.left = long(minX);
if (minY < float(result.top))
result.top = long(minY);
if (float(result.right) < maxX)
result.right = long(maxX);
if (float(result.bottom) < maxY)
result.bottom = long(maxY);
}, ignoreWhitespace);
if (result.left == LONG_MAX)
{
result.left = 0;
result.top = 0;
}
return result;
}
RECT XM_CALLCONV SpriteFont::MeasureDrawBounds(_In_z_ wchar_t const* text, FXMVECTOR position, bool ignoreWhitespace) const
{
XMFLOAT2 pos;
XMStoreFloat2(&pos, position);
return MeasureDrawBounds(text, pos, ignoreWhitespace);
}
// UTF-8
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ char const* text, XMFLOAT2 const& position, FXMVECTOR color, float rotation, XMFLOAT2 const& origin, float scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, pImpl->ConvertUTF8(text), XMLoadFloat2(&position), color, rotation, XMLoadFloat2(&origin), XMVectorReplicate(scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ char const* text, XMFLOAT2 const& position, FXMVECTOR color, float rotation, XMFLOAT2 const& origin, XMFLOAT2 const& scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, pImpl->ConvertUTF8(text), XMLoadFloat2(&position), color, rotation, XMLoadFloat2(&origin), XMLoadFloat2(&scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ char const* text, FXMVECTOR position, FXMVECTOR color, float rotation, FXMVECTOR origin, float scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, pImpl->ConvertUTF8(text), position, color, rotation, origin, XMVectorReplicate(scale), effects, layerDepth);
}
void XM_CALLCONV SpriteFont::DrawString(_In_ SpriteBatch* spriteBatch, _In_z_ char const* text, FXMVECTOR position, FXMVECTOR color, float rotation, FXMVECTOR origin, GXMVECTOR scale, SpriteEffects effects, float layerDepth) const
{
DrawString(spriteBatch, pImpl->ConvertUTF8(text), position, color, rotation, origin, scale, effects, layerDepth);
}
XMVECTOR XM_CALLCONV SpriteFont::MeasureString(_In_z_ char const* text, bool ignoreWhitespace) const
{
return MeasureString(pImpl->ConvertUTF8(text), ignoreWhitespace);
}
RECT SpriteFont::MeasureDrawBounds(_In_z_ char const* text, XMFLOAT2 const& position, bool ignoreWhitespace) const
{
return MeasureDrawBounds(pImpl->ConvertUTF8(text), position, ignoreWhitespace);
}
RECT XM_CALLCONV SpriteFont::MeasureDrawBounds(_In_z_ char const* text, FXMVECTOR position, bool ignoreWhitespace) const
{
XMFLOAT2 pos;
XMStoreFloat2(&pos, position);
return MeasureDrawBounds(pImpl->ConvertUTF8(text), pos, ignoreWhitespace);
}
// Spacing properties
float SpriteFont::GetLineSpacing() const noexcept
{
return pImpl->lineSpacing;
}
void SpriteFont::SetLineSpacing(float spacing)
{
pImpl->lineSpacing = spacing;
}
// Font properties
wchar_t SpriteFont::GetDefaultCharacter() const noexcept
{
return static_cast<wchar_t>(pImpl->defaultGlyph ? pImpl->defaultGlyph->Character : 0);
}
void SpriteFont::SetDefaultCharacter(wchar_t character)
{
pImpl->SetDefaultCharacter(character);
}
bool SpriteFont::ContainsCharacter(wchar_t character) const
{
return std::binary_search(pImpl->glyphs.begin(), pImpl->glyphs.end(), character);
}
// Custom layout/rendering
SpriteFont::Glyph const* SpriteFont::FindGlyph(wchar_t character) const
{
return pImpl->FindGlyph(character);
}
void SpriteFont::GetSpriteSheet(ID3D11ShaderResourceView** texture) const
{
if (!texture)
return;
ThrowIfFailed(pImpl->texture.CopyTo(texture));
}