2024-09-24 12:41:39 +02:00
|
|
|
/*
|
|
|
|
* FontChain.cpp, part of VCMI engine
|
|
|
|
*
|
|
|
|
* Authors: listed in file AUTHORS in main folder
|
|
|
|
*
|
|
|
|
* License: GNU General Public License v2.0 or later
|
|
|
|
* Full text of license available in license.txt file, in main folder
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
#include "StdInc.h"
|
|
|
|
#include "FontChain.h"
|
|
|
|
|
|
|
|
#include "CTrueTypeFont.h"
|
|
|
|
#include "CBitmapFont.h"
|
|
|
|
|
2024-09-28 21:31:16 +02:00
|
|
|
#include "../CGameInfo.h"
|
|
|
|
|
2024-09-24 12:41:39 +02:00
|
|
|
#include "../../lib/CConfigHandler.h"
|
2024-09-28 21:31:16 +02:00
|
|
|
#include "../../lib/modding/CModHandler.h"
|
2024-09-24 12:41:39 +02:00
|
|
|
#include "../../lib/texts/TextOperations.h"
|
2024-09-28 21:31:16 +02:00
|
|
|
#include "../../lib/texts/CGeneralTextHandler.h"
|
|
|
|
#include "../../lib/texts/Languages.h"
|
2024-09-24 12:41:39 +02:00
|
|
|
|
|
|
|
void FontChain::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const
|
|
|
|
{
|
|
|
|
auto chunks = splitTextToChunks(data);
|
|
|
|
int maxAscent = getFontAscentScaled();
|
|
|
|
Point currentPos = pos;
|
|
|
|
for (auto const & chunk : chunks)
|
|
|
|
{
|
|
|
|
Point chunkPos = currentPos;
|
|
|
|
int currAscent = chunk.font->getFontAscentScaled();
|
|
|
|
chunkPos.y += maxAscent - currAscent;
|
|
|
|
chunk.font->renderText(surface, chunk.text, color, chunkPos);
|
|
|
|
currentPos.x += chunk.font->getStringWidthScaled(chunk.text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FontChain::getFontAscentScaled() const
|
|
|
|
{
|
|
|
|
size_t maxHeight = 0;
|
|
|
|
for(const auto & font : chain)
|
|
|
|
maxHeight = std::max(maxHeight, font->getFontAscentScaled());
|
|
|
|
return maxHeight;
|
|
|
|
}
|
|
|
|
|
2024-09-28 21:31:16 +02:00
|
|
|
bool FontChain::bitmapFontsPrioritized(const std::string & bitmapFontName) const
|
2024-09-24 14:17:25 +02:00
|
|
|
{
|
|
|
|
const std::string & fontType = settings["video"]["fontsType"].String();
|
|
|
|
if (fontType == "original")
|
|
|
|
return true;
|
|
|
|
if (fontType == "scalable")
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// else - autoselection.
|
|
|
|
|
|
|
|
if (getScalingFactor() != 1)
|
|
|
|
return false; // If xbrz in use ttf/scalable fonts are preferred
|
|
|
|
|
|
|
|
if (!vstd::isAlmostEqual(1.0, settings["video"]["fontScalingFactor"].Float()))
|
|
|
|
return false; // If player requested non-100% scaling - use scalable fonts
|
|
|
|
|
2024-09-28 21:31:16 +02:00
|
|
|
std::string gameLanguage = CGI->generaltexth->getPreferredLanguage();
|
|
|
|
std::string gameEncoding = Languages::getLanguageOptions(gameLanguage).encoding;
|
2024-10-30 13:54:35 +02:00
|
|
|
std::string fontEncoding = CGI->modh->findResourceEncoding(ResourcePath("data/" + bitmapFontName, EResType::BMP_FONT));
|
2024-09-28 21:31:16 +02:00
|
|
|
|
|
|
|
// player uses language with different encoding than his bitmap fonts
|
|
|
|
// for example, Polish language with English fonts or Chinese language which can't use H3 fonts at all
|
|
|
|
// this may result in unintended mixing of ttf and bitmap fonts, which may have a bit different look
|
|
|
|
// so in this case prefer ttf fonts that are likely to cover target language better than H3 fonts
|
|
|
|
if (fontEncoding != gameEncoding)
|
|
|
|
return false;
|
|
|
|
|
2024-09-24 14:17:25 +02:00
|
|
|
return true; // else - use original bitmap fonts
|
|
|
|
}
|
|
|
|
|
2024-09-24 12:41:39 +02:00
|
|
|
void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig)
|
|
|
|
{
|
2024-09-24 14:17:25 +02:00
|
|
|
chain.insert(chain.begin(), std::make_unique<CTrueTypeFont>(trueTypeConfig));
|
2024-09-24 12:41:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void FontChain::addBitmapFont(const std::string & bitmapFilename)
|
|
|
|
{
|
2024-09-28 21:31:16 +02:00
|
|
|
if (bitmapFontsPrioritized(bitmapFilename))
|
2024-09-24 12:41:39 +02:00
|
|
|
chain.insert(chain.begin(), std::make_unique<CBitmapFont>(bitmapFilename));
|
2024-09-24 14:17:25 +02:00
|
|
|
else
|
|
|
|
chain.push_back(std::make_unique<CBitmapFont>(bitmapFilename));
|
2024-09-24 12:41:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool FontChain::canRepresentCharacter(const char * data) const
|
|
|
|
{
|
|
|
|
for(const auto & font : chain)
|
|
|
|
if (font->canRepresentCharacter(data))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FontChain::getLineHeightScaled() const
|
|
|
|
{
|
|
|
|
size_t maxHeight = 0;
|
|
|
|
for(const auto & font : chain)
|
|
|
|
maxHeight = std::max(maxHeight, font->getLineHeightScaled());
|
|
|
|
return maxHeight;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FontChain::getGlyphWidthScaled(const char * data) const
|
|
|
|
{
|
|
|
|
for(const auto & font : chain)
|
|
|
|
if (font->canRepresentCharacter(data))
|
|
|
|
return font->getGlyphWidthScaled(data);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<FontChain::TextChunk> FontChain::splitTextToChunks(const std::string & data) const
|
|
|
|
{
|
|
|
|
std::vector<TextChunk> chunks;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
|
|
|
|
{
|
|
|
|
const IFont * currentFont = nullptr;
|
|
|
|
for(const auto & font : chain)
|
|
|
|
{
|
|
|
|
if (font->canRepresentCharacter(data.data() + i))
|
|
|
|
{
|
|
|
|
currentFont = font.get();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (currentFont == nullptr)
|
|
|
|
continue; // not representable
|
|
|
|
|
|
|
|
std::string symbol = data.substr(i, TextOperations::getUnicodeCharacterSize(data[i]));
|
|
|
|
|
|
|
|
if (chunks.empty() || chunks.back().font != currentFont)
|
|
|
|
chunks.push_back({currentFont, symbol});
|
|
|
|
else
|
|
|
|
chunks.back().text += symbol;
|
|
|
|
}
|
|
|
|
|
|
|
|
return chunks;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t FontChain::getStringWidthScaled(const std::string & data) const
|
|
|
|
{
|
|
|
|
size_t result = 0;
|
|
|
|
auto chunks = splitTextToChunks(data);
|
|
|
|
for (auto const & chunk : chunks)
|
|
|
|
result += chunk.font->getStringWidthScaled(chunk.text);
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|