AtomFont.cpp 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. // Description : AtomFont class.
  9. #if !defined(USE_NULLFONT_ALWAYS)
  10. #include <AtomLyIntegration/AtomFont/AtomFont.h>
  11. #include <AtomLyIntegration/AtomFont/FFont.h>
  12. #include <AtomLyIntegration/AtomFont/FontTexture.h>
  13. #include <AtomLyIntegration/AtomFont/FontRenderer.h>
  14. #include <CryCommon/CryPath.h>
  15. #include <CryCommon/ILocalizationManager.h>
  16. #include <AzCore/std/string/conversions.h>
  17. #include <AzCore/std/string/string_view.h>
  18. #include <AzCore/std/parallel/lock.h>
  19. #include <AzCore/Interface/Interface.h>
  20. #include <AzFramework/Archive/IArchive.h>
  21. #include <Atom/RPI.Public/RPIUtils.h>
  22. #include <Atom/RPI.Public/DynamicDraw/DynamicDrawInterface.h>
  23. #include <Atom/RPI.Reflect/Asset/AssetUtils.h>
  24. // Static member definitions
  25. const AZ::AtomFont::GlyphSize AZ::AtomFont::defaultGlyphSize = AZ::AtomFont::GlyphSize(ICryFont::defaultGlyphSizeX, ICryFont::defaultGlyphSizeY);
  26. #if !defined(_RELEASE)
  27. static void DumfontTexture(IConsoleCmdArgs* cmdArgs)
  28. {
  29. if (cmdArgs->GetArgCount() != 2)
  30. {
  31. return;
  32. }
  33. const char* fontName = cmdArgs->GetArg(1);
  34. if (fontName && *fontName && *fontName != '0')
  35. {
  36. AZStd::string fontFilePath("@engroot@/");
  37. fontFilePath += fontName;
  38. fontFilePath += ".bmp";
  39. AZ::FFont* font = (AZ::FFont*) gEnv->pCryFont->GetFont(fontName);
  40. if (font)
  41. {
  42. font->GetFontTexture()->WriteToFile(fontFilePath.c_str());
  43. gEnv->pLog->LogWithType(IMiniLog::eInputResponse, "Dumped \"%s\" texture to \"%s\"!", fontName, fontFilePath.c_str());
  44. }
  45. }
  46. }
  47. static void DumfontNames([[maybe_unused]] IConsoleCmdArgs* cmdArgs)
  48. {
  49. AZStd::string names = gEnv->pCryFont->GetLoadedFontNames();
  50. gEnv->pLog->LogWithType(IMiniLog::eInputResponse, "Currently loaded fonts: %s", names.c_str());
  51. }
  52. static void ReloadFonts([[maybe_unused]] IConsoleCmdArgs* cmdArgs)
  53. {
  54. gEnv->pCryFont->ReloadAllFonts();
  55. }
  56. #endif
  57. namespace
  58. {
  59. //! Stores paths to styled font assets for a given set of languages
  60. //! This struct stores the XML data contained within the <font> tag of
  61. //! an enclosing <fontfamily> definition:
  62. //!
  63. //! <fontfamily name="FontFamilyName">
  64. //! <font lang="Language1, Language2">
  65. //! <file path="regular.font" />
  66. //! <file path="bold.font" tags="b" />
  67. //! <file path="italic.font" tags="i" />
  68. //! <file path="bolditalic.font" tags="b,i" />
  69. //! </font>
  70. //! </fontfamily>
  71. struct FontTagXml
  72. {
  73. //! \return True if all font asset paths are non-empty, false otherwise
  74. bool IsValid() const
  75. {
  76. // Note that "lang" can be empty
  77. return !m_fontFilename.empty()
  78. && !m_boldFontFilename.empty()
  79. && !m_italicFontFilename.empty()
  80. && !m_boldItalicFontFilename.empty();
  81. }
  82. AZStd::string m_lang; //!< Stores a comma-separated list of languages this collection of fonts applies to.
  83. //!< If this is an empty string, it implies that these set of fonts will be applied
  84. //!< by default (when a language is being used but no fonts in the font family are
  85. //!< mapped to that language).
  86. AZStd::string m_fontFilename; //!< Font used when no styling is applied.
  87. AZStd::string m_boldFontFilename; //!< Bold-styled font
  88. AZStd::string m_italicFontFilename; //!< Italic-styled font
  89. AZStd::string m_boldItalicFontFilename; //!< Bold-italic-styled font
  90. };
  91. //! Stores parsed font family XML data.
  92. //! This struct contains the name of the font family and a list of font
  93. //! file XML data for all the language-specific mappings of this
  94. //! font family.
  95. //!
  96. //! Example XML:
  97. //!
  98. //! <fontfamily name="FontFamilyName">
  99. //! <font>
  100. //! <file path="regular.font" />
  101. //! <file path="bold.font" tags="b" />
  102. //! <file path="italic.font" tags="i" />
  103. //! <file path="bolditalic.font" tags="b,i" />
  104. //! </font>
  105. //! <font lang="korean">
  106. //! <file path="../korean/korean-regular.font" />
  107. //! <file path="../korean/korean-italic.font" tags="b" />
  108. //! <file path="../korean/korean-bold.font" tags="i" />
  109. //! <file path="../korean/korean-bolditalic.font" tags="b,i" />
  110. //! </font>
  111. //! <font lang="chinesesimplified">
  112. //! <file path="../chinesesimplified/chinesesimplified-regular.font" />
  113. //! <file path="../chinesesimplified/chinesesimplified-bold.font" tags="b" />
  114. //! <file path="../chinesesimplified/chinesesimplified-italic.font" tags="i" />
  115. //! <file path="../chinesesimplified/chinesesimplified-bolditalic.font" tags="b,i" />
  116. //! </font>
  117. //! </fontfamily>
  118. struct FontFamilyTagXml
  119. {
  120. //! Returns true if all font file fields were parsed, false otherwise.
  121. bool IsValid() const
  122. {
  123. for (const FontTagXml& fontTagXml : m_fontTagsXml)
  124. {
  125. if (!fontTagXml.IsValid())
  126. {
  127. return false;
  128. }
  129. }
  130. // Every font family must have a name
  131. return !m_fontFamilyName.empty();
  132. }
  133. AZStd::string m_fontFamilyName; //!< Value of the "name" font-family tag attribute
  134. AZStd::list<FontTagXml> m_fontTagsXml; //!< List of child <font> tag data.
  135. };
  136. //! Returns true if the XML tree was traversed successfully, false otherwise.
  137. //!
  138. //! Note that, if this function returns true, it simply means that there were
  139. //! no unexpected structure issues with the given XML tree, it doesn't
  140. //! necessarily mean that all the required fields were parsed.
  141. bool ParseFontFamilyXml(const XmlNodeRef& node, FontFamilyTagXml& xmlData)
  142. {
  143. if (!node)
  144. {
  145. return false;
  146. }
  147. // <fontfamily>
  148. if (AZStd::string(node->getTag()) == "fontfamily")
  149. {
  150. const int numAttributes = node->getNumAttributes();
  151. if (numAttributes <= 0)
  152. {
  153. // Expecting at least one attribute
  154. return false;
  155. }
  156. AZStd::string name;
  157. for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
  158. {
  159. const char* key = "";
  160. const char* value = "";
  161. if (node->getAttributeByIndex(i, &key, &value))
  162. {
  163. if (AZStd::string(key) == "name")
  164. {
  165. name = value;
  166. }
  167. else
  168. {
  169. // Unexpected font tag attribute
  170. return false;
  171. }
  172. }
  173. }
  174. AZ::StringFunc::TrimWhiteSpace(name, true, true);
  175. if (!name.empty())
  176. {
  177. xmlData.m_fontFamilyName = name;
  178. }
  179. else
  180. {
  181. // Font family must have a name
  182. return false;
  183. }
  184. }
  185. // <font>
  186. if (AZStd::string(node->getTag()) == "font")
  187. {
  188. xmlData.m_fontTagsXml.push_back(FontTagXml());
  189. AZStd::string lang;
  190. for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
  191. {
  192. const char* key = "";
  193. const char* value = "";
  194. if (node->getAttributeByIndex(i, &key, &value))
  195. {
  196. if (AZStd::string(key) == "lang")
  197. {
  198. lang = value;
  199. }
  200. else
  201. {
  202. // Unexpected font tag attribute
  203. return false;
  204. }
  205. }
  206. }
  207. AZ::StringFunc::TrimWhiteSpace(lang, true, true);
  208. if (!lang.empty())
  209. {
  210. xmlData.m_fontTagsXml.back().m_lang = lang;
  211. }
  212. }
  213. // <file>
  214. else if (AZStd::string(node->getTag()) == "file")
  215. {
  216. const int numAttributes = node->getNumAttributes();
  217. if (numAttributes <= 0)
  218. {
  219. // Expecting at least one attribute
  220. return false;
  221. }
  222. AZStd::string path;
  223. AZStd::string tags;
  224. for (int i = 0, count = node->getNumAttributes(); i < count; ++i)
  225. {
  226. const char* key = "";
  227. const char* value = "";
  228. if (node->getAttributeByIndex(i, &key, &value))
  229. {
  230. if (AZStd::string(key) == "path")
  231. {
  232. path = value;
  233. }
  234. else if (AZStd::string(key) == "tags")
  235. {
  236. tags = value;
  237. }
  238. else
  239. {
  240. // Unexpected font tag attribute
  241. return false;
  242. }
  243. }
  244. }
  245. AZ::StringFunc::TrimWhiteSpace(tags, true, true);
  246. if (tags.empty())
  247. {
  248. xmlData.m_fontTagsXml.back().m_fontFilename = path;
  249. }
  250. else if (tags == "b")
  251. {
  252. xmlData.m_fontTagsXml.back().m_boldFontFilename = path;
  253. }
  254. else if (tags == "i")
  255. {
  256. xmlData.m_fontTagsXml.back().m_italicFontFilename = path;
  257. }
  258. else
  259. {
  260. // We'll just assume any other tag indicates bold italic
  261. xmlData.m_fontTagsXml.back().m_boldItalicFontFilename = path;
  262. }
  263. }
  264. for (int i = 0, count = node->getChildCount(); i < count; ++i)
  265. {
  266. XmlNodeRef child = node->getChild(i);
  267. if (!ParseFontFamilyXml(child, xmlData))
  268. {
  269. return false;
  270. }
  271. }
  272. return true;
  273. }
  274. //! Only attempt XML file load if file exists.
  275. //! There are use-cases where the XML path is not fully known (such as
  276. //! when referencing font family names from font family XML files), and
  277. //! attempting to load the XML files directly via ISystem() methods can
  278. //! produce a lot of warning noise.
  279. XmlNodeRef SafeLoadXmlFromFile(const AZStd::string& xmlPath)
  280. {
  281. if (gEnv->pCryPak->IsFileExist(xmlPath.c_str()))
  282. {
  283. return GetISystem()->LoadXmlFromFile(xmlPath.c_str());
  284. }
  285. return XmlNodeRef();
  286. }
  287. }
  288. AZ::AtomFont::AtomFont([[maybe_unused]] ISystem* system)
  289. {
  290. CryLogAlways("Using FreeType %d.%d.%d", FREETYPE_MAJOR, FREETYPE_MINOR, FREETYPE_PATCH);
  291. // Persist fonts for application lifetime to prevent unnecessary work
  292. REGISTER_CVAR(r_persistFontFamilies, r_persistFontFamilies, VF_NULL, "Persist loaded font families for lifetime of application.");
  293. #if !defined(_RELEASE)
  294. REGISTER_COMMAND("r_DumfontTexture", DumfontTexture, 0,
  295. "Dumps the specified font's texture to a bitmap file\n"
  296. "Use r_DumfontTexture to get the loaded font names\n"
  297. "Usage: r_DumfontTexture <fontname>");
  298. REGISTER_COMMAND("r_DumfontNames", DumfontNames, 0,
  299. "Logs a list of fonts currently loaded");
  300. REGISTER_COMMAND("r_ReloadFonts", ReloadFonts, VF_NULL,
  301. "Reload all fonts");
  302. #endif
  303. AZ::Interface<AzFramework::FontQueryInterface>::Register(this);
  304. // Queue a load for the font per viewport dynamic draw context shader, and wait for it to load
  305. static const char* shaderFilepath = "Shaders/SimpleTextured.azshader";
  306. Data::Asset<RPI::ShaderAsset> shaderAsset = RPI::AssetUtils::GetAssetByProductPath<RPI::ShaderAsset>(shaderFilepath, RPI::AssetUtils::TraceLevel::Assert);
  307. shaderAsset.QueueLoad();
  308. Data::AssetBus::Handler::BusConnect(shaderAsset.GetId());
  309. }
  310. AZ::AtomFont::~AtomFont()
  311. {
  312. Data::AssetBus::Handler::BusDisconnect();
  313. AZ::Interface<AzFramework::FontQueryInterface>::Unregister(this);
  314. m_defaultFontDrawInterface = nullptr;
  315. // Persist fonts for application lifetime to prevent unnecessary work
  316. m_persistedFontFamilies.clear();
  317. for (FontMapItor it = m_fonts.begin(), itEnd = m_fonts.end(); it != itEnd; )
  318. {
  319. FFont* font = it->second;
  320. ++it; // iterate as Release() below will remove font from the map
  321. SAFE_RELEASE(font);
  322. }
  323. }
  324. void AZ::AtomFont::Release()
  325. {
  326. delete this;
  327. }
  328. IFFont* AZ::AtomFont::NewFont(const char* fontName)
  329. {
  330. AZStd::string name = fontName;
  331. AZStd::to_lower(name.begin(), name.end());
  332. AzFramework::FontId fontId = GetFontId(name.c_str());
  333. FontMapItor it = m_fonts.find(fontId);
  334. if (it != m_fonts.end())
  335. {
  336. return it->second;
  337. }
  338. FFont* font = new FFont(this, name.c_str());
  339. m_fonts.insert(FontMapItor::value_type(fontId, font));
  340. if(!m_defaultFontDrawInterface)
  341. {
  342. m_defaultFontDrawInterface = static_cast<AzFramework::FontDrawInterface*>(font);
  343. }
  344. return font;
  345. }
  346. IFFont* AZ::AtomFont::GetFont(const char* fontName) const
  347. {
  348. AZStd::string name = fontName;
  349. AZStd::to_lower(name.begin(), name.end());
  350. AzFramework::FontId fontId = GetFontId(name.c_str());
  351. FontMapConstItor it = m_fonts.find(fontId);
  352. return it != m_fonts.end() ? it->second : 0;
  353. }
  354. AzFramework::FontDrawInterface* AZ::AtomFont::GetFontDrawInterface(AzFramework::FontId fontId) const
  355. {
  356. FontMapConstItor it = m_fonts.find(fontId);
  357. return (it != m_fonts.end()) ? it->second : nullptr;
  358. }
  359. AzFramework::FontDrawInterface* AZ::AtomFont::GetDefaultFontDrawInterface() const
  360. {
  361. return m_defaultFontDrawInterface;
  362. }
  363. FontFamilyPtr AZ::AtomFont::LoadFontFamily(const char* fontFamilyName)
  364. {
  365. FontFamilyPtr fontFamily(nullptr);
  366. AZStd::string fontFamilyPath;
  367. AZStd::string fontFamilyFullPath;
  368. XmlNodeRef root = LoadFontFamilyXml(fontFamilyName, fontFamilyPath, fontFamilyFullPath);
  369. if (root)
  370. {
  371. FontFamilyTagXml xmlData;
  372. const bool parseSuccess = ParseFontFamilyXml(root, xmlData);
  373. if (parseSuccess && xmlData.IsValid())
  374. {
  375. const char* currentLanguage = gEnv->pSystem->GetLocalizationManager()->GetLanguage();
  376. FontTagXml* defaultFont = nullptr;
  377. FontTagXml* langSpecificFont = nullptr;
  378. // Note that we don't break out of this for-loop early because we
  379. // want to find both the default font family and the
  380. // language-specific font family. We prefer the lang-specific
  381. // family but will fall back on the default if it doesn't exist.
  382. for (FontTagXml& fontTagXml : xmlData.m_fontTagsXml)
  383. {
  384. if (fontTagXml.m_lang.empty())
  385. {
  386. defaultFont = &fontTagXml;
  387. }
  388. else
  389. {
  390. // "lang" font-tag attribute could be comma-separated
  391. AZStd::vector<AZStd::string> tokens;
  392. AZ::StringFunc::Tokenize(fontTagXml.m_lang, tokens, ',');
  393. for(AZStd::string& langToken : tokens)
  394. {
  395. AZ::StringFunc::TrimWhiteSpace(langToken, true, true);
  396. if (langToken == currentLanguage)
  397. {
  398. langSpecificFont = &fontTagXml;
  399. break;
  400. }
  401. }
  402. }
  403. }
  404. if (langSpecificFont || defaultFont)
  405. {
  406. // Prefer lang-specific font-family over default, if it exists
  407. FontTagXml* fontTagXml = langSpecificFont ? langSpecificFont : defaultFont;
  408. // Pre-pend font family's path to make font family XML paths
  409. // relative to font family file
  410. fontTagXml->m_fontFilename = fontFamilyPath + fontTagXml->m_fontFilename;
  411. fontTagXml->m_boldFontFilename = fontFamilyPath + fontTagXml->m_boldFontFilename;
  412. fontTagXml->m_italicFontFilename = fontFamilyPath + fontTagXml->m_italicFontFilename;
  413. fontTagXml->m_boldItalicFontFilename = fontFamilyPath + fontTagXml->m_boldItalicFontFilename;
  414. IFFont* normal = LoadFont(fontTagXml->m_fontFilename.c_str());
  415. IFFont* bold = LoadFont(fontTagXml->m_boldFontFilename.c_str());
  416. IFFont* italic = LoadFont(fontTagXml->m_italicFontFilename.c_str());
  417. IFFont* boldItalic = LoadFont(fontTagXml->m_boldItalicFontFilename.c_str());
  418. // Only continue if all fonts were created successfully
  419. if (normal && bold && italic && boldItalic)
  420. {
  421. fontFamily.reset(new FontFamily(),
  422. [this](FontFamily* fontFamily)
  423. {
  424. if (AZ::Interface<AzFramework::FontQueryInterface>::Get())
  425. {
  426. ReleaseFontFamily(fontFamily);
  427. }
  428. });
  429. // Map the font family name both by path and by name defined
  430. // within the Font Family XML itself. This allows font
  431. // families to also be referenced simply by name.
  432. if (!AddFontFamilyToMaps(fontFamilyFullPath.c_str(), xmlData.m_fontFamilyName.c_str(), fontFamily))
  433. {
  434. SAFE_RELEASE(normal);
  435. SAFE_RELEASE(bold);
  436. SAFE_RELEASE(italic);
  437. SAFE_RELEASE(boldItalic);
  438. return nullptr;
  439. }
  440. fontFamily->familyName = xmlData.m_fontFamilyName;
  441. fontFamily->normal = normal;
  442. fontFamily->bold = bold;
  443. fontFamily->italic = italic;
  444. fontFamily->boldItalic = boldItalic;
  445. }
  446. else
  447. {
  448. SAFE_RELEASE(normal);
  449. SAFE_RELEASE(bold);
  450. SAFE_RELEASE(italic);
  451. SAFE_RELEASE(boldItalic);
  452. }
  453. }
  454. }
  455. }
  456. if (!fontFamily)
  457. {
  458. // Unable to load font family XML, so load font normally and associate
  459. // it with a font family
  460. IFFont* font = LoadFont(fontFamilyName);
  461. if (font)
  462. {
  463. // Create a font family from a single font by assigning all the
  464. // font family stylings to the same font
  465. fontFamily.reset(new FontFamily(),
  466. [this](FontFamily* fontFamily)
  467. {
  468. if (AZ::Interface<AzFramework::FontQueryInterface>::Get())
  469. {
  470. ReleaseFontFamily(fontFamily);
  471. }
  472. });
  473. // Use filepath as familyName so font loading/unloading doesn't break with duplicate file names
  474. fontFamily->familyName = fontFamilyName;
  475. if (!AddFontFamilyToMaps(fontFamilyName, fontFamily->familyName.c_str(), fontFamily))
  476. {
  477. SAFE_RELEASE(font);
  478. return nullptr;
  479. }
  480. // Assign all stylings to the same font
  481. fontFamily->normal = font;
  482. fontFamily->bold = font;
  483. fontFamily->italic = font;
  484. fontFamily->boldItalic = font;
  485. // The other three stylings need to have their ref count
  486. // incremented (even though in this particular case its all the
  487. // same font) because when ReleaseFontFamily executes all fonts
  488. // in the family will be (corresondingly) Release'd.
  489. fontFamily->bold->AddRef();
  490. fontFamily->italic->AddRef();
  491. fontFamily->boldItalic->AddRef();
  492. }
  493. }
  494. // Persist fonts for application lifetime to prevent unnecessary work
  495. if (r_persistFontFamilies > 0)
  496. {
  497. m_persistedFontFamilies.emplace_back(FontFamilyPtr(fontFamily));
  498. }
  499. return fontFamily;
  500. }
  501. FontFamilyPtr AZ::AtomFont::GetFontFamily(const char* fontFamilyName)
  502. {
  503. FontFamilyPtr fontFamily = nullptr;
  504. // The given string could either be: a font family name (defined in font
  505. // family XML), a file path (for regular fonts mapped as font families),
  506. // or just the filename of a font itself. Fonts are mapped by font family
  507. // name or by filepath, so attempt lookup using the map first since it's
  508. // the fastest.
  509. AZStd::string loweredName = AZStd::string(fontFamilyName);
  510. AZ::StringFunc::TrimWhiteSpace(loweredName, true, true);
  511. AZStd::to_lower(loweredName.begin(), loweredName.end());
  512. auto it = m_fontFamilies.find(PathUtil::MakeGamePath(loweredName).c_str());
  513. if (it != m_fontFamilies.end())
  514. {
  515. fontFamily = FontFamilyPtr(it->second);
  516. }
  517. else
  518. {
  519. // Iterate through all fonts, returning the first match where simply
  520. // the filename of a font could be a match. This case will likely be
  521. // hit when text markup references a font that doesn't belong to a
  522. // font family.
  523. for (const auto& fontFamilyIter : m_fontFamilies)
  524. {
  525. const AZStd::string& mappedFontFamilyName = fontFamilyIter.first;
  526. AZStd::string mappedFilenameNoExtension = PathUtil::GetFileName(mappedFontFamilyName.c_str());
  527. AZStd::string searchStringFilenameNoExtension = PathUtil::GetFileName(loweredName);
  528. if (mappedFilenameNoExtension == searchStringFilenameNoExtension)
  529. {
  530. fontFamily = FontFamilyPtr(fontFamilyIter.second);
  531. break;
  532. }
  533. }
  534. }
  535. return fontFamily;
  536. }
  537. void AZ::AtomFont::AddCharsToFontTextures(FontFamilyPtr fontFamily, const char* chars, int glyphSizeX, int glyphSizeY)
  538. {
  539. fontFamily->normal->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
  540. fontFamily->bold->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
  541. fontFamily->italic->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
  542. fontFamily->boldItalic->AddCharsToFontTexture(chars, glyphSizeX, glyphSizeY);
  543. }
  544. AZStd::string AZ::AtomFont::GetLoadedFontNames() const
  545. {
  546. AZStd::string ret;
  547. for (FontMapConstItor it = m_fonts.begin(), itEnd = m_fonts.end(); it != itEnd; ++it)
  548. {
  549. FFont* font = it->second;
  550. if (font)
  551. {
  552. if (!ret.empty())
  553. {
  554. ret += ",";
  555. }
  556. ret += font->GetName();
  557. }
  558. }
  559. return ret;
  560. }
  561. void AZ::AtomFont::OnLanguageChanged()
  562. {
  563. ReloadAllFonts();
  564. LanguageChangeNotificationBus::Broadcast(&LanguageChangeNotificationBus::Events::LanguageChanged);
  565. }
  566. void AZ::AtomFont::ReloadAllFonts()
  567. {
  568. // Persist fonts for application lifetime to prevent unnecessary work
  569. m_persistedFontFamilies.clear();
  570. AZStd::list<AZStd::string> fontFamilyFilenames;
  571. AZStd::list<FontFamily*> fontFamilyWeakPtrs;
  572. // Iterate through all currently loaded font families
  573. for (auto it : m_fontFamilyReverseLookup)
  574. {
  575. fontFamilyWeakPtrs.push_back(it.first);
  576. fontFamilyFilenames.push_back(it.second->first);
  577. }
  578. // Release font-family resources and unmap them
  579. for (auto fontFamily : fontFamilyWeakPtrs)
  580. {
  581. ReleaseFontFamily(fontFamily);
  582. }
  583. // Reload the font families
  584. for (auto familyFilename : fontFamilyFilenames)
  585. {
  586. LoadFontFamily(familyFilename.c_str());
  587. }
  588. // All UI text components need to reload their font assets (both in-game
  589. // and in-editor).
  590. FontNotificationBus::Broadcast(&FontNotificationBus::Events::OnFontsReloaded);
  591. }
  592. void AZ::AtomFont::UnregisterFont(const char* fontName)
  593. {
  594. AZStd::string name(fontName);
  595. AZStd::to_lower(name.begin(), name.end());
  596. AzFramework::FontId fontId = GetFontId(name.c_str());
  597. FontMapItor it = m_fonts.find(fontId);
  598. #if defined(AZ_ENABLE_TRACING)
  599. IFFont* fontPtr = it->second;
  600. #endif
  601. if (it != m_fonts.end())
  602. {
  603. m_fonts.erase(it);
  604. }
  605. #if defined(AZ_ENABLE_TRACING)
  606. // Make sure the font being released isn't currently in use by a font family.
  607. // If it is, the FontFamily will have a dangling pointer and will cause a
  608. // crash when the FontFamily eventually gets released.
  609. for (auto reverseMapEntry : m_fontFamilyReverseLookup)
  610. {
  611. FontFamily* fontFamily = reverseMapEntry.first;
  612. AZ_Assert(fontFamily->normal != fontPtr,
  613. "The following font is being freed but still in use by a FontFamily: %s",
  614. fontName);
  615. AZ_Assert(fontFamily->italic != fontPtr,
  616. "The following font is being freed but still in use by a FontFamily: %s",
  617. fontName);
  618. AZ_Assert(fontFamily->bold != fontPtr,
  619. "The following font is being freed but still in use by a FontFamily: %s",
  620. fontName);
  621. AZ_Assert(fontFamily->boldItalic != fontPtr,
  622. "The following font is being freed but still in use by a FontFamily: %s",
  623. fontName);
  624. }
  625. #endif
  626. }
  627. IFFont* AZ::AtomFont::LoadFont(const char* fontName)
  628. {
  629. AZStd::string fontNameLower = fontName;
  630. AZStd::to_lower(fontNameLower.begin(), fontNameLower.end());
  631. IFFont* font = GetFont(fontNameLower.c_str());
  632. if (font)
  633. {
  634. font->AddRef(); // use existing loaded font
  635. }
  636. else
  637. {
  638. // attempt to create and load a new font, use the font pathname as the font name
  639. font = NewFont(fontNameLower.c_str());
  640. if (!font)
  641. {
  642. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Error creating a new font named %s.", fontNameLower.c_str());
  643. }
  644. else
  645. {
  646. // creating font adds one to its refcount so no need for AddRef here
  647. if (!font->Load(fontNameLower.c_str()))
  648. {
  649. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_ERROR, "Error loading a font from %s.", fontNameLower.c_str());
  650. font->Release();
  651. font = nullptr;
  652. }
  653. }
  654. }
  655. return font;
  656. }
  657. void AZ::AtomFont::ReleaseFontFamily(FontFamily* fontFamily)
  658. {
  659. // Ensure that Font Family was mapped prior to destruction
  660. const bool isMapped = m_fontFamilyReverseLookup.find(fontFamily) != m_fontFamilyReverseLookup.end();
  661. if (!isMapped)
  662. {
  663. return;
  664. }
  665. // Note that the FontFamily is mapped both by filename and by "family name"
  666. auto it = m_fontFamilyReverseLookup[fontFamily];
  667. m_fontFamilies.erase(it);
  668. AZStd::string familyName(fontFamily->familyName);
  669. AZStd::to_lower(familyName.begin(), familyName.end());
  670. m_fontFamilies.erase(familyName.c_str());
  671. // Reverse lookup is used to avoid needing to store filename path with
  672. // the font family, so we need to remove that entry also.
  673. m_fontFamilyReverseLookup.erase(fontFamily);
  674. SAFE_RELEASE(fontFamily->normal);
  675. SAFE_RELEASE(fontFamily->bold);
  676. SAFE_RELEASE(fontFamily->italic);
  677. SAFE_RELEASE(fontFamily->boldItalic);
  678. }
  679. bool AZ::AtomFont::AddFontFamilyToMaps(const char* fontFamilyFilename, const char* fontFamilyName, FontFamilyPtr fontFamily)
  680. {
  681. if (!fontFamilyFilename || !fontFamilyName || !fontFamily.get())
  682. {
  683. return false;
  684. }
  685. // We don't support "updating" mapped values.
  686. AZStd::string loweredFilename(PathUtil::MakeGamePath(AZStd::string(fontFamilyFilename)).c_str());
  687. AZStd::to_lower<AZStd::string::iterator>(loweredFilename.begin(), loweredFilename.end());
  688. if (m_fontFamilies.find(loweredFilename) != m_fontFamilies.end())
  689. {
  690. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Couldn't load Font Family '%s': already loaded", fontFamilyFilename);
  691. return false;
  692. }
  693. // Similarly, we don't support Font Family XMLs that have the same font
  694. // family name (we assume all Font Family names are unique).
  695. AZStd::string loweredFontFamilyName(fontFamilyName);
  696. AZStd::to_lower<AZStd::string::iterator>(loweredFontFamilyName.begin(), loweredFontFamilyName.end());
  697. if (m_fontFamilies.find(loweredFontFamilyName) != m_fontFamilies.end())
  698. {
  699. CryWarning(VALIDATOR_MODULE_SYSTEM, VALIDATOR_WARNING, "Couldn't load Font Family '%s': already loaded", fontFamilyName);
  700. return false;
  701. }
  702. // First, insert by filename
  703. AZStd::pair<AZStd::string, AZStd::weak_ptr<FontFamily>> insertPair(loweredFilename, fontFamily);
  704. auto iterPosition = m_fontFamilies.insert(insertPair).first;
  705. m_fontFamilyReverseLookup[fontFamily.get()] = iterPosition;
  706. // Then, by Font Family name
  707. AZStd::pair<AZStd::string, AZStd::weak_ptr<FontFamily>> nameInsertPair(loweredFontFamilyName, fontFamily);
  708. m_fontFamilies.insert(nameInsertPair);
  709. return true;
  710. }
  711. XmlNodeRef AZ::AtomFont::LoadFontFamilyXml(const char* fontFamilyName, AZStd::string& outputDirectory, AZStd::string& outputFullPath)
  712. {
  713. outputFullPath = fontFamilyName;
  714. outputDirectory = PathUtil::GetPath(fontFamilyName);
  715. XmlNodeRef root = SafeLoadXmlFromFile(outputFullPath);
  716. // When parsing a <font> tag in markup, only the font name is given and
  717. // not a path, so we try to build a "best guess" path from the name.
  718. if (!root)
  719. {
  720. AZStd::string fileNoExtension(PathUtil::GetFileName(fontFamilyName));
  721. AZStd::string fileExtension(PathUtil::GetExt(fontFamilyName));
  722. if (fileExtension.empty())
  723. {
  724. fileExtension = ".fontfamily";
  725. }
  726. // Try: "fonts/fontName.fontfamily"
  727. outputDirectory = AZStd::string("fonts/");
  728. outputFullPath = outputDirectory + fileNoExtension + fileExtension;
  729. root = SafeLoadXmlFromFile(outputFullPath);
  730. // Finally, try: "fonts/fontName/fontName.fontfamily"
  731. if (!root)
  732. {
  733. outputDirectory = AZStd::string("fonts/") + fileNoExtension + "/";
  734. outputFullPath = outputDirectory + fileNoExtension + fileExtension;
  735. root = SafeLoadXmlFromFile(outputFullPath);
  736. }
  737. }
  738. return root;
  739. }
  740. void AZ::AtomFont::OnAssetReady(Data::Asset<Data::AssetData> asset)
  741. {
  742. Data::Asset<RPI::ShaderAsset> shaderAsset = asset;
  743. AZ::AtomBridge::PerViewportDynamicDraw::Get()->RegisterDynamicDrawContext(
  744. AZ::Name(AZ::AtomFontDynamicDrawContextName),
  745. [shaderAsset](RPI::Ptr<RPI::DynamicDrawContext> drawContext)
  746. {
  747. AZ_Assert(shaderAsset->IsReady(), "Attempting to register the AtomFont"
  748. " dynamic draw context before the shader asset is loaded. The shader should be loaded first"
  749. " to avoid a blocking asset load and potential deadlock, since the DynamicDrawContext lambda"
  750. " will be executed during scene processing and there may be multiple scenes executing in parallel.");
  751. Data::Instance<RPI::Shader> shader = RPI::Shader::FindOrCreate(shaderAsset);
  752. AZ::RPI::ShaderOptionList shaderOptions;
  753. shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_useColorChannels"), AZ::Name("false")));
  754. shaderOptions.push_back(AZ::RPI::ShaderOption(AZ::Name("o_clamp"), AZ::Name("true")));
  755. drawContext->InitShaderWithVariant(shader, &shaderOptions);
  756. drawContext->InitVertexFormat(
  757. {
  758. {"POSITION", RHI::Format::R32G32B32_FLOAT},
  759. {"COLOR", RHI::Format::B8G8R8A8_UNORM},
  760. {"TEXCOORD0", RHI::Format::R32G32_FLOAT}
  761. });
  762. drawContext->EndInit();
  763. });
  764. Data::AssetBus::Handler::BusDisconnect();
  765. }
  766. #endif