You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Code/CryEngine/CryFont/FontTexture.cpp

633 lines
19 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
// Original file Copyright Crytek GMBH or its affiliates, used under license.
// Purpose:
// - Create and update a texture with the most recently used glyphs
#include "CryFont_precompiled.h"
#if !defined(USE_NULLFONT_ALWAYS)
#include "FontTexture.h"
#include "UnicodeIterator.h"
#include <AzCore/IO/FileIO.h>
#ifdef WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#undef GetCharWidth
#endif
//-------------------------------------------------------------------------------------------------
CFontTexture::CFontTexture()
: m_wSlotUsage(1)
, m_iWidth(0)
, m_iHeight(0)
, m_fInvWidth(0.0f)
, m_fInvHeight(0.0f)
, m_iCellWidth(0)
, m_iCellHeight(0)
, m_fTextureCellWidth(0)
, m_fTextureCellHeight(0)
, m_iWidthCellCount(0)
, m_iHeightCellCount(0)
, m_nTextureSlotCount(0)
, m_pBuffer(0)
, m_iSmoothMethod(FONT_SMOOTH_NONE)
, m_iSmoothAmount(FONT_SMOOTH_AMOUNT_NONE)
{
}
//-------------------------------------------------------------------------------------------------
CFontTexture::~CFontTexture()
{
Release();
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::CreateFromFile(const string& szFileName, int iWidth, int iHeight, int iSmoothMethod, int iSmoothAmount, int iWidthCellCount, int iHeightCellCount)
{
if (!m_pGlyphCache.LoadFontFromFile(szFileName))
{
Release();
return 0;
}
if (!Create(iWidth, iHeight, iSmoothMethod, iSmoothAmount, iWidthCellCount, iHeightCellCount))
{
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::CreateFromMemory(unsigned char* pFileData, int iDataSize, int iWidth, int iHeight, int iSmoothMethod, int iSmoothAmount, int iWidthCellCount, int iHeightCellCount, float sizeRatio)
{
if (!m_pGlyphCache.LoadFontFromMemory(pFileData, iDataSize))
{
Release();
return 0;
}
if (!Create(iWidth, iHeight, iSmoothMethod, iSmoothAmount, iWidthCellCount, iHeightCellCount, sizeRatio))
{
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::Create(int iWidth, int iHeight, int iSmoothMethod, int iSmoothAmount, int iWidthCellCount, int iHeightCellCount, float sizeRatio)
{
m_pBuffer = new FONT_TEXTURE_TYPE[iWidth * iHeight];
if (!m_pBuffer)
{
return 0;
}
memset(m_pBuffer, 0, iWidth * iHeight * sizeof(FONT_TEXTURE_TYPE));
if (!(iWidthCellCount * iHeightCellCount))
{
return 0;
}
m_iWidth = iWidth;
m_iHeight = iHeight;
m_fInvWidth = 1.0f / (float)iWidth;
m_fInvHeight = 1.0f / (float)iHeight;
m_iWidthCellCount = iWidthCellCount;
m_iHeightCellCount = iHeightCellCount;
m_nTextureSlotCount = m_iWidthCellCount * m_iHeightCellCount;
m_iSmoothMethod = iSmoothMethod;
m_iSmoothAmount = iSmoothAmount;
m_iCellWidth = m_iWidth / m_iWidthCellCount;
m_iCellHeight = m_iHeight / m_iHeightCellCount;
m_fTextureCellWidth = m_iCellWidth * m_fInvWidth;
m_fTextureCellHeight = m_iCellHeight * m_fInvHeight;
if (!m_pGlyphCache.Create(FONT_GLYPH_CACHE_SIZE, m_iCellWidth, m_iCellHeight, iSmoothMethod, iSmoothAmount, sizeRatio))
{
Release();
return 0;
}
if (!CreateSlotList(m_nTextureSlotCount))
{
Release();
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::Release()
{
delete[] m_pBuffer;
m_pBuffer = 0;
ReleaseSlotList();
m_pSlotTable.clear();
m_pGlyphCache.Release();
m_iWidthCellCount = 0;
m_iHeightCellCount = 0;
m_nTextureSlotCount = 0;
m_iWidth = 0;
m_iHeight = 0;
m_fInvWidth = 0.0f;
m_fInvHeight = 0.0f;
m_iCellWidth = 0;
m_iCellHeight = 0;
m_iSmoothMethod = 0;
m_iSmoothAmount = 0;
m_fTextureCellWidth = 0.0f;
m_fTextureCellHeight = 0.0f;
m_wSlotUsage = 1;
return 1;
}
//-------------------------------------------------------------------------------------------------
uint32 CFontTexture::GetSlotChar(int iSlot) const
{
return m_pSlotList[iSlot]->cCurrentChar;
}
//-------------------------------------------------------------------------------------------------
CTextureSlot* CFontTexture::GetCharSlot(uint32 cChar, const Vec2i& glyphSize)
{
CryFont::FontTexture::CTextureSlotKey slotKey = GetTextureSlotKey(cChar, glyphSize);
CTextureSlotTableItor pItor = m_pSlotTable.find(slotKey);
if (pItor != m_pSlotTable.end())
{
return pItor->second;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
CTextureSlot* CFontTexture::GetLRUSlot()
{
uint16 wMaxSlotAge = 0;
CTextureSlot* pLRUSlot = 0;
CTextureSlot* pSlot;
CTextureSlotListItor pItor = m_pSlotList.begin();
while (pItor != m_pSlotList.end())
{
pSlot = *pItor;
if (pSlot->wSlotUsage == 0)
{
return pSlot;
}
else
{
uint16 slotAge = m_wSlotUsage - pSlot->wSlotUsage;
if (slotAge > wMaxSlotAge)
{
pLRUSlot = pSlot;
wMaxSlotAge = slotAge;
}
}
++pItor;
}
return pLRUSlot;
}
//-------------------------------------------------------------------------------------------------
CTextureSlot* CFontTexture::GetMRUSlot()
{
uint16 wMinSlotAge = 0xFFFF;
CTextureSlot* pMRUSlot = 0;
CTextureSlot* pSlot;
CTextureSlotListItor pItor = m_pSlotList.begin();
while (pItor != m_pSlotList.end())
{
pSlot = *pItor;
if (pSlot->wSlotUsage != 0)
{
uint16 slotAge = m_wSlotUsage - pSlot->wSlotUsage;
if (slotAge > wMinSlotAge)
{
pMRUSlot = pSlot;
wMinSlotAge = slotAge;
}
}
++pItor;
}
return pMRUSlot;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::PreCacheString(const char* szString, int* pUpdated, float sizeRatio, const Vec2i& glyphSize, const CFFont::FontHintParams& fontHintParams)
{
Vec2i clampedGlyphSize = ClampGlyphSize(glyphSize, m_iCellWidth, m_iCellHeight);
uint16 wSlotUsage = m_wSlotUsage++;
int iUpdated = 0;
uint32 cChar;
for (Unicode::CIterator<const char*, false> it(szString); cChar = *it; ++it)
{
CTextureSlot* pSlot = GetCharSlot(cChar, clampedGlyphSize);
if (!pSlot)
{
pSlot = GetLRUSlot();
if (!pSlot)
{
return 0;
}
if (!UpdateSlot(pSlot->iTextureSlot, wSlotUsage, cChar, sizeRatio, clampedGlyphSize, fontHintParams))
{
return 0;
}
++iUpdated;
}
else
{
pSlot->wSlotUsage = wSlotUsage;
}
}
if (pUpdated)
{
*pUpdated = iUpdated;
}
if (iUpdated)
{
return 1;
}
return 2;
}
//-------------------------------------------------------------------------------------------------
void CFontTexture::GetTextureCoord(CTextureSlot* pSlot, float texCoords[4],
int& iCharSizeX, int& iCharSizeY, int& iCharOffsetX, int& iCharOffsetY,
const Vec2i& glyphSize) const
{
if (!pSlot)
{
return; // expected behavior
}
// Re-rendered glyphs are stored at smaller sizes than glyphs rendered at
// the (maximum) font texture slot resolution. We scale the returned width
// and height of the (actual) rendered glyph sizes so its transparent to
// callers that the glyph is actually smaller (from being re-rendered).
const float requestSizeWidthScale = AZ::GetMin<float>(1.0f, GetRequestSizeWidthScale(glyphSize));
const float requestSizeHeightScale = AZ::GetMin<float>(1.0f, GetRequestSizeHeightScale(glyphSize));
const float invRequestSizeWidthScale = 1.0f / requestSizeWidthScale;
const float invRequestSizeHeightScale = 1.0f / requestSizeHeightScale;
// The inverse scale grows as the glyph size decreases. Once the glyph size
// reaches the font texture's max slot dimensions, we cap width/height scale
// since the text draw context will apply normal (as opposed to re-rendered)
// scaling.
int iChWidth = static_cast<int>(pSlot->iCharWidth * invRequestSizeWidthScale);
int iChHeight = static_cast<int>(pSlot->iCharHeight * invRequestSizeHeightScale);
float slotCoord0 = pSlot->vTexCoord[0];
float slotCoord1 = pSlot->vTexCoord[1];
texCoords[0] = slotCoord0 - m_fInvWidth; // extra pixel for nicer bilinear filter
texCoords[1] = slotCoord1 - m_fInvHeight; // extra pixel for nicer bilinear filter
// UV coordinates also must be scaled relative to the re-rendered glyph size
// as well. Width scale must be capped at 1.0f since glyph can't grow
// beyond the slot's resolution.
texCoords[2] = slotCoord0 + (((float)iChWidth * m_fInvWidth) * requestSizeWidthScale);
texCoords[3] = slotCoord1 + (((float)iChHeight * m_fInvHeight) * requestSizeHeightScale);
iCharSizeX = iChWidth + 1; // extra pixel for nicer bilinear filter
iCharSizeY = iChHeight + 1; // extra pixel for nicer bilinear filter
// Offsets are scaled accordingly when the rendered glyph size is smaller
// than the glyph/slot dimensions, but otherwise we expect the text draw
// context to apply scaling beyond that.
iCharOffsetX = static_cast<int>(pSlot->iCharOffsetX * invRequestSizeWidthScale);
iCharOffsetY = static_cast<int>(pSlot->iCharOffsetY * invRequestSizeHeightScale);
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::GetCharacterWidth(uint32 cChar) const
{
CTextureSlotTableItorConst pItor = m_pSlotTable.find(GetTextureSlotKey(cChar));
if (pItor == m_pSlotTable.end())
{
return 0;
}
const CTextureSlot& rSlot = *pItor->second;
// For proportional fonts, add one pixel of spacing for aesthetic reasons
int proportionalOffset = GetMonospaced() ? 0 : 1;
return rSlot.iCharWidth + proportionalOffset;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::GetHorizontalAdvance(uint32 cChar, const Vec2i& glyphSize) const
{
CTextureSlotTableItorConst pItor = m_pSlotTable.find(GetTextureSlotKey(cChar, glyphSize));
if (pItor == m_pSlotTable.end())
{
return 0;
}
const CTextureSlot& rSlot = *pItor->second;
// Re-rendered glyphs are stored at smaller sizes than glyphs rendered at
// the (maximum) font texture slot resolution. We scale the returned width
// and height of the (actual) rendered glyph sizes so its transparent to
// callers that the glyph is actually smaller (from being re-rendered).
const float requestSizeWidthScale = GetRequestSizeWidthScale(glyphSize);
const float invRequestSizeWidthScale = 1.0f / requestSizeWidthScale;
// Only multiply by 1.0f when glyphsize is greater than cell width because we assume that callers
// will use the font draw text context to scale the value appropriately.
return static_cast<int>(rSlot.iHoriAdvance * AZ::GetMax<float>(1.0f, invRequestSizeWidthScale));
}
//-------------------------------------------------------------------------------------------------
/*
int CFontTexture::GetCharHeightByChar(wchar_t cChar)
{
CTextureSlotTableItor pItor = m_pSlotTable.find(cChar);
if (pItor != m_pSlotTable.end())
{
return pItor->second->iCharHeight;
}
return 0;
}
*/
//-------------------------------------------------------------------------------------------------
int CFontTexture::WriteToFile([[maybe_unused]] const string& szFileName)
{
#ifdef WIN32
AZ::IO::FileIOStream outputFile(szFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeBinary);
if (!outputFile.IsOpen())
{
return 0;
}
BITMAPFILEHEADER pHeader;
BITMAPINFOHEADER pInfoHeader;
memset(&pHeader, 0, sizeof(BITMAPFILEHEADER));
memset(&pInfoHeader, 0, sizeof(BITMAPINFOHEADER));
pHeader.bfType = 0x4D42;
pHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + m_iWidth * m_iHeight * 3;
pHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
pInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
pInfoHeader.biWidth = m_iWidth;
pInfoHeader.biHeight = m_iHeight;
pInfoHeader.biPlanes = 1;
pInfoHeader.biBitCount = 24;
pInfoHeader.biCompression = 0;
pInfoHeader.biSizeImage = m_iWidth * m_iHeight * 3;
outputFile.Write(sizeof(BITMAPFILEHEADER), &pHeader);
outputFile.Write(sizeof(BITMAPINFOHEADER), &pInfoHeader);
unsigned char cRGB[3];
for (int i = m_iHeight - 1; i >= 0; i--)
{
for (int j = 0; j < m_iWidth; j++)
{
cRGB[0] = m_pBuffer[(i * m_iWidth) + j];
cRGB[1] = *cRGB;
cRGB[2] = *cRGB;
outputFile.Write(3, cRGB);
}
}
#endif
return 1;
}
//-------------------------------------------------------------------------------------------------
Vec2 CFontTexture::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph)
{
return m_pGlyphCache.GetKerning(leftGlyph, rightGlyph);
}
//-------------------------------------------------------------------------------------------------
float CFontTexture::GetAscenderToHeightRatio()
{
return m_pGlyphCache.GetAscenderToHeightRatio();
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::CreateSlotList(int iListSize)
{
int y, x;
for (int i = 0; i < iListSize; i++)
{
CTextureSlot* pTextureSlot = new CTextureSlot;
if (!pTextureSlot)
{
return 0;
}
pTextureSlot->iTextureSlot = i;
pTextureSlot->Reset();
y = i / m_iWidthCellCount;
x = i % m_iWidthCellCount;
pTextureSlot->vTexCoord[0] = (float)(x * m_fTextureCellWidth) + (0.5f / (float)m_iWidth);
pTextureSlot->vTexCoord[1] = (float)(y * m_fTextureCellHeight) + (0.5f / (float)m_iHeight);
m_pSlotList.push_back(pTextureSlot);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::ReleaseSlotList()
{
CTextureSlotListItor pItor = m_pSlotList.begin();
while (pItor != m_pSlotList.end())
{
delete (*pItor);
pItor = m_pSlotList.erase(pItor);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int CFontTexture::UpdateSlot(int iSlot, uint16 wSlotUsage, uint32 cChar, float sizeRatio, const Vec2i& glyphSize, const CFFont::FontHintParams& fontHintParams)
{
CTextureSlot* pSlot = m_pSlotList[iSlot];
if (!pSlot)
{
return 0;
}
CTextureSlotTableItor pItor = m_pSlotTable.find(GetTextureSlotKey(pSlot->cCurrentChar, pSlot->glyphSize));
if (pItor != m_pSlotTable.end())
{
m_pSlotTable.erase(pItor);
}
m_pSlotTable.insert(AZStd::pair<CryFont::FontTexture::CTextureSlotKey, CTextureSlot*>(GetTextureSlotKey(cChar, glyphSize), pSlot));
pSlot->glyphSize = glyphSize;
pSlot->wSlotUsage = wSlotUsage;
pSlot->cCurrentChar = cChar;
int iWidth = 0;
int iHeight = 0;
// blit the char glyph into the texture
int x = pSlot->iTextureSlot % m_iWidthCellCount;
int y = pSlot->iTextureSlot / m_iWidthCellCount;
CGlyphBitmap* pGlyphBitmap;
if (glyphSize.x > 0 && glyphSize.y > 0)
{
m_pGlyphCache.SetGlyphBitmapSize(glyphSize.x, glyphSize.y, sizeRatio);
}
if (!m_pGlyphCache.GetGlyph(&pGlyphBitmap, &pSlot->iHoriAdvance, &iWidth, &iHeight, pSlot->iCharOffsetX, pSlot->iCharOffsetY, cChar, glyphSize, fontHintParams))
{
return 0;
}
pSlot->iCharWidth = iWidth;
pSlot->iCharHeight = iHeight;
// Add a pixel along width and height to avoid artifacts being rendered
// from a previous glyph in this slot due to bilinear filtering. The source
// glyph bitmap buffer is presumed to be cleared prior to FreeType rendering
// to the bitmap.
const int blitWidth = AZ::GetMin<int>(iWidth + 1, m_iCellWidth);
const int blitHeight = AZ::GetMin<int>(iHeight + 1, m_iCellHeight);
pGlyphBitmap->BlitTo8(m_pBuffer, 0, 0,
blitWidth, blitHeight, x * m_iCellWidth, y * m_iCellHeight, m_iWidth);
return 1;
}
//-------------------------------------------------------------------------------------------------
void CFontTexture::CreateGradientSlot()
{
CTextureSlot* pSlot = GetGradientSlot();
assert(pSlot->cCurrentChar == (uint32)~0); // 0 needs to be unused spot
pSlot->Reset();
pSlot->iCharWidth = m_iCellWidth - 2;
pSlot->iCharHeight = m_iCellHeight - 2;
pSlot->SetNotReusable();
int x = pSlot->iTextureSlot % m_iWidthCellCount;
int y = pSlot->iTextureSlot / m_iWidthCellCount;
assert(sizeof(*m_pBuffer) == sizeof(uint8));
uint8* pBuffer = &m_pBuffer[x * m_iCellWidth + y * m_iCellHeight * m_iWidth];
for (uint32 dwY = 0; dwY < pSlot->iCharHeight; ++dwY)
{
for (uint32 dwX = 0; dwX < pSlot->iCharWidth; ++dwX)
{
pBuffer[dwX + dwY * m_iWidth] = dwY * 255 / (pSlot->iCharHeight - 1);
}
}
}
//-------------------------------------------------------------------------------------------------
CTextureSlot* CFontTexture::GetGradientSlot()
{
return m_pSlotList[0];
}
//-------------------------------------------------------------------------------------------------
CryFont::FontTexture::CTextureSlotKey CFontTexture::GetTextureSlotKey(uint32 cChar, const Vec2i& glyphSize) const
{
const Vec2i clampedGlyphSize(ClampGlyphSize(glyphSize, m_iCellWidth, m_iCellHeight));
return CryFont::FontTexture::CTextureSlotKey(clampedGlyphSize, cChar);
}
Vec2i CFontTexture::ClampGlyphSize(const Vec2i& glyphSize, int cellWidth, int cellHeight)
{
const Vec2i maxCellDimensions(cellWidth, cellHeight);
Vec2i clampedGlyphSize(glyphSize);
const bool hasZeroDimension = glyphSize.x == 0 || glyphSize.y == 0;
const bool isDefaultSize = glyphSize == CCryFont::defaultGlyphSize;
const bool exceedsDimensions = glyphSize.x > cellWidth || glyphSize.y > cellHeight;
const bool useMaxCellDimension = hasZeroDimension || isDefaultSize || exceedsDimensions;
if (useMaxCellDimension)
{
clampedGlyphSize = maxCellDimensions;
}
return clampedGlyphSize;
}
#endif // #if !defined(USE_NULLFONT_ALWAYS)