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.
912 lines
32 KiB
C++
912 lines
32 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.
|
|
|
|
// 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 <AzCore/std/parallel/lock.h>
|
|
#include <AzCore/Interface/Interface.h>
|
|
#include <AzFramework/Archive/IArchive.h>
|
|
|
|
#include <Atom/RPI.Public/RPIUtils.h>
|
|
#include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.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::Interface<AzFramework::FontQueryInterface>::Register(this);
|
|
|
|
m_sceneEventHandler = AzFramework::ISceneSystem::SceneEvent::Handler(
|
|
[this](AzFramework::ISceneSystem::EventType eventType, const AZStd::shared_ptr<AzFramework::Scene>& scene)
|
|
{
|
|
if (eventType == AzFramework::ISceneSystem::EventType::ScenePendingRemoval)
|
|
{
|
|
SceneAboutToBeRemoved(*scene);
|
|
}
|
|
});
|
|
auto sceneSystem = AzFramework::SceneSystemInterface::Get();
|
|
AZ_Assert(sceneSystem, "Font created before the scene system is available.");
|
|
sceneSystem->ConnectToEvents(m_sceneEventHandler);
|
|
}
|
|
|
|
AZ::AtomFont::~AtomFont()
|
|
{
|
|
AZ::Interface<AzFramework::FontQueryInterface>::Unregister(this);
|
|
m_defaultFontDrawInterface = nullptr;
|
|
|
|
// 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();
|
|
AzFramework::FontId fontId = GetFontId(name.c_str());
|
|
|
|
FontMapItor it = m_fonts.find(fontId);
|
|
if (it != m_fonts.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
FFont* font = new FFont(this, name.c_str());
|
|
m_fonts.insert(FontMapItor::value_type(fontId, font));
|
|
if(!m_defaultFontDrawInterface)
|
|
{
|
|
m_defaultFontDrawInterface = static_cast<AzFramework::FontDrawInterface*>(font);
|
|
}
|
|
return font;
|
|
}
|
|
|
|
IFFont* AZ::AtomFont::GetFont(const char* fontName) const
|
|
{
|
|
AzFramework::FontId fontId = GetFontId(string(fontName).MakeLower().c_str());
|
|
FontMapConstItor it = m_fonts.find(fontId);
|
|
return it != m_fonts.end() ? it->second : 0;
|
|
}
|
|
|
|
AzFramework::FontDrawInterface* AZ::AtomFont::GetFontDrawInterface(AzFramework::FontId fontId) const
|
|
{
|
|
FontMapConstItor it = m_fonts.find(fontId);
|
|
return (it != m_fonts.end()) ? it->second : nullptr;
|
|
}
|
|
|
|
AzFramework::FontDrawInterface* AZ::AtomFont::GetDefaultFontDrawInterface() const
|
|
{
|
|
return m_defaultFontDrawInterface;
|
|
}
|
|
|
|
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)
|
|
{
|
|
AzFramework::FontId fontId = GetFontId(string(fontName).MakeLower().c_str());
|
|
FontMapItor it = m_fonts.find(fontId);
|
|
|
|
#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;
|
|
}
|
|
|
|
void AZ::AtomFont::SceneAboutToBeRemoved(AzFramework::Scene& scene)
|
|
{
|
|
AZ::RPI::ScenePtr* rpiScene = scene.FindSubsystem<AZ::RPI::ScenePtr>();
|
|
if (rpiScene)
|
|
{
|
|
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
|
if (auto it = m_sceneToDynamicDrawMap.find(rpiScene->get()); it != m_sceneToDynamicDrawMap.end())
|
|
{
|
|
m_sceneToDynamicDrawMap.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> AZ::AtomFont::GetOrCreateDynamicDrawForScene(AZ::RPI::Scene* scene)
|
|
{
|
|
static const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
|
|
|
|
{
|
|
// shared lock while reading
|
|
AZStd::shared_lock<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
|
|
|
if (auto it = m_sceneToDynamicDrawMap.find(scene); it != m_sceneToDynamicDrawMap.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
// Create and initialize DynamicDrawContext for font draw
|
|
AZ::RHI::Ptr<AZ::RPI::DynamicDrawContext> dynamicDraw = RPI::DynamicDrawInterface::Get()->CreateDynamicDrawContext(scene);
|
|
|
|
Data::Instance<RPI::Shader> shader = AZ::RPI::LoadShader(shaderFilepath);
|
|
AZ::RPI::ShaderOptionList shaderOptions;
|
|
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false")));
|
|
shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true")));
|
|
dynamicDraw->InitShaderWithVariant(shader, &shaderOptions);
|
|
dynamicDraw->InitVertexFormat({{"POSITION", RHI::Format::R32G32B32_FLOAT}, {"COLOR", RHI::Format::R8G8B8A8_UNORM}, {"TEXCOORD0", RHI::Format::R32G32_FLOAT}});
|
|
dynamicDraw->EndInit();
|
|
|
|
// exclusive lock while writing
|
|
AZStd::lock_guard<AZStd::shared_mutex> lock(m_sceneToDynamicDrawMutex);
|
|
m_sceneToDynamicDrawMap.insert(AZStd::make_pair(scene, dynamicDraw));
|
|
|
|
return dynamicDraw;
|
|
}
|
|
|
|
|
|
#endif
|
|
|