Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,827 @@
/*
* 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.
// Description : AtomFont class.
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include <AtomLyIntegration/AtomFont/AtomFont.h>
#include <AtomLyIntegration/AtomFont/FFont.h>
#include <AtomLyIntegration/AtomFont/FontTexture.h>
#include <AtomLyIntegration/AtomFont/FontRenderer.h>
#include <CryCommon/CryPath.h>
#include <CryCommon/ILocalizationManager.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/std/string/string_view.h>
#include <AzFramework/Archive/IArchive.h>
// Static member definitions
const AZ::AtomFont::GlyphSize AZ::AtomFont::defaultGlyphSize = AZ::AtomFont::GlyphSize(ICryFont::defaultGlyphSizeX, ICryFont::defaultGlyphSizeY);
#if !defined(_RELEASE)
static void DumfontTexture(IConsoleCmdArgs* cmdArgs)
{
if (cmdArgs->GetArgCount() != 2)
{
return;
}
const char* fontName = cmdArgs->GetArg(1);
if (fontName && *fontName && *fontName != '0')
{
string fontFilePath("@devroot@/");
fontFilePath += fontName;
fontFilePath += ".bmp";
AZ::FFont* font = (AZ::FFont*) gEnv->pCryFont->GetFont(fontName);
if (font)
{
font->GetFontTexture()->WriteToFile(fontFilePath.c_str());
gEnv->pLog->LogWithType(IMiniLog::eInputResponse, "Dumped \"%s\" texture to \"%s\"!", fontName, fontFilePath.c_str());
}
}
}
static void DumfontNames([[maybe_unused]] IConsoleCmdArgs* cmdArgs)
{
string names = gEnv->pCryFont->GetLoadedFontNames();
gEnv->pLog->LogWithType(IMiniLog::eInputResponse, "Currently loaded fonts: %s", names.c_str());
}
static void ReloadFonts([[maybe_unused]] IConsoleCmdArgs* cmdArgs)
{
gEnv->pCryFont->ReloadAllFonts();
}
#endif
namespace
{
//! Stores paths to styled font assets for a given set of languages
//! This struct stores the XML data contained within the <font> tag of
//! an enclosing <fontfamily> definition:
//!
//! <fontfamily name="FontFamilyName">
//! <font lang="Language1, Language2">
//! <file path="regular.font" />
//! <file path="bold.font" tags="b" />
//! <file path="italic.font" tags="i" />
//! <file path="bolditalic.font" tags="b,i" />
//! </font>
//! </fontfamily>
struct FontTagXml
{
//! \return True if all font asset paths are non-empty, false otherwise
bool IsValid() const
{
// Note that "lang" can be empty
return !m_fontFilename.empty()
&& !m_boldFontFilename.empty()
&& !m_italicFontFilename.empty()
&& !m_boldItalicFontFilename.empty();
}
string m_lang; //!< Stores a comma-separated list of languages this collection of fonts applies to.
//!< If this is an empty string, it implies that these set of fonts will be applied
//!< by default (when a language is being used but no fonts in the font family are
//!< mapped to that language).
string m_fontFilename; //!< Font used when no styling is applied.
string m_boldFontFilename; //!< Bold-styled font
string m_italicFontFilename; //!< Italic-styled font
string m_boldItalicFontFilename; //!< Bold-italic-styled font
};
//! Stores parsed font family XML data.
//! This struct contains the name of the font family and a list of font
//! file XML data for all the language-specific mappings of this
//! font family.
//!
//! Example XML:
//!
//! <fontfamily name="FontFamilyName">
//! <font>
//! <file path="regular.font" />
//! <file path="bold.font" tags="b" />
//! <file path="italic.font" tags="i" />
//! <file path="bolditalic.font" tags="b,i" />
//! </font>
//! <font lang="korean">
//! <file path="../korean/korean-regular.font" />
//! <file path="../korean/korean-italic.font" tags="b" />
//! <file path="../korean/korean-bold.font" tags="i" />
//! <file path="../korean/korean-bolditalic.font" tags="b,i" />
//! </font>
//! <font lang="chinesesimplified">
//! <file path="../chinesesimplified/chinesesimplified-regular.font" />
//! <file path="../chinesesimplified/chinesesimplified-bold.font" tags="b" />
//! <file path="../chinesesimplified/chinesesimplified-italic.font" tags="i" />
//! <file path="../chinesesimplified/chinesesimplified-bolditalic.font" tags="b,i" />
//! </font>
//! </fontfamily>
struct FontFamilyTagXml
{
//! Returns true if all font file fields were parsed, false otherwise.
bool IsValid() const
{
for (const FontTagXml& fontTagXml : m_fontTagsXml)
{
if (!fontTagXml.IsValid())
{
return false;
}
}
// Every font family must have a name
return !m_fontFamilyName.empty();
}
string m_fontFamilyName; //!< Value of the "name" font-family tag attribute
AZStd::list<FontTagXml> m_fontTagsXml; //!< List of child <font> tag data.
};
//! Returns true if the XML tree was traversed successfully, false otherwise.
//!
//! Note that, if this function returns true, it simply means that there were
//! no unexpected structure issues with the given XML tree, it doesn't
//! necessarily mean that all the required fields were parsed.
bool ParseFontFamilyXml(const XmlNodeRef& node, FontFamilyTagXml& xmlData)
{
if (!node)
{
return false;
}
// <fontfamily>
if (AZStd::string(node->getTag()) == "fontfamily")
{
const int numAttributes = node->getNumAttributes();
if (numAttributes <= 0)
{
// Expecting at least one attribute
return false;
}
string name;
for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
{
const char* key = "";
const char* value = "";
if (node->getAttributeByIndex(i, &key, &value))
{
if (string(key) == "name")
{
name = value;
}
else
{
// Unexpected font tag attribute
return false;
}
}
}
name.Trim();
if (!name.empty())
{
xmlData.m_fontFamilyName = name;
}
else
{
// Font family must have a name
return false;
}
}
// <font>
if (AZStd::string(node->getTag()) == "font")
{
xmlData.m_fontTagsXml.push_back(FontTagXml());
string lang;
for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
{
const char* key = "";
const char* value = "";
if (node->getAttributeByIndex(i, &key, &value))
{
if (string(key) == "lang")
{
lang = value;
}
else
{
// Unexpected font tag attribute
return false;
}
}
}
lang.Trim();
if (!lang.empty())
{
xmlData.m_fontTagsXml.back().m_lang = lang;
}
}
// <file>
else if (AZStd::string(node->getTag()) == "file")
{
const int numAttributes = node->getNumAttributes();
if (numAttributes <= 0)
{
// Expecting at least one attribute
return false;
}
string path;
string tags;
for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
{
const char* key = "";
const char* value = "";
if (node->getAttributeByIndex(i, &key, &value))
{
if (string(key) == "path")
{
path = value;
}
else if (string(key) == "tags")
{
tags = value;
}
else
{
// Unexpected font tag attribute
return false;
}
}
}
tags.Trim();
if (tags.empty())
{
xmlData.m_fontTagsXml.back().m_fontFilename = path;
}
else if (tags == "b")
{
xmlData.m_fontTagsXml.back().m_boldFontFilename = path;
}
else if (tags == "i")
{
xmlData.m_fontTagsXml.back().m_italicFontFilename = path;
}
else
{
// We'll just assume any other tag indicates bold italic
xmlData.m_fontTagsXml.back().m_boldItalicFontFilename = path;
}
}
for (int i = 0, count = node->getChildCount(); i < count; ++i)
{
XmlNodeRef child = node->getChild(i);
if (!ParseFontFamilyXml(child, xmlData))
{
return false;
}
}
return true;
}
//! Only attempt XML file load if file exists.
//! There are use-cases where the XML path is not fully known (such as
//! when referencing font family names from font family XML files), and
//! attempting to load the XML files directly via ISystem() methods can
//! produce a lot of warning noise.
XmlNodeRef SafeLoadXmlFromFile(const string& xmlPath)
{
if (gEnv->pCryPak->IsFileExist(xmlPath.c_str()))
{
return GetISystem()->LoadXmlFromFile(xmlPath.c_str());
}
return XmlNodeRef();
}
}
AZ::AtomFont::AtomFont(ISystem* system)
: m_system(system)
, m_fonts()
{
assert(m_system);
CryLogAlways("Using FreeType %d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
// Persist fonts for application lifetime to prevent unnecessary work
REGISTER_CVAR(r_persistFontFamilies, r_persistFontFamilies, VF_NULL, "Persist loaded font families for lifetime of application.");
#if !defined(_RELEASE)
REGISTER_COMMAND("r_DumfontTexture", DumfontTexture, 0,
"Dumps the specified font's texture to a bitmap file\n"
"Use r_DumfontTexture to get the loaded font names\n"
"Usage: r_DumfontTexture <fontname>");
REGISTER_COMMAND("r_DumfontNames", DumfontNames, 0,
"Logs a list of fonts currently loaded");
REGISTER_COMMAND("r_ReloadFonts", ReloadFonts, VF_NULL,
"Reload all fonts");
#endif
}
AZ::AtomFont::~AtomFont()
{
// Persist fonts for application lifetime to prevent unnecessary work
m_persistedFontFamilies.clear();
for (FontMapItor it = m_fonts.begin(), itEnd = m_fonts.end(); it != itEnd; )
{
FFont* font = it->second;
++it; // iterate as Release() below will remove font from the map
SAFE_RELEASE(font);
}
}
void AZ::AtomFont::Release()
{
delete this;
}
IFFont* AZ::AtomFont::NewFont(const char* fontName)
{
string name = fontName;
name.MakeLower();
FontMapItor it = m_fonts.find(CONST_TEMP_STRING(name.c_str()));
if (it != m_fonts.end())
{
return it->second;
}
FFont* font = new FFont(this, name.c_str());
m_fonts.insert(FontMapItor::value_type(name, font));
return font;
}
IFFont* AZ::AtomFont::GetFont(const char* fontName) const
{
FontMapConstItor it = m_fonts.find(CONST_TEMP_STRING(string(fontName).MakeLower()));
return it != m_fonts.end() ? it->second : 0;
}
FontFamilyPtr AZ::AtomFont::LoadFontFamily(const char* fontFamilyName)
{
FontFamilyPtr fontFamily(nullptr);
string fontFamilyPath;
string fontFamilyFullPath;
XmlNodeRef root = LoadFontFamilyXml(fontFamilyName, fontFamilyPath, fontFamilyFullPath);
if (root)
{
FontFamilyTagXml xmlData;
const bool parseSuccess = ParseFontFamilyXml(root, xmlData);
if (parseSuccess && xmlData.IsValid())
{
const char* currentLanguage = gEnv->pSystem->GetLocalizationManager()->GetLanguage();
FontTagXml* defaultFont = nullptr;
FontTagXml* langSpecificFont = nullptr;
// Note that we don't break out of this for-loop early because we
// want to find both the default font family and the
// language-specific font family. We prefer the lang-specific
// family but will fall back on the default if it doesn't exist.
for (FontTagXml& fontTagXml : xmlData.m_fontTagsXml)
{
if (fontTagXml.m_lang.empty())
{
defaultFont = &fontTagXml;
}
else
{
int searchPos = 0;
string langToken;
// "lang" font-tag attribute could be comma-separated
while (!(langToken = fontTagXml.m_lang.Tokenize(",", searchPos)).empty())
{
if (langToken.Trim() == currentLanguage)
{
langSpecificFont = &fontTagXml;
break;
}
}
}
}
if (langSpecificFont || defaultFont)
{
// Prefer lang-specific font-family over default, if it exists
FontTagXml* fontTagXml = langSpecificFont ? langSpecificFont : defaultFont;
// Pre-pend font family's path to make font family XML paths
// relative to font family file
fontTagXml->m_fontFilename = fontFamilyPath + fontTagXml->m_fontFilename;
fontTagXml->m_boldFontFilename = fontFamilyPath + fontTagXml->m_boldFontFilename;
fontTagXml->m_italicFontFilename = fontFamilyPath + fontTagXml->m_italicFontFilename;
fontTagXml->m_boldItalicFontFilename = fontFamilyPath + fontTagXml->m_boldItalicFontFilename;
IFFont* normal = LoadFont(fontTagXml->m_fontFilename.c_str());
IFFont* bold = LoadFont(fontTagXml->m_boldFontFilename.c_str());
IFFont* italic = LoadFont(fontTagXml->m_italicFontFilename.c_str());
IFFont* boldItalic = LoadFont(fontTagXml->m_boldItalicFontFilename.c_str());
// Only continue if all fonts were created successfully
if (normal && bold && italic && boldItalic)
{
fontFamily.reset(new FontFamily(),
[this](FontFamily* fontFamily)
{
ReleaseFontFamily(fontFamily);
});
// Map the font family name both by path and by name defined
// within the Font Family XML itself. This allows font
// families to also be referenced simply by name.
if (!AddFontFamilyToMaps(fontFamilyFullPath, xmlData.m_fontFamilyName, fontFamily))
{
SAFE_RELEASE(normal);
SAFE_RELEASE(bold);
SAFE_RELEASE(italic);
SAFE_RELEASE(boldItalic);
return nullptr;
}
fontFamily->familyName = xmlData.m_fontFamilyName;
fontFamily->normal = normal;
fontFamily->bold = bold;
fontFamily->italic = italic;
fontFamily->boldItalic = boldItalic;
}
else
{
SAFE_RELEASE(normal);
SAFE_RELEASE(bold);
SAFE_RELEASE(italic);
SAFE_RELEASE(boldItalic);
}
}
}
}
if (!fontFamily)
{
// Unable to load font family XML, so load font normally and associate
// it with a font family
IFFont* font = LoadFont(fontFamilyName);
if (font)
{
// Create a font family from a single font by assigning all the
// font family stylings to the same font
fontFamily.reset(new FontFamily(),
[this](FontFamily* fontFamily)
{
ReleaseFontFamily(fontFamily);
});
// Use filepath as familyName so font loading/unloading doesn't break with duplicate file names
fontFamily->familyName = fontFamilyName;
if (!AddFontFamilyToMaps(fontFamilyName, fontFamily->familyName, fontFamily))
{
SAFE_RELEASE(font);
return nullptr;
}
// Assign all stylings to the same font
fontFamily->normal = font;
fontFamily->bold = font;
fontFamily->italic = font;
fontFamily->boldItalic = font;
// The other three stylings need to have their ref count
// incremented (even though in this particular case its all the
// same font) because when ReleaseFontFamily executes all fonts
// in the family will be (corresondingly) Release'd.
fontFamily->bold->AddRef();
fontFamily->italic->AddRef();
fontFamily->boldItalic->AddRef();
}
}
// Persist fonts for application lifetime to prevent unnecessary work
if (r_persistFontFamilies > 0)
{
m_persistedFontFamilies.emplace_back(FontFamilyPtr(fontFamily));
}
return fontFamily;
}
FontFamilyPtr AZ::AtomFont::GetFontFamily(const char* fontFamilyName)
{
FontFamilyPtr fontFamily = nullptr;
// The given string could either be: a font family name (defined in font
// family XML), a file path (for regular fonts mapped as font families),
// or just the filename of a font itself. Fonts are mapped by font family
// name or by filepath, so attempt lookup using the map first since it's
// the fastest.
string loweredName = string(fontFamilyName).Trim().MakeLower();
auto it = m_fontFamilies.find(PathUtil::MakeGamePath(loweredName).c_str());
if (it != m_fontFamilies.end())
{
fontFamily = FontFamilyPtr(it->second);
}
else
{
// Iterate through all fonts, returning the first match where simply
// the filename of a font could be a match. This case will likely be
// hit when text markup references a font that doesn't belong to a
// font family.
for (const auto& fontFamilyIter : m_fontFamilies)
{
const AZStd::string& mappedFontFamilyName = fontFamilyIter.first;
string mappedFilenameNoExtension = PathUtil::GetFileName(mappedFontFamilyName.c_str());
string searchStringFilenameNoExtension = PathUtil::GetFileName(loweredName);
if (mappedFilenameNoExtension == searchStringFilenameNoExtension)
{
fontFamily = FontFamilyPtr(fontFamilyIter.second);
break;
}
}
}
return fontFamily;
}
void AZ::AtomFont::AddCharsToFontTextures(FontFamilyPtr fontFamily, const char* chars, int glyphSizeX, int glyphSizeY)
{
fontFamily->normal->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
fontFamily->bold->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
fontFamily->italic->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
fontFamily->boldItalic->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
}
string AZ::AtomFont::GetLoadedFontNames() const
{
string ret;
for (FontMapConstItor it = m_fonts.begin(), itEnd = m_fonts.end(); it != itEnd; ++it)
{
FFont* font = it->second;
if (font)
{
if (!ret.empty())
{
ret += ",";
}
ret += font->GetName();
}
}
return ret;
}
void AZ::AtomFont::OnLanguageChanged()
{
ReloadAllFonts();
EBUS_EVENT(LanguageChangeNotificationBus, LanguageChanged);
}
void AZ::AtomFont::ReloadAllFonts()
{
// Persist fonts for application lifetime to prevent unnecessary work
m_persistedFontFamilies.clear();
AZStd::list<AZStd::string> fontFamilyFilenames;
AZStd::list<FontFamily*> fontFamilyWeakPtrs;
// Iterate through all currently loaded font families
for (auto it : m_fontFamilyReverseLookup)
{
fontFamilyWeakPtrs.push_back(it.first);
fontFamilyFilenames.push_back(it.second->first);
}
// Release font-family resources and unmap them
for (auto fontFamily : fontFamilyWeakPtrs)
{
ReleaseFontFamily(fontFamily);
}
// Reload the font families
for (auto familyFilename : fontFamilyFilenames)
{
LoadFontFamily(familyFilename.c_str());
}
// All UI text components need to reload their font assets (both in-game
// and in-editor).
EBUS_EVENT(FontNotificationBus, OnFontsReloaded);
}
void AZ::AtomFont::UnregisterFont(const char* fontName)
{
FontMapItor it = m_fonts.find(CONST_TEMP_STRING(fontName));
#if defined(AZ_ENABLE_TRACING)
IFFont* fontPtr = it->second;
#endif
if (it != m_fonts.end())
{
m_fonts.erase(it);
}
#if defined(AZ_ENABLE_TRACING)
// Make sure the font being released isn't currently in use by a font family.
// If it is, the FontFamily will have a dangling pointer and will cause a
// crash when the FontFamily eventually gets released.
for (auto reverseMapEntry : m_fontFamilyReverseLookup)
{
FontFamily* fontFamily = reverseMapEntry.first;
AZ_Assert(fontFamily->normal != fontPtr,
"The following font is being freed but still in use by a FontFamily: %s",
fontName);
AZ_Assert(fontFamily->italic != fontPtr,
"The following font is being freed but still in use by a FontFamily: %s",
fontName);
AZ_Assert(fontFamily->bold != fontPtr,
"The following font is being freed but still in use by a FontFamily: %s",
fontName);
AZ_Assert(fontFamily->boldItalic != fontPtr,
"The following font is being freed but still in use by a FontFamily: %s",
fontName);
}
#endif
}
IFFont* AZ::AtomFont::LoadFont(const char* fontName)
{
string fontNameLower = fontName;
fontNameLower.MakeLower();
IFFont* font = GetFont(fontNameLower);
if (font)
{
font->AddRef(); // use existing loaded font
}
else
{
// attempt to create and load a new font, use the font pathname as the font name
font = NewFont(fontNameLower);
if (!font)
{
string errorMsg = "Error creating a new font named ";
errorMsg += fontNameLower;
errorMsg += ".";
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, errorMsg.c_str());
}
else
{
// creating font adds one to its refcount so no need for AddRef here
if (!font->Load(fontNameLower))
{
string errorMsg = "Error loading a font from ";
errorMsg += fontNameLower;
errorMsg += ".";
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, errorMsg);
font->Release();
font = nullptr;
}
}
}
return font;
}
void AZ::AtomFont::ReleaseFontFamily(FontFamily* fontFamily)
{
// Ensure that Font Family was mapped prior to destruction
const bool isMapped = m_fontFamilyReverseLookup.find(fontFamily) != m_fontFamilyReverseLookup.end();
if (!isMapped)
{
return;
}
// Note that the FontFamily is mapped both by filename and by "family name"
auto it = m_fontFamilyReverseLookup[fontFamily];
m_fontFamilies.erase(it);
string familyName(fontFamily->familyName);
m_fontFamilies.erase(familyName.MakeLower().c_str());
// Reverse lookup is used to avoid needing to store filename path with
// the font family, so we need to remove that entry also.
m_fontFamilyReverseLookup.erase(fontFamily);
SAFE_RELEASE(fontFamily->normal);
SAFE_RELEASE(fontFamily->bold);
SAFE_RELEASE(fontFamily->italic);
SAFE_RELEASE(fontFamily->boldItalic);
}
bool AZ::AtomFont::AddFontFamilyToMaps(const char* fontFamilyFilename, const char* fontFamilyName, FontFamilyPtr fontFamily)
{
if (!fontFamilyFilename || !fontFamilyName || !fontFamily.get())
{
return false;
}
// We don't support "updating" mapped values.
AZStd::string loweredFilename(PathUtil::MakeGamePath(string(fontFamilyFilename)).c_str());
AZStd::to_lower<AZStd::string::iterator>(loweredFilename.begin(), loweredFilename.end());
if (m_fontFamilies.find(loweredFilename) != m_fontFamilies.end())
{
string warnMsg;
warnMsg.Format("Couldn't load Font Family '%s': already loaded", fontFamilyFilename);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, warnMsg.c_str());
return false;
}
// Similarly, we don't support Font Family XMLs that have the same font
// family name (we assume all Font Family names are unique).
AZStd::string loweredFontFamilyName(fontFamilyName);
AZStd::to_lower<AZStd::string::iterator>(loweredFontFamilyName.begin(), loweredFontFamilyName.end());
if (m_fontFamilies.find(loweredFontFamilyName) != m_fontFamilies.end())
{
string warnMsg;
warnMsg.Format("Couldn't load Font Family '%s': already loaded", fontFamilyName);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, warnMsg.c_str());
return false;
}
// First, insert by filename
AZStd::pair<AZStd::string, AZStd::weak_ptr<FontFamily>> insertPair(loweredFilename, fontFamily);
auto iterPosition = m_fontFamilies.insert(insertPair).first;
m_fontFamilyReverseLookup[fontFamily.get()] = iterPosition;
// Then, by Font Family name
AZStd::pair<AZStd::string, AZStd::weak_ptr<FontFamily>> nameInsertPair(loweredFontFamilyName, fontFamily);
m_fontFamilies.insert(nameInsertPair);
return true;
}
XmlNodeRef AZ::AtomFont::LoadFontFamilyXml(const char* fontFamilyName, string& outputDirectory, string& outputFullPath)
{
outputFullPath = fontFamilyName;
outputDirectory = PathUtil::GetPath(fontFamilyName);
XmlNodeRef root = SafeLoadXmlFromFile(outputFullPath);
// When parsing a <font> tag in markup, only the font name is given and
// not a path, so we try to build a "best guess" path from the name.
if (!root)
{
string fileNoExtension(PathUtil::GetFileName(fontFamilyName));
string fileExtension(PathUtil::GetExt(fontFamilyName));
if (fileExtension.empty())
{
fileExtension = ".fontfamily";
}
// Try: "fonts/fontName.fontfamily"
outputDirectory = string("fonts/");
outputFullPath = outputDirectory + fileNoExtension + fileExtension;
root = SafeLoadXmlFromFile(outputFullPath);
// Finally, try: "fonts/fontName/fontName.fontfamily"
if (!root)
{
outputDirectory = string("fonts/") + fileNoExtension + "/";
outputFullPath = outputDirectory + fileNoExtension + fileExtension;
root = SafeLoadXmlFromFile(outputFullPath);
}
}
return root;
}
#endif
@@ -0,0 +1,111 @@
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// Russian resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS)
#ifdef _WIN32
LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT
#pragma code_page(1251)
#endif //_WIN32
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"..\Include\resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // Russian resources
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// German (Germany) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEU)
#ifdef _WIN32
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN
#pragma code_page(1252)
#endif //_WIN32
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x17L
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x4L
FILETYPE 0x2L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000904b0"
BEGIN
VALUE "CompanyName", "Amazon.com, Inc."
VALUE "FileVersion", "1, 0, 0, 1"
VALUE "LegalCopyright", "Portions of this file Copyright (c) Amazon.com, Inc. or its affiliates. All Rights Reserved. Original file Copyright (c) Crytek GMBH. Used under license by Amazon.com, Inc. and its affiliates."
VALUE "ProductName", "Lumberyard"
VALUE "ProductVersion", "1, 0, 0, 1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x9, 1200
END
END
#endif // German (Germany) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#include "AtomFontSystemComponent.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/EditContextConstants.inl>
#include <AzFramework/Components/ConsoleBus.h>
#include <ISystem.h>
#include <AtomLyIntegration/AtomFont/AtomNullFont.h>
#include <AtomLyIntegration/AtomFont/AtomFont.h>
namespace AZ
{
namespace Render
{
void AtomFontSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
{
serialize->Class<AtomFontSystemComponent, AZ::Component>()
->Version(0)
;
if (AZ::EditContext* ec = serialize->GetEditContext())
{
ec->Class<AtomFontSystemComponent>("Font", "Manages lifetime of the font subsystem")
->ClassElement(Edit::ClassElements::EditorData, "")
->Attribute(Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(Edit::Attributes::AutoExpand, true)
;
}
}
}
void AtomFontSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AtomFontService"));
}
void AtomFontSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AtomFontService"));
}
void AtomFontSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
}
void AtomFontSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
}
void AtomFontSystemComponent::Activate()
{
AZ::CryFontCreationRequestBus::Handler::BusConnect();
}
void AtomFontSystemComponent::Deactivate()
{
AZ::CryFontCreationRequestBus::Handler::BusDisconnect();
}
bool AtomFontSystemComponent::CreateCryFont(SSystemGlobalEnvironment& env, [[maybe_unused]] const SSystemInitParams& initParams)
{
ISystem* system = env.pSystem;
#if !defined(AZ_MONOLITHIC_BUILD)
// When module is linked dynamically, we must set our gEnv pointer.
// When module is linked statically, we'll share the application's gEnv pointer.
gEnv = system->GetGlobalEnvironment();
#endif
if (env.IsDedicated())
{
#if defined(USE_NULLFONT)
env.pCryFont = new AtomNullFont();
#else
// The NULL font implementation must be present for all platforms
// supporting running as a pure dedicated server.
system->GetILog()->LogError("Missing NULL font implementation for dedicated server");
env.pCryFont = NULL;
#endif
}
else
{
#if defined(USE_NULLFONT) && defined(USE_NULLFONT_ALWAYS)
env.pCryFont = new AtomNullFont();
#else
env.pCryFont = new AtomFont(system);
#endif
}
return env.pCryFont != 0;
}
void AtomFontSystemComponent::OnCrySystemShutdown([[maybe_unused]] ISystem& system)
{
#if !defined(AZ_MONOLITHIC_BUILD)
gEnv = nullptr;
#endif
}
} // namespace Render
} // namespace AZ
@@ -0,0 +1,56 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <CryCommon/CryFontBus.h>
#include <CryCommon/CrySystemBus.h>
namespace AZ
{
namespace Render
{
class AtomFontSystemComponent
: public AZ::Component
, private AZ::CryFontCreationRequestBus::Handler
, private CrySystemEventBus::Handler
{
public:
AZ_COMPONENT(AtomFontSystemComponent, "{29DC7010-CF2A-4EE4-91F8-8E3C8BE65F41}");
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
protected:
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// CryFontCreationBus
bool CreateCryFont(SSystemGlobalEnvironment& env, const SSystemInitParams& initParams) override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// CryFontCreationBus
void OnCrySystemShutdown(ISystem& system) override;
////////////////////////////////////////////////////////////////////////
};
} // namespace Render
} // namespace AZ
@@ -0,0 +1,14 @@
/*
* 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.
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
@@ -0,0 +1,24 @@
/*
* 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.
// Description : Dummy font implementation (dedicated server)
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if defined(USE_NULLFONT)
#include <AtomLyIntegration/AtomFont//AtomNullFont.h>
AZ::AtomNullFFont AZ::AtomNullFont::NullFFont;
#endif // USE_NULLFONT
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,81 @@
/*
* 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.
// Description : XML parsing to load a font.
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include "FFontXML_Internal.h"
#include <AtomLyIntegration/AtomFont/FFont.h>
#include <AtomLyIntegration/AtomFont/FontTexture.h>
#include <CryCommon/Cry_Math.h>
#include <CryCommon/CryPath.h>
#include <AzCore/PlatformIncl.h>
//////////////////////////////////////////////////////////////////////////
// Main loading function
bool AZ::FFont::Load(const char* xmlFile)
{
m_curPath = "";
if (xmlFile)
{
m_curPath = PathUtil::GetPath(xmlFile);
}
XmlNodeRef root = GetISystem()->LoadXmlFromFile(xmlFile);
if (!root)
{
return false;
}
AtomFontInternal::XmlFontShader xmlfs(this);
xmlfs.ScanXmlNodesRecursively(root);
// if this was not a valid font XML file then return false
if (!m_fontTexture || !m_fontBuffer)
{
return false;
}
// if there was a font effect file then parse that for effects
if (!xmlfs.m_strFontEffectPath.empty())
{
XmlNodeRef fontEffectRoot = GetISystem()->LoadXmlFromFile(xmlfs.m_strFontEffectPath.c_str());
if (!fontEffectRoot)
{
AZ_Warning("Font", false, "Error parsing font file %s, 'effectfile' pathname %s could not be found.",
xmlFile, xmlfs.m_strFontEffectPath.c_str());
return false;
}
if (m_effects.size() > 1 || (m_effects.size() == 1 && (m_effects[0].m_name != "default" || m_effects[0].m_passes.size() > 1)))
{
AZ_Warning("Font", false, "Error parsing font file %s, 'effectfile' and 'effect' cannot both be used in the same font file.",
xmlFile);
m_effects.clear();
}
// parse the font effects file, adding to this font object
AtomFontInternal::XmlFontShader xmlfsEffect(this);
xmlfsEffect.ScanXmlNodesRecursively(fontEffectRoot);
}
return true;
}
#endif
@@ -0,0 +1,408 @@
/*
* 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.
#pragma once
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include <AtomLyIntegration/AtomFont/FFont.h>
#include <AtomLyIntegration/AtomFont/FontTexture.h>
#include <CryCommon/Cry_Math.h>
#include <CryCommon/CryPath.h>
#include <AzCore/PlatformIncl.h>
//////////////////////////////////////////////////////////////////////////
// Xml parser implementation
namespace AtomFontInternal
{
enum
{
ELEMENT_UNKNOWN = 0,
ELEMENT_FONT = 1,
ELEMENT_EFFECT = 2,
ELEMENT_EFFECTFILE = 3,
ELEMENT_PASS = 4,
ELEMENT_PASS_COLOR = 5,
ELEMENT_PASS_POSOFFSET = 12,
ELEMENT_PASS_BLEND = 14
};
inline int GetBlendModeFromString(const string& str, bool dst)
{
int blend = GS_BLSRC_ONE;
if (str == "zero")
{
blend = dst ? GS_BLDST_ZERO : GS_BLSRC_ZERO;
}
else if (str == "one")
{
blend = dst ? GS_BLDST_ONE : GS_BLSRC_ONE;
}
else if (str == "srcalpha" ||
str == "src_alpha")
{
blend = dst ? GS_BLDST_SRCALPHA : GS_BLSRC_SRCALPHA;
}
else if (str == "invsrcalpha" ||
str == "inv_src_alpha")
{
blend = dst ? GS_BLDST_ONEMINUSSRCALPHA : GS_BLSRC_ONEMINUSSRCALPHA;
}
else if (str == "dstalpha" ||
str == "dst_alpha")
{
blend = dst ? GS_BLDST_DSTALPHA : GS_BLSRC_DSTALPHA;
}
else if (str == "invdstalpha" ||
str == "inv_dst_alpha")
{
blend = dst ? GS_BLDST_ONEMINUSDSTALPHA : GS_BLSRC_ONEMINUSDSTALPHA;
}
else if (str == "dstcolor" ||
str == "dst_color")
{
blend = GS_BLSRC_DSTCOL;
}
else if (str == "srccolor" ||
str == "src_color")
{
blend = GS_BLDST_SRCCOL;
}
else if (str == "invdstcolor" ||
str == "inv_dst_color")
{
blend = GS_BLSRC_ONEMINUSDSTCOL;
}
else if (str == "invsrccolor" ||
str == "inv_src_color")
{
blend = GS_BLDST_ONEMINUSSRCCOL;
}
return blend;
}
inline int CreateTTFFontFlag(AZ::FontSmoothMethod smoothMethod, AZ::FontSmoothAmount smoothAmount)
{
return (
((static_cast<int>(smoothMethod) << TTFFLAG_SMOOTH_SHIFT) & TTFFLAG_SMOOTH_MASK) |
((static_cast<int>(smoothAmount) << TTFFLAG_SMOOTH_AMOUNT_SHIFT) & TTFFLAG_SMOOTH_AMOUNT_MASK)
);
}
inline AZ::FontSmoothMethod TranslateSmoothMethod(const string& value)
{
AZ::FontSmoothMethod smoothMethod = AZ::FontSmoothMethod::None;
if (value == "blur")
{
smoothMethod = AZ::FontSmoothMethod::Blur;
}
else if (value == "supersample")
{
smoothMethod = AZ::FontSmoothMethod::SuperSample;
}
return smoothMethod;
}
inline AZ::FontSmoothAmount TranslateSmoothAmount(int value)
{
AZ::FontSmoothAmount smoothAmount = AZ::FontSmoothAmount::None;
if (value == 1)
{
smoothAmount = AZ::FontSmoothAmount::x2;
}
else if (value > 1)
{
smoothAmount = AZ::FontSmoothAmount::x4;
}
return smoothAmount;
}
class XmlFontShader
{
static const int DefaultSlotWidthSize = 16;
static const int DefaultSlotHeightSize = 8;
public:
XmlFontShader(AZ::FFont* font)
: m_font(font)
, m_nElement(ELEMENT_UNKNOWN)
, m_slotSizes(DefaultSlotWidthSize, DefaultSlotHeightSize)
, m_effect(NULL)
, m_pass(NULL)
, m_FontTexSize(0, 0)
, m_FontSmoothAmount(AZ::FontSmoothAmount::None)
, m_FontSmoothMethod(AZ::FontSmoothMethod::None)
{
}
~XmlFontShader()
{
}
void ScanXmlNodesRecursively(XmlNodeRef node)
{
if (!node)
{
return;
}
FoundElement(node->getTag());
for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
{
const char* key = "";
const char* value = "";
if (node->getAttributeByIndex(i, &key, &value))
{
FoundAttribute(key, value);
}
}
for (int i = 0, count = node->getChildCount(); i < count; ++i)
{
XmlNodeRef child = node->getChild(i);
ScanXmlNodesRecursively(child);
}
}
private:
void FoundElementImpl();
// notify methods
void FoundElement(const string& name)
{
//MessageBox(NULL, string("[" + name + "]").c_str(), "FoundElement", MB_OK);
// process the previous element
switch (m_nElement)
{
case ELEMENT_FONT:
{
if (!m_FontTexSize.x || !m_FontTexSize.y)
{
m_FontTexSize.set(512, 512);
}
bool fontLoaded = m_font->Load(m_strFontPath.c_str(), m_FontTexSize.x, m_FontTexSize.y, m_slotSizes.x, m_slotSizes.y, CreateTTFFontFlag(m_FontSmoothMethod, m_FontSmoothAmount), m_SizeRatio);
if (!fontLoaded)
{
FoundElementImpl();
}
}
break;
default:
break;
}
// Translate the m_nElement name to a define
if (name == "font")
{
m_nElement = ELEMENT_FONT;
}
else if (name == "effect")
{
m_nElement = ELEMENT_EFFECT;
}
else if (name == "effectfile")
{
m_nElement = ELEMENT_EFFECTFILE;
}
else if (name == "pass")
{
m_pass = NULL;
m_nElement = ELEMENT_PASS;
if (m_effect)
{
m_pass = m_effect->AddPass();
}
}
else if (name == "color")
{
m_nElement = ELEMENT_PASS_COLOR;
}
else if (name == "pos" ||
name == "offset")
{
m_nElement = ELEMENT_PASS_POSOFFSET;
}
else if (name == "blend" ||
name == "blending")
{
m_nElement = ELEMENT_PASS_BLEND;
}
else
{
m_nElement = ELEMENT_UNKNOWN;
}
}
void FoundAttribute(const string& name, const string& value)
{
//MessageBox(NULL, string(name + "\n" + value).c_str(), "FoundAttribute", MB_OK);
switch (m_nElement)
{
case ELEMENT_FONT:
if (name == "path")
{
m_strFontPath = value;
}
else if (name == "w")
{
m_FontTexSize.x = (long)atof(value.c_str());
}
else if (name == "h")
{
m_FontTexSize.y = (long)atof(value.c_str());
}
else if (name == "widthslots")
{
m_slotSizes.x = (int)atoi(value.c_str());
}
else if (name == "heightslots")
{
m_slotSizes.y = (int)atoi(value.c_str());
}
else if (name == "sizeratio")
{
m_SizeRatio = static_cast<float>(atof(value.c_str()));
}
else if (name == "smooth")
{
m_FontSmoothMethod = TranslateSmoothMethod(value);
}
else if (name == "smooth_amount")
{
m_FontSmoothAmount = TranslateSmoothAmount((int)atof(value.c_str()));
}
break;
case ELEMENT_EFFECT:
if (name == "name")
{
if (value == "default")
{
m_effect = m_font->GetDefaultEffect();
m_effect->ClearPasses();
}
else
{
m_effect = m_font->AddEffect(value.c_str());
}
}
break;
case ELEMENT_EFFECTFILE:
if (name == "path")
{
m_strFontEffectPath = value;
}
break;
case ELEMENT_PASS_COLOR:
if (!m_pass)
{
break;
}
if (name == "r")
{
m_pass->m_color.r = (uint8_t)((float)atof(value.c_str()) * 255.0f);
}
else if (name == "g")
{
m_pass->m_color.g = (uint8_t)((float)atof(value.c_str()) * 255.0f);
}
else if (name == "b")
{
m_pass->m_color.b = (uint8_t)((float)atof(value.c_str()) * 255.0f);
}
else if (name == "a")
{
m_pass->m_color.a = (uint8_t)((float)atof(value.c_str()) * 255.0f);
}
break;
case ELEMENT_PASS_POSOFFSET:
if (!m_pass)
{
break;
}
if (name == "x")
{
m_pass->m_posOffset.x = (float)atoi(value.c_str());
}
else if (name == "y")
{
m_pass->m_posOffset.y = (float)atoi(value.c_str());
}
break;
case ELEMENT_PASS_BLEND:
if (!m_pass)
{
break;
}
if (name == "src")
{
m_pass->m_blendSrc = GetBlendModeFromString(value, false);
}
else if (name == "dst")
{
m_pass->m_blendDest = GetBlendModeFromString(value, true);
}
else if (name == "type")
{
if (value == "modulate")
{
m_pass->m_blendSrc = GS_BLSRC_SRCALPHA;
m_pass->m_blendDest = GS_BLDST_ONEMINUSSRCALPHA;
}
else if (value == "additive")
{
m_pass->m_blendSrc = GS_BLSRC_SRCALPHA;
m_pass->m_blendDest = GS_BLDST_ONE;
}
}
break;
default:
case ELEMENT_UNKNOWN:
break;
}
}
public:
AZ::FFont* m_font;
unsigned long m_nElement;
AZ::FFont::FontEffect* m_effect;
AZ::FFont::FontRenderingPass* m_pass;
string m_strFontPath;
string m_strFontEffectPath;
vector2l m_FontTexSize;
AZ::AtomFont::GlyphSize m_slotSizes;
float m_SizeRatio = IFFontConstants::defaultSizeRatio;
AZ::FontSmoothMethod m_FontSmoothMethod;
AZ::FontSmoothAmount m_FontSmoothAmount;
};
}
#endif
@@ -0,0 +1,332 @@
/*
* 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:
// - Render a glyph outline into a bitmap using FreeType 2
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include <AtomLyIntegration/AtomFont/FontRenderer.h>
#include <freetype/ftoutln.h>
#include <freetype/ftglyph.h>
#include <freetype/ftimage.h>
#include <AzCore/Casting/lossy_cast.h>
// Sizes are defined in in 26.6 fixed float format (TT_F26Dot6), where
// 1 unit is 1/64 of a pixel.
constexpr int FractionalPixelUnits = 64;
namespace
{
FT_Int32 GetLoadFlags(AZ::FFont::HintBehavior hintBehavior)
{
switch (hintBehavior)
{
case AZ::FFont::HintBehavior::NoHinting:
{
return FT_LOAD_NO_HINTING;
break;
}
case AZ::FFont::HintBehavior::AutoHint:
{
return FT_LOAD_FORCE_AUTOHINT;
break;
}
}
return FT_LOAD_DEFAULT;
}
FT_Int32 GetLoadTarget(AZ::FFont::HintStyle hintStyle)
{
if (hintStyle == AZ::FFont::HintStyle::Light)
{
return FT_LOAD_TARGET_LIGHT;
}
return FT_LOAD_TARGET_NORMAL;
}
FT_Render_Mode GetRenderMode(AZ::FFont::HintStyle hintStyle)
{
// We use the hint style to drive the render mode also. These should
// usually be correlated with each other for best results, even though
// they could technically be different.
if (hintStyle == AZ::FFont::HintStyle::Light)
{
return FT_RENDER_MODE_LIGHT;
}
return FT_RENDER_MODE_NORMAL;
}
}
//-------------------------------------------------------------------------------------------------
AZ::FontRenderer::FontRenderer()
: m_library(0)
, m_face(0)
, m_glyph(0)
, m_sizeRatio(IFFontConstants::defaultSizeRatio)
, m_encoding(AZ_FONT_ENCODING_UNICODE)
, m_glyphBitmapWidth(0)
, m_glyphBitmapHeight(0)
{
}
//-------------------------------------------------------------------------------------------------
AZ::FontRenderer::~FontRenderer()
{
FT_Done_Face(m_face);
;
FT_Done_FreeType(m_library);
m_face = NULL;
m_library = NULL;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::LoadFromFile(const string& fileName)
{
int iError = FT_Init_FreeType(&m_library);
if (iError)
{
return 0;
}
if (m_face)
{
FT_Done_Face(m_face);
m_face = 0;
}
iError = FT_New_Face(m_library, fileName.c_str(), 0, &m_face);
if (iError)
{
return 0;
}
SetEncoding(AZ_FONT_ENCODING_UNICODE);
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::LoadFromMemory(unsigned char* buffer, int bufferSize)
{
int iError = FT_Init_FreeType(&m_library);
if (iError)
{
return 0;
}
if (m_face)
{
FT_Done_Face(m_face);
m_face = 0;
}
iError = FT_New_Memory_Face(m_library, buffer, bufferSize, 0, &m_face);
if (iError)
{
return 0;
}
SetEncoding(AZ_FONT_ENCODING_UNICODE);
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::Release()
{
FT_Done_Face(m_face);
;
FT_Done_FreeType(m_library);
m_face = NULL;
m_library = NULL;
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::SetGlyphBitmapSize(int width, int height, float sizeRatio)
{
m_glyphBitmapWidth = width;
m_glyphBitmapHeight = height;
// Assign the given scale for texture slots as long as its positive
m_sizeRatio = sizeRatio > 0.0f ? sizeRatio : m_sizeRatio;
FT_Set_Pixel_Sizes(m_face, (int)(m_glyphBitmapWidth * m_sizeRatio), (int)(m_glyphBitmapHeight * m_sizeRatio));
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::GetGlyphBitmapSize(int* width, int* height)
{
if (width)
{
*width = m_glyphBitmapWidth;
}
if (height)
{
*height = m_glyphBitmapHeight;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::SetEncoding(FT_Encoding encoding)
{
if (FT_Select_Charmap(m_face, encoding))
{
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
int AZ::FontRenderer::GetGlyph(GlyphBitmap* glyphBitmap, int* horizontalAdvance, uint8_t* glyphWidth, uint8_t* glyphHeight, int32_t& m_characterOffsetX, int32_t& m_characterOffsetY, int iX, int iY, int characterCode, const FFont::FontHintParams& fontHintParams)
{
FT_Int32 loadFlags = GetLoadFlags(fontHintParams.hintBehavior);
loadFlags |= GetLoadTarget(fontHintParams.hintStyle);
int iError = FT_Load_Char(m_face, characterCode, loadFlags);
if (iError)
{
return 0;
}
FT_Render_Mode renderMode = GetRenderMode(fontHintParams.hintStyle);
m_glyph = m_face->glyph;
iError = FT_Render_Glyph(m_glyph, renderMode);
if (iError)
{
return 0;
}
if (horizontalAdvance)
{
*horizontalAdvance = m_glyph->metrics.horiAdvance / FractionalPixelUnits;
}
if (glyphWidth)
{
*glyphWidth = m_glyph->bitmap.width;
}
if (glyphHeight)
{
*glyphHeight = m_glyph->bitmap.rows;
}
unsigned char* buffer = glyphBitmap->GetBuffer();
AZ_Assert(buffer, "GlyphBitmap: bad buffer");
uint32_t dwGlyphWidth = glyphBitmap->GetWidth();
m_characterOffsetX = m_glyph->bitmap_left;
m_characterOffsetY = (static_cast<int32_t>(round(m_glyphBitmapHeight * m_sizeRatio)) - m_glyph->bitmap_top);
const int textureSlotBufferWidth = glyphBitmap->GetWidth();
const int textureSlotBufferHeight = glyphBitmap->GetHeight();
// might happen if font characters are too big or cache dimenstions in font.xml is too small "<font path="VeraMono.ttf" w="320" h="368"/>"
const bool charWidthFits = iX + m_glyph->bitmap.width <= textureSlotBufferWidth;
const bool charHeightFits = iY + m_glyph->bitmap.rows <= textureSlotBufferHeight;
const bool charFitsInSlot = charWidthFits && charHeightFits;
AZ_Error("Font", charFitsInSlot, "Character code %d doesn't fit in font texture; check 'sizeRatio' attribute in font XML or adjust this character's sizing in the font.", characterCode);
// Since we might be re-rendering/overwriting a glyph that already exists
// in the font texture, clear the contents of this particular slot so no
// artifacts of the previous glyph remain.
glyphBitmap->Clear();
// Restrict iteration to smallest of either the texture slot or glyph
// bitmap buffer ranges
const int bufferMaxIterWidth = AZStd::GetMin<int>(textureSlotBufferWidth, m_glyph->bitmap.width);
const int bufferMaxIterHeight = AZStd::GetMin<int>(textureSlotBufferHeight, m_glyph->bitmap.rows);
for (int i = 0; i < bufferMaxIterHeight; i++)
{
int iNewY = i + iY;
for (int j = 0; j < bufferMaxIterWidth; j++)
{
unsigned char cColor = m_glyph->bitmap.buffer[(i * m_glyph->bitmap.width) + j];
int iOffset = iNewY * dwGlyphWidth + iX + j;
if (iOffset >= (int)dwGlyphWidth * m_glyphBitmapHeight)
{
continue;
}
buffer[iOffset] = cColor;
// buffer[iOffset] = cColor/2+32; // debug - visualize character in a block
}
}
return 1;
}
int AZ::FontRenderer::GetGlyphScaled([[maybe_unused]] GlyphBitmap* glyphBitmap, [[maybe_unused]] int* glyphWidth, [[maybe_unused]] int* glyphHeight, [[maybe_unused]] int iX, [[maybe_unused]] int iY, [[maybe_unused]] float scaleX, [[maybe_unused]] float scaleY, [[maybe_unused]] int characterCode)
{
return 1;
}
Vec2 AZ::FontRenderer::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph)
{
FT_Vector kerningOffsets;
kerningOffsets.x = kerningOffsets.y = 0;
if (FT_HAS_KERNING(m_face))
{
const FT_UInt leftGlyphIndex = FT_Get_Char_Index(m_face, leftGlyph);
const FT_UInt rightGlyphIndex = FT_Get_Char_Index(m_face, rightGlyph);
FT_Error ftError = FT_Get_Kerning(m_face, leftGlyphIndex, rightGlyphIndex, FT_KERNING_DEFAULT, &kerningOffsets);
#if !defined(_RELEASE)
if (0 != ftError)
{
string warnMsg;
warnMsg.Format("FT_Get_Kerning returned %d", ftError);
CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, warnMsg.c_str());
}
#endif
}
return Vec2(azlossy_cast<float>(kerningOffsets.x / FractionalPixelUnits), azlossy_cast<float>(kerningOffsets.y / FractionalPixelUnits));
}
float AZ::FontRenderer::GetAscenderToHeightRatio()
{
return (static_cast<float>(m_face->ascender) / static_cast<float>(m_face->height));
}
#endif // #if !defined(USE_NULLFONT_ALWAYS)
@@ -0,0 +1,575 @@
/*
* 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 <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include <AtomLyIntegration/AtomFont/FontTexture.h>
#include <CryCommon/UnicodeIterator.h>
#include <AzCore/IO/FileIO.h>
//-------------------------------------------------------------------------------------------------
AZ::FontTexture::FontTexture()
: m_slotUsage(1)
, m_width(0)
, m_height(0)
, m_invWidth(0.0f)
, m_invHeight(0.0f)
, m_cellWidth(0)
, m_cellHeight(0)
, m_textureCellWidth(0)
, m_textureCellHeight(0)
, m_widthCellCount(0)
, m_heightCellCount(0)
, m_textureSlotCount(0)
, m_buffer(0)
, m_smoothMethod(AZ::FontSmoothMethod::None)
, m_smoothAmount(AZ::FontSmoothAmount::None)
{
}
//-------------------------------------------------------------------------------------------------
AZ::FontTexture::~FontTexture()
{
Release();
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::CreateFromFile(const string& fileName, int width, int height, AZ::FontSmoothMethod smoothMethod, AZ::FontSmoothAmount smoothAmount, int widthCellCount, int heightCellCount)
{
if (!m_glyphCache.LoadFontFromFile(fileName))
{
Release();
return 0;
}
if (!Create(width, height, smoothMethod, smoothAmount, widthCellCount, heightCellCount))
{
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::CreateFromMemory(unsigned char* fileData, int dataSize, int width, int height, AZ::FontSmoothMethod smoothMethod, AZ::FontSmoothAmount smoothAmount, int widthCellCount, int heightCellCount, float sizeRatio)
{
if (!m_glyphCache.LoadFontFromMemory(fileData, dataSize))
{
Release();
return 0;
}
if (!Create(width, height, smoothMethod, smoothAmount, widthCellCount, heightCellCount, sizeRatio))
{
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::Create(int width, int height, AZ::FontSmoothMethod smoothMethod, AZ::FontSmoothAmount smoothAmount, int widthCellCount, int heightCellCount, float sizeRatio)
{
m_buffer = new FONT_TEXTURE_TYPE[width * height];
if (!m_buffer)
{
return 0;
}
memset(m_buffer, 0, width * height * sizeof(FONT_TEXTURE_TYPE));
if (!(widthCellCount * heightCellCount))
{
return 0;
}
m_width = width;
m_height = height;
m_invWidth = 1.0f / (float)width;
m_invHeight = 1.0f / (float)height;
m_widthCellCount = widthCellCount;
m_heightCellCount = heightCellCount;
m_textureSlotCount = m_widthCellCount * m_heightCellCount;
m_smoothMethod = smoothMethod;
m_smoothAmount = smoothAmount;
m_cellWidth = m_width / m_widthCellCount;
m_cellHeight = m_height / m_heightCellCount;
m_textureCellWidth = m_cellWidth * m_invWidth;
m_textureCellHeight = m_cellHeight * m_invHeight;
if (!m_glyphCache.Create(AZ_FONT_GLYPH_CACHE_SIZE, m_cellWidth, m_cellHeight, smoothMethod, smoothAmount, sizeRatio))
{
Release();
return 0;
}
if (!CreateSlotList(m_textureSlotCount))
{
Release();
return 0;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::Release()
{
delete[] m_buffer;
m_buffer = 0;
ReleaseSlotList();
m_slotIndexMap.clear();
m_glyphCache.Release();
m_widthCellCount = 0;
m_heightCellCount = 0;
m_textureSlotCount = 0;
m_width = 0;
m_height = 0;
m_invWidth = 0.0f;
m_invHeight = 0.0f;
m_cellWidth = 0;
m_cellHeight = 0;
m_smoothMethod = AZ::FontSmoothMethod::None;
m_smoothAmount = AZ::FontSmoothAmount::None;
m_textureCellWidth = 0.0f;
m_textureCellHeight = 0.0f;
m_slotUsage = 1;
return 1;
}
//-------------------------------------------------------------------------------------------------
uint32_t AZ::FontTexture::GetSlotChar(int slotIndex) const
{
return m_slotList[slotIndex]->m_currentCharacter;
}
//-------------------------------------------------------------------------------------------------
AZ::TextureSlot* AZ::FontTexture::GetCharSlot(uint32_t character, const AZ::AtomFont::GlyphSize& glyphSize)
{
TextureSlotKey slotKey = GetTextureSlotKey(character, glyphSize);
TextureSlotTableItor pItor = m_slotIndexMap.find(slotKey);
if (pItor != m_slotIndexMap.end())
{
return pItor->second;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
AZ::TextureSlot* AZ::FontTexture::GetLRUSlot()
{
uint16_t wMaxSlotAge = 0;
TextureSlot* pLRUSlot = 0;
TextureSlot* slot;
TextureSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
slot = *pItor;
if (slot->m_slotUsage == 0)
{
return slot;
}
else
{
uint16_t slotAge = m_slotUsage - slot->m_slotUsage;
if (slotAge > wMaxSlotAge)
{
pLRUSlot = slot;
wMaxSlotAge = slotAge;
}
}
++pItor;
}
return pLRUSlot;
}
//-------------------------------------------------------------------------------------------------
AZ::TextureSlot* AZ::FontTexture::GetMRUSlot()
{
uint16_t wMinSlotAge = 0xFFFF;
TextureSlot* pMRUSlot = 0;
TextureSlot* slot;
TextureSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
slot = *pItor;
if (slot->m_slotUsage != 0)
{
uint16_t slotAge = m_slotUsage - slot->m_slotUsage;
if (slotAge > wMinSlotAge)
{
pMRUSlot = slot;
wMinSlotAge = slotAge;
}
}
++pItor;
}
return pMRUSlot;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::PreCacheString(const char* string, int* updated, float sizeRatio, const AZ::AtomFont::GlyphSize& glyphSize, const FFont::FontHintParams& fontHintParams)
{
AZ::AtomFont::GlyphSize clampedGlyphSize = ClampGlyphSize(glyphSize, m_cellWidth, m_cellHeight);
uint16_t slotUsage = m_slotUsage++;
int updateCount = 0;
uint32_t character;
for (Unicode::CIterator<const char*, false> it(string); character = *it; ++it)
{
TextureSlot* slot = GetCharSlot(character, clampedGlyphSize);
if (!slot)
{
slot = GetLRUSlot();
if (!slot)
{
return 0;
}
if (!UpdateSlot(slot->m_textureSlot, slotUsage, character, sizeRatio, clampedGlyphSize, fontHintParams))
{
return 0;
}
++updateCount;
}
else
{
slot->m_slotUsage = slotUsage;
}
}
if (updated)
{
*updated = updateCount;
}
if (updated)
{
return 1;
}
return 2;
}
//-------------------------------------------------------------------------------------------------
void AZ::FontTexture::GetTextureCoord(AZ::TextureSlot* slot, float texCoords[4],
int& characterSizeX, int& characterSizeY, int& m_characterOffsetX, int& m_characterOffsetY,
const AZ::AtomFont::GlyphSize& glyphSize) const
{
if (!slot)
{
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>(slot->m_characterWidth * invRequestSizeWidthScale);
int iChHeight = static_cast<int>(slot->m_characterHeight * invRequestSizeHeightScale);
float slotCoord0 = slot->m_texCoords[0];
float slotCoord1 = slot->m_texCoords[1];
texCoords[0] = slotCoord0 - m_invWidth; // extra pixel for nicer bilinear filter
texCoords[1] = slotCoord1 - m_invHeight; // 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_invWidth) * requestSizeWidthScale);
texCoords[3] = slotCoord1 + (((float)iChHeight * m_invHeight) * requestSizeHeightScale);
characterSizeX = iChWidth + 1; // extra pixel for nicer bilinear filter
characterSizeY = 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.
m_characterOffsetX = static_cast<int>(slot->m_characterOffsetX * invRequestSizeWidthScale);
m_characterOffsetY = static_cast<int>(slot->m_characterOffsetY * invRequestSizeHeightScale);
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::GetCharacterWidth(uint32_t character) const
{
TextureSlotTableItorConst pItor = m_slotIndexMap.find(GetTextureSlotKey(character));
if (pItor == m_slotIndexMap.end())
{
return 0;
}
const TextureSlot& rSlot = *pItor->second;
// For proportional fonts, add one pixel of spacing for aesthetic reasons
int proportionalOffset = GetMonospaced() ? 0 : 1;
return rSlot.m_characterWidth + proportionalOffset;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::GetHorizontalAdvance(uint32_t character, const AZ::AtomFont::GlyphSize& glyphSize) const
{
TextureSlotTableItorConst pItor = m_slotIndexMap.find(GetTextureSlotKey(character, glyphSize));
if (pItor == m_slotIndexMap.end())
{
return 0;
}
const TextureSlot& 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.m_horizontalAdvance * AZ::GetMax<float>(1.0f, invRequestSizeWidthScale));
}
//-------------------------------------------------------------------------------------------------
/*
int AZ::FontTexture::GetCharHeightByChar(wchar_t character)
{
TextureSlotTableItor pItor = m_slotIndexMap.find(character);
if (pItor != m_slotIndexMap.end())
{
return pItor->second->m_characterHeight;
}
return 0;
}
*/
//-------------------------------------------------------------------------------------------------
Vec2 AZ::FontTexture::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph)
{
return m_glyphCache.GetKerning(leftGlyph, rightGlyph);
}
//-------------------------------------------------------------------------------------------------
float AZ::FontTexture::GetAscenderToHeightRatio()
{
return m_glyphCache.GetAscenderToHeightRatio();
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::CreateSlotList(int listSize)
{
int y, x;
for (int i = 0; i < listSize; i++)
{
TextureSlot* pTextureSlot = new TextureSlot;
if (!pTextureSlot)
{
return 0;
}
pTextureSlot->m_textureSlot = i;
pTextureSlot->Reset();
y = i / m_widthCellCount;
x = i % m_widthCellCount;
pTextureSlot->m_texCoords[0] = (float)(x * m_textureCellWidth) + (0.5f / (float)m_width);
pTextureSlot->m_texCoords[1] = (float)(y * m_textureCellHeight) + (0.5f / (float)m_height);
m_slotList.push_back(pTextureSlot);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::ReleaseSlotList()
{
TextureSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
delete (*pItor);
pItor = m_slotList.erase(pItor);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::FontTexture::UpdateSlot(int slotIndex, uint16_t slotUsage, uint32_t character, float sizeRatio, const AZ::AtomFont::GlyphSize& glyphSize, const FFont::FontHintParams& fontHintParams)
{
TextureSlot* slot = m_slotList[slotIndex];
if (!slot)
{
return 0;
}
TextureSlotTableItor pItor = m_slotIndexMap.find(GetTextureSlotKey(slot->m_currentCharacter, slot->m_glyphSize));
if (pItor != m_slotIndexMap.end())
{
m_slotIndexMap.erase(pItor);
}
m_slotIndexMap.insert(TextureSlotTableEntry(GetTextureSlotKey(character, glyphSize), slot));
slot->m_glyphSize = glyphSize;
slot->m_slotUsage = slotUsage;
slot->m_currentCharacter = character;
int width = 0;
int height = 0;
// blit the char glyph into the texture
int x = slot->m_textureSlot % m_widthCellCount;
int y = slot->m_textureSlot / m_widthCellCount;
GlyphBitmap* glyphBitmap;
if (glyphSize.x > 0 && glyphSize.y > 0)
{
m_glyphCache.SetGlyphBitmapSize(glyphSize.x, glyphSize.y, sizeRatio);
}
if (!m_glyphCache.GetGlyph(&glyphBitmap, &slot->m_horizontalAdvance, &width, &height, slot->m_characterOffsetX, slot->m_characterOffsetY, character, glyphSize, fontHintParams))
{
return 0;
}
slot->m_characterWidth = width;
slot->m_characterHeight = height;
// 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>(width + 1, m_cellWidth);
const int blitHeight = AZ::GetMin<int>(height + 1, m_cellHeight);
glyphBitmap->BlitTo8(m_buffer, 0, 0,
blitWidth, blitHeight, x * m_cellWidth, y * m_cellHeight, m_width);
return 1;
}
//-------------------------------------------------------------------------------------------------
void AZ::FontTexture::CreateGradientSlot()
{
TextureSlot* slot = GetGradientSlot();
assert(slot->m_currentCharacter == (uint32_t)~0); // 0 needs to be unused spot
slot->Reset();
slot->m_characterWidth = m_cellWidth - 2;
slot->m_characterHeight = m_cellHeight - 2;
slot->SetNotReusable();
int x = slot->m_textureSlot % m_widthCellCount;
int y = slot->m_textureSlot / m_widthCellCount;
assert(sizeof(*m_buffer) == sizeof(uint8_t));
uint8_t* buffer = &m_buffer[x * m_cellWidth + y * m_cellHeight * m_width];
for (uint32_t dwY = 0; dwY < slot->m_characterHeight; ++dwY)
{
for (uint32_t dwX = 0; dwX < slot->m_characterWidth; ++dwX)
{
buffer[dwX + dwY * m_width] = dwY * 255 / (slot->m_characterHeight - 1);
}
}
}
//-------------------------------------------------------------------------------------------------
AZ::TextureSlot* AZ::FontTexture::GetGradientSlot()
{
return m_slotList[0];
}
//-------------------------------------------------------------------------------------------------
AZ::FontTexture::TextureSlotKey AZ::FontTexture::GetTextureSlotKey(uint32_t character, const AZ::AtomFont::GlyphSize& glyphSize) const
{
const AZ::AtomFont::GlyphSize clampedGlyphSize(ClampGlyphSize(glyphSize, m_cellWidth, m_cellHeight));
return AZ::FontTexture::TextureSlotKey(clampedGlyphSize, character);
}
AZ::AtomFont::GlyphSize AZ::FontTexture::ClampGlyphSize(const AZ::AtomFont::GlyphSize& glyphSize, int cellWidth, int cellHeight)
{
const AZ::AtomFont::GlyphSize maxCellDimensions(cellWidth, cellHeight);
AZ::AtomFont::GlyphSize clampedGlyphSize(glyphSize);
const bool hasZeroDimension = glyphSize.x == 0 || glyphSize.y == 0;
const bool isDefaultSize = glyphSize == AZ::AtomFont::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)
@@ -0,0 +1,242 @@
/*
* 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:
// - Hold a glyph bitmap and blit it to the main texture
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#include <AtomLyIntegration/AtomFont/GlyphBitmap.h>
#include <math.h>
//-------------------------------------------------------------------------------------------------
AZ::GlyphBitmap::GlyphBitmap()
: m_width(0)
, m_height(0)
, m_buffer(nullptr)
{
}
//-------------------------------------------------------------------------------------------------
AZ::GlyphBitmap::~GlyphBitmap()
{
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::Create(int width, int height)
{
Release();
m_buffer = AZStd::unique_ptr<uint8_t[]>(new uint8_t[width * height]);
if (!m_buffer)
{
return 0;
}
m_width = width;
m_height = height;
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::Release()
{
m_buffer = nullptr;
m_width = m_height = 0;
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::Blur(AZ::FontSmoothAmount smoothAmount)
{
int iterationCount = 0;
switch(smoothAmount)
{
case AZ::FontSmoothAmount::x2:
iterationCount = 1;
break;
case AZ::FontSmoothAmount::x4:
iterationCount = 2;
break;
}
int colorSum;
int yOffset;
int yUpOffset;
int yDownOffset;
for (int i = 0; i < iterationCount; i++)
{
for (int y = 0; y < m_height; y++)
{
yOffset = y * m_width;
if (y - 1 >= 0)
{
yUpOffset = (y - 1) * m_width;
}
else
{
yUpOffset = (y) * m_width;
}
if (y + 1 < m_height)
{
yDownOffset = (y + 1) * m_width;
}
else
{
yDownOffset = (y) * m_width;
}
for (int x = 0; x < m_width; x++)
{
colorSum = m_buffer[yUpOffset + x] + m_buffer[yDownOffset + x];
if (x - 1 >= 0)
{
colorSum += m_buffer[yOffset + x - 1];
}
else
{
colorSum += m_buffer[yOffset + x];
}
if (x + 1 < m_width)
{
colorSum += m_buffer[yOffset + x + 1];
}
else
{
colorSum += m_buffer[yOffset + x];
}
m_buffer[yOffset + x] = colorSum >> 2;
}
}
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::Clear()
{
memset(m_buffer.get(), 0, m_width * m_height);
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::BlitTo8(unsigned char* destBuffer, int srcX, int srcY, int srcWidth, int srcHeight, int destX, int destY, int destWidth)
{
int ySrcOffset;
int yDestOffset;
for (int y = 0; y < srcHeight; y++)
{
ySrcOffset = (srcY + y) * m_width;
yDestOffset = (destY + y) * destWidth;
for (int x = 0; x < srcWidth; x++)
{
destBuffer[yDestOffset + destX + x] = m_buffer[ySrcOffset + srcX + x];
}
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphBitmap::BlitScaledTo8(unsigned char* destBuffer, [[maybe_unused]] int srcReadXOffset, int srcReadYOffset, int srcWidth, int srcHeight, int destX, [[maybe_unused]] int destY, int destWidth, int destHeight, int destBufferWidth)
{
int newWidth = (int)destWidth;
int newHeight = (int)destHeight;
float destToSrcXScale = srcWidth / (float)newWidth;
float destToSrcYScale = srcHeight / (float)newHeight;
float srcReadX;
float srcReadY;
float srcReadXFraction;
float srcReadYFraction;
float oneMinusX;
float oneMinusY;
float fR0;
float fR1;
int srcReadXCeil;
int srcReadYCeil;
int srcReadXFloor;
int srcReadYFloor;
int destOffsetY;
uint8_t color0;
uint8_t color1;
uint8_t color2;
uint8_t color3;
for (int y = 0; y < newHeight; ++y)
{
srcReadY = y * destToSrcYScale;
srcReadYFloor = (int)floor_tpl(srcReadY);
srcReadYCeil = srcReadYFloor + 1;
srcReadYFraction = srcReadY - srcReadYFloor;
oneMinusY = 1.0f - srcReadYFraction;
destOffsetY = y * destBufferWidth;
srcReadYFloor += srcReadYOffset;
srcReadYCeil += srcReadYOffset;
if (srcReadYCeil >= m_height)
{
srcReadYCeil = srcReadYFloor;
}
for (int x = 0; x < newWidth; ++x)
{
srcReadX = x * destToSrcXScale;
srcReadXFloor = (int)floor_tpl(srcReadX);
srcReadXCeil = srcReadXFloor + 1;
srcReadXFraction = srcReadX - srcReadXFloor;
oneMinusX = 1.0f - srcReadXFraction;
// possible bug from Cry, using the y offset here
srcReadXFloor += srcReadYOffset;
srcReadXCeil += srcReadYOffset;
if (srcReadXCeil >= m_width)
{
srcReadXCeil = srcReadXFloor;
}
color0 = m_buffer[srcReadYFloor * m_width + srcReadXFloor];
color1 = m_buffer[srcReadYFloor * m_width + srcReadXCeil];
color2 = m_buffer[srcReadYCeil * m_width + srcReadXFloor];
color3 = m_buffer[srcReadYCeil * m_width + srcReadXCeil];
fR0 = (oneMinusX * color0 + srcReadXFraction * color1);
fR1 = (oneMinusX * color2 + srcReadXFraction * color3);
destBuffer[destOffsetY + x + destX] = (unsigned char)((oneMinusY * fR0) + (srcReadYFraction * fR1));
}
}
return 1;
}
//-------------------------------------------------------------------------------------------------
@@ -0,0 +1,433 @@
/*
* 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:
// - Manage and cache glyphs, retrieving them from the renderer as needed
#include <AtomLyIntegration/AtomFont/AtomFont_precompiled.h>
#if !defined(USE_NULLFONT_ALWAYS)
#include <AtomLyIntegration/AtomFont/GlyphCache.h>
#include <AtomLyIntegration/AtomFont/FontTexture.h>
//-------------------------------------------------------------------------------------------------
AZ::GlyphCache::GlyphCache()
: m_usage(1)
, m_glyphBitmapWidth(0)
, m_glyphBitmapHeight(0)
, m_scaleBitmap(0)
{
m_cacheTable.clear();
m_slotList.clear();
}
//-------------------------------------------------------------------------------------------------
AZ::GlyphCache::~GlyphCache()
{
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::Create(int iCacheSize, int glyphBitmapWidth, int glyphBitmapHeight, AZ::FontSmoothMethod smoothMethod, AZ::FontSmoothAmount smoothAmount, float sizeRatio)
{
m_smoothMethod = smoothMethod;
m_smoothAmount = smoothAmount;
m_glyphBitmapWidth = glyphBitmapWidth;
m_glyphBitmapHeight = glyphBitmapHeight;
if (!CreateSlotList(iCacheSize))
{
ReleaseSlotList();
return 0;
}
int iScaledGlyphWidth = 0;
int iScaledGlyphHeight = 0;
switch (m_smoothMethod)
{
case AZ::FontSmoothMethod::SuperSample:
{
switch (m_smoothAmount)
{
case AZ::FontSmoothAmount::x2:
iScaledGlyphWidth = m_glyphBitmapWidth << 1;
iScaledGlyphHeight = m_glyphBitmapHeight << 1;
break;
case AZ::FontSmoothAmount::x4:
iScaledGlyphWidth = m_glyphBitmapWidth << 2;
iScaledGlyphHeight = m_glyphBitmapHeight << 2;
break;
}
}
break;
}
if (iScaledGlyphWidth)
{
m_scaleBitmap = new GlyphBitmap;
if (!m_scaleBitmap)
{
Release();
return 0;
}
if (!m_scaleBitmap->Create(iScaledGlyphWidth, iScaledGlyphHeight))
{
Release();
return 0;
}
m_fontRenderer.SetGlyphBitmapSize(iScaledGlyphWidth, iScaledGlyphHeight, sizeRatio);
}
else
{
m_fontRenderer.SetGlyphBitmapSize(m_glyphBitmapWidth, m_glyphBitmapHeight, sizeRatio);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::Release()
{
ReleaseSlotList();
m_cacheTable.clear();
if (m_scaleBitmap)
{
m_scaleBitmap->Release();
delete m_scaleBitmap;
m_scaleBitmap = 0;
}
m_glyphBitmapWidth = 0;
m_glyphBitmapHeight = 0;
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::LoadFontFromFile(const string& fileName)
{
return m_fontRenderer.LoadFromFile(fileName);
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::LoadFontFromMemory(unsigned char* fileBuffer, int dataSize)
{
return m_fontRenderer.LoadFromMemory(fileBuffer, dataSize);
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::ReleaseFont()
{
m_fontRenderer.Release();
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::GetGlyphBitmapSize(int* width, int* height)
{
if (width)
{
*width = m_glyphBitmapWidth;
}
if (height)
{
*height = m_glyphBitmapHeight;
}
return 1;
}
//-------------------------------------------------------------------------------------------------
void AZ::GlyphCache::SetGlyphBitmapSize(int width, int height, float sizeRatio)
{
m_fontRenderer.SetGlyphBitmapSize(width, height, sizeRatio);
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::PreCacheGlyph(uint32_t character, const AtomFont::GlyphSize& glyphSize, const FFont::FontHintParams& fontHintParams)
{
CacheTable::iterator pItor = m_cacheTable.find(GetCacheSlotKey(character, glyphSize));
if (pItor != m_cacheTable.end())
{
pItor->second->m_usage = m_usage;
return 1;
}
CacheSlot* slot = GetLRUSlot();
if (!slot)
{
return 0;
}
if (slot->m_usage > 0)
{
UnCacheGlyph(slot->m_currentCharacter, slot->m_glyphSize);
}
if (m_scaleBitmap)
{
int iOffsetMult = 1;
switch (m_smoothAmount)
{
case AZ::FontSmoothAmount::x2:
iOffsetMult = 2;
break;
case AZ::FontSmoothAmount::x4:
iOffsetMult = 4;
break;
}
m_scaleBitmap->Clear();
if (!m_fontRenderer.GetGlyph(m_scaleBitmap, &slot->m_horizontalAdvance, &slot->m_characterWidth, &slot->m_characterHeight, slot->m_characterOffsetX, slot->m_characterOffsetY, 0, 0, character, fontHintParams))
{
return 0;
}
slot->m_characterWidth >>= iOffsetMult >> 1;
slot->m_characterHeight >>= iOffsetMult >> 1;
m_scaleBitmap->BlitScaledTo8(slot->m_glyphBitmap.GetBuffer(), 0, 0, m_scaleBitmap->GetWidth(), m_scaleBitmap->GetHeight(), 0, 0, slot->m_glyphBitmap.GetWidth(), slot->m_glyphBitmap.GetHeight(), slot->m_glyphBitmap.GetWidth());
}
else
{
if (!m_fontRenderer.GetGlyph(&slot->m_glyphBitmap, &slot->m_horizontalAdvance, &slot->m_characterWidth, &slot->m_characterHeight, slot->m_characterOffsetX, slot->m_characterOffsetY, 0, 0, character, fontHintParams))
{
return 0;
}
}
if (m_smoothMethod == AZ::FontSmoothMethod::Blur)
{
slot->m_glyphBitmap.Blur(m_smoothAmount);
}
slot->m_usage = m_usage;
slot->m_currentCharacter = character;
slot->m_glyphSize = glyphSize;
m_cacheTable.insert(AZStd::pair<CacheTableKey, CacheSlot*>(GetCacheSlotKey(character, glyphSize), slot));
return 1;
}
int AZ::GlyphCache::UnCacheGlyph(uint32_t character, const AtomFont::GlyphSize& glyphSize)
{
CacheTable::iterator pItor = m_cacheTable.find(GetCacheSlotKey(character, glyphSize));
if (pItor != m_cacheTable.end())
{
CacheSlot* slot = pItor->second;
slot->Reset();
m_cacheTable.erase(pItor);
return 1;
}
return 0;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::GlyphCached(uint32_t character, const AtomFont::GlyphSize& glyphSize)
{
return (m_cacheTable.find(GetCacheSlotKey(character, glyphSize)) != m_cacheTable.end());
}
//-------------------------------------------------------------------------------------------------
AZ::CacheSlot* AZ::GlyphCache::GetLRUSlot()
{
unsigned int dwMinUsage = 0xffffffff;
CacheSlot* pLRUSlot = 0;
CacheSlot* slot;
CacheSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
slot = *pItor;
if (slot->m_usage == 0)
{
return slot;
}
else
{
if (slot->m_usage < dwMinUsage)
{
pLRUSlot = slot;
dwMinUsage = slot->m_usage;
}
}
pItor++;
}
return pLRUSlot;
}
//-------------------------------------------------------------------------------------------------
AZ::CacheSlot* AZ::GlyphCache::GetMRUSlot()
{
unsigned int dwMaxUsage = 0;
CacheSlot* pMRUSlot = 0;
CacheSlot* slot;
CacheSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
slot = *pItor;
if (slot->m_usage != 0)
{
if (slot->m_usage > dwMaxUsage)
{
pMRUSlot = slot;
dwMaxUsage = slot->m_usage;
}
}
pItor++;
}
return pMRUSlot;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::GetGlyph(AZ::GlyphBitmap** glyph, int* horizontalAdvance, int* width, int* height, int32_t& m_characterOffsetX, int32_t& m_characterOffsetY, uint32_t character, const AZ::AtomFont::GlyphSize& glyphSize, const AZ::FFont::FontHintParams& fontHintParams)
{
CacheTable::iterator pItor = m_cacheTable.find(GetCacheSlotKey(character, glyphSize));
if (pItor == m_cacheTable.end())
{
if (!PreCacheGlyph(character, glyphSize, fontHintParams))
{
return 0;
}
}
pItor = m_cacheTable.find(GetCacheSlotKey(character, glyphSize));
pItor->second->m_usage = m_usage++;
(*glyph) = &pItor->second->m_glyphBitmap;
if (horizontalAdvance)
{
*horizontalAdvance = pItor->second->m_horizontalAdvance;
}
if (width)
{
*width = pItor->second->m_characterWidth;
}
if (height)
{
*height = pItor->second->m_characterHeight;
}
m_characterOffsetX = pItor->second->m_characterOffsetX;
m_characterOffsetY = pItor->second->m_characterOffsetY;
return 1;
}
//-------------------------------------------------------------------------------------------------
Vec2 AZ::GlyphCache::GetKerning(uint32_t leftGlyph, uint32_t rightGlyph)
{
return m_fontRenderer.GetKerning(leftGlyph, rightGlyph);
}
//-------------------------------------------------------------------------------------------------
float AZ::GlyphCache::GetAscenderToHeightRatio()
{
return m_fontRenderer.GetAscenderToHeightRatio();
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::CreateSlotList(int listSize)
{
for (int i = 0; i < listSize; i++)
{
CacheSlot* cacheSlot = new CacheSlot;
if (!cacheSlot)
{
return 0;
}
if (!cacheSlot->m_glyphBitmap.Create(m_glyphBitmapWidth, m_glyphBitmapHeight))
{
delete cacheSlot;
return 0;
}
cacheSlot->Reset();
cacheSlot->m_slotIndex = i;
m_slotList.push_back(cacheSlot);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
int AZ::GlyphCache::ReleaseSlotList()
{
CacheSlotListItor pItor = m_slotList.begin();
while (pItor != m_slotList.end())
{
(*pItor)->m_glyphBitmap.Release();
delete (*pItor);
pItor = m_slotList.erase(pItor);
}
return 1;
}
//-------------------------------------------------------------------------------------------------
AZ::GlyphCache::CacheTableKey AZ::GlyphCache::GetCacheSlotKey(uint32_t character, const AZ::AtomFont::GlyphSize& glyphSize) const
{
const AZ::AtomFont::GlyphSize clampedGlyphSize = AZ::FontTexture::ClampGlyphSize(glyphSize, m_glyphBitmapWidth, m_glyphBitmapHeight);
return CacheTableKey(clampedGlyphSize, character);
}
#endif // #if !defined(USE_NULLFONT_ALWAYS)
@@ -0,0 +1,51 @@
/*
* 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.
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Module/Module.h>
#include "AtomFontSystemComponent.h"
namespace AZ
{
namespace Render
{
class AtomFontModule
: public AZ::Module
{
public:
AZ_RTTI(AtomFontModule, "{E5EDF3B2-F85D-441B-8D0B-21D44D177799}", AZ::Module);
AZ_CLASS_ALLOCATOR(AtomFontModule, AZ::SystemAllocator, 0);
AtomFontModule()
: AZ::Module()
{
m_descriptors.insert(m_descriptors.end(), {
AtomFontSystemComponent::CreateDescriptor(),
});
}
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<AtomFontSystemComponent>(),
};
}
};
} // namespace Render
} // namespace AZ
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_AtomFont, AZ::Render::AtomFontModule)
@@ -0,0 +1,20 @@
/*
* 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.
*
*/
#include <AzTest/AzTest.h>
AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);
TEST(AtomFontSanityTest, Sanity)
{
EXPECT_EQ(1, 1);
}