1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-11-24 08:32:34 +02:00

Proper support for usage of multiple fonts in a chain

This commit is contained in:
Ivan Savenko 2024-09-24 10:41:39 +00:00
parent 557b72f2b3
commit 87274128e7
11 changed files with 284 additions and 83 deletions

View File

@ -98,6 +98,7 @@ set(vcmiclientcommon_SRCS
renderSDL/CTrueTypeFont.cpp
renderSDL/CursorHardware.cpp
renderSDL/CursorSoftware.cpp
renderSDL/FontChain.cpp
renderSDL/ImageScaled.cpp
renderSDL/RenderHandler.cpp
renderSDL/SDLImage.cpp
@ -305,6 +306,7 @@ set(vcmiclientcommon_HEADERS
renderSDL/CTrueTypeFont.h
renderSDL/CursorHardware.h
renderSDL/CursorSoftware.h
renderSDL/FontChain.h
renderSDL/ImageScaled.h
renderSDL/RenderHandler.h
renderSDL/SDLImage.h

View File

@ -23,13 +23,33 @@ int IFont::getScalingFactor() const
return GH.screenHandler().getScalingFactor();
}
size_t IFont::getLineHeight() const
{
return getLineHeightScaled() / getScalingFactor();
}
size_t IFont::getGlyphWidth(const char * data) const
{
return getGlyphWidthScaled(data) / getScalingFactor();
}
size_t IFont::getStringWidth(const std::string & data) const
{
return getStringWidthScaled(data) / getScalingFactor();
}
size_t IFont::getFontAscent() const
{
return getFontAscentScaled() / getScalingFactor();
}
size_t IFont::getStringWidthScaled(const std::string & data) const
{
size_t width = 0;
for(size_t i=0; i<data.size(); i += TextOperations::getUnicodeCharacterSize(data[i]))
{
width += getGlyphWidth(data.data() + i);
width += getGlyphWidthScaled(data.data() + i);
}
return width;
}

View File

@ -19,8 +19,6 @@ struct SDL_Surface;
class IFont : boost::noncopyable
{
protected:
/// Internal function to render font, see renderTextLeft
virtual void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const = 0;
int getScalingFactor() const;
@ -29,11 +27,27 @@ public:
{}
/// Returns height of font
virtual size_t getLineHeight() const = 0;
virtual size_t getLineHeightScaled() const = 0;
/// Returns width, in pixels of a character glyph. Pointer must contain at least characterSize valid bytes
virtual size_t getGlyphWidth(const char * data) const = 0;
virtual size_t getGlyphWidthScaled(const char * data) const = 0;
/// Return width of the string
virtual size_t getStringWidth(const std::string & data) const;
virtual size_t getStringWidthScaled(const std::string & data) const;
/// Returns distance from top of the font glyphs to baseline
virtual size_t getFontAscentScaled() const = 0;
/// Returns height of font
size_t getLineHeight() const;
/// Returns width, in pixels of a character glyph. Pointer must contain at least characterSize valid bytes
size_t getGlyphWidth(const char * data) const;
/// Return width of the string
size_t getStringWidth(const std::string & data) const;
/// Returns distance from top of the font glyphs to baseline
size_t getFontAscent() const;
/// Internal function to render font, see renderTextLeft
virtual void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const = 0;
virtual bool canRepresentCharacter(const char * data) const = 0;
/**
* @param surface - destination to print text on

View File

@ -96,21 +96,14 @@ static AtlasLayout doAtlasPacking(const std::map<int, Point> & images)
}
}
void CBitmapFont::loadModFont(const std::string & modName, const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars)
void CBitmapFont::loadFont(const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars)
{
if (!CResourceHandler::get(modName)->existsResource(resource))
{
logGlobal->error("Failed to load font %s from mod %s", resource.getName(), modName);
return;
}
auto data = CResourceHandler::get(modName)->load(resource)->readAll();
std::string modLanguage = CGI->modh->getModLanguage(modName);
auto data = CResourceHandler::get()->load(resource)->readAll();
std::string modName = VLC->modh->findResourceOrigin(resource);
std::string modLanguage = VLC->modh->getModLanguage(modName);
std::string modEncoding = Languages::getLanguageOptions(modLanguage).encoding;
uint32_t dataHeight = data.first[5];
maxHeight = std::max(maxHeight, dataHeight);
height = data.first[5];
constexpr size_t symbolsInFile = 0x100;
constexpr size_t baseIndex = 32;
@ -126,10 +119,10 @@ void CBitmapFont::loadModFont(const std::string & modName, const ResourcePath &
symbol.leftOffset = read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 0);
symbol.width = read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 4);
symbol.rightOffset = read_le_u32(data.first.get() + baseIndex + charIndex * 12 + 8);
symbol.height = dataHeight;
symbol.height = height;
uint32_t pixelDataOffset = read_le_u32(data.first.get() + offsetIndex + charIndex * 4);
uint32_t pixelsCount = dataHeight * symbol.width;
uint32_t pixelsCount = height * symbol.width;
symbol.pixels.resize(pixelsCount);
@ -139,21 +132,27 @@ void CBitmapFont::loadModFont(const std::string & modName, const ResourcePath &
loadedChars[codepoint] = symbol;
}
// Try to use symbol 'L' to detect font 'ascent' - number of pixels above text baseline
const auto & symbolL = loadedChars['L'];
uint32_t lastNonEmptyRow = 0;
for (uint32_t row = 0; row < symbolL.height; ++row)
{
for (uint32_t col = 0; col < symbolL.width; ++col)
if (symbolL.pixels.at(row * symbolL.width + col) == 255)
lastNonEmptyRow = std::max(lastNonEmptyRow, row);
}
ascent = lastNonEmptyRow + 1;
}
CBitmapFont::CBitmapFont(const std::string & filename):
maxHeight(0)
height(0)
{
ResourcePath resource("data/" + filename, EResType::BMP_FONT);
std::unordered_map<CodePoint, EntryFNT> loadedChars;
loadModFont("core", resource, loadedChars);
for(const auto & modName : VLC->modh->getActiveMods())
{
if (CResourceHandler::get(modName)->existsResource(resource))
loadModFont(modName, resource, loadedChars);
}
loadFont(resource, loadedChars);
std::map<int, Point> atlasSymbol;
for (auto const & symbol : loadedChars)
@ -213,8 +212,6 @@ CBitmapFont::CBitmapFont(const std::string & filename):
SDL_FreeSurface(atlasImage);
atlasImage = scaledSurface;
}
IMG_SavePNG(atlasImage, ("/home/ivan/font_" + filename).c_str());
}
CBitmapFont::~CBitmapFont()
@ -222,12 +219,12 @@ CBitmapFont::~CBitmapFont()
SDL_FreeSurface(atlasImage);
}
size_t CBitmapFont::getLineHeight() const
size_t CBitmapFont::getLineHeightScaled() const
{
return maxHeight;
return height * getScalingFactor();
}
size_t CBitmapFont::getGlyphWidth(const char * data) const
size_t CBitmapFont::getGlyphWidthScaled(const char * data) const
{
CodePoint localChar = TextOperations::getUnicodeCodepoint(data, 4);
@ -236,7 +233,12 @@ size_t CBitmapFont::getGlyphWidth(const char * data) const
if (iter == chars.end())
return 0;
return iter->second.leftOffset + iter->second.positionInAtlas.w + iter->second.rightOffset;
return (iter->second.leftOffset + iter->second.positionInAtlas.w + iter->second.rightOffset) * getScalingFactor();
}
size_t CBitmapFont::getFontAscentScaled() const
{
return ascent * getScalingFactor();
}
bool CBitmapFont::canRepresentCharacter(const char *data) const

View File

@ -40,9 +40,10 @@ class CBitmapFont final : public IFont
};
std::unordered_map<CodePoint, BitmapChar> chars;
uint32_t maxHeight;
uint32_t height;
uint32_t ascent;
void loadModFont(const std::string & modName, const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars);
void loadFont(const ResourcePath & resource, std::unordered_map<CodePoint, EntryFNT> & loadedChars);
void renderCharacter(SDL_Surface * surface, const BitmapChar & character, const ColorRGBA & color, int &posX, int &posY) const;
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
@ -50,11 +51,12 @@ public:
explicit CBitmapFont(const std::string & filename);
~CBitmapFont();
size_t getLineHeight() const override;
size_t getGlyphWidth(const char * data) const override;
size_t getFontAscentScaled() const override;
size_t getLineHeightScaled() const override;
size_t getGlyphWidthScaled(const char * data) const override;
/// returns true if this font contains provided utf-8 character
bool canRepresentCharacter(const char * data) const;
bool canRepresentCharacter(const char * data) const override;
bool canRepresentString(const std::string & data) const;
friend class CBitmapHanFont;

View File

@ -15,6 +15,7 @@
#include "../render/Colors.h"
#include "../renderSDL/SDL_Extensions.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/json/JsonNode.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/texts/TextOperations.h"
@ -29,12 +30,13 @@ std::pair<std::unique_ptr<ui8[]>, ui64> CTrueTypeFont::loadData(const JsonNode &
int CTrueTypeFont::getPointSize(const JsonNode & config) const
{
float fontScale = settings["video"]["fontScalingFactor"].Float();
int scalingFactor = getScalingFactor();
if (config.isNumber())
return config.Integer() * scalingFactor;
return std::round(config.Integer() * scalingFactor * fontScale);
else
return config[scalingFactor-1].Integer();
return std::round(config[scalingFactor-1].Integer() * fontScale);
}
TTF_Font * CTrueTypeFont::loadFont(const JsonNode &config)
@ -71,48 +73,39 @@ CTrueTypeFont::CTrueTypeFont(const JsonNode & fontConfig):
TTF_SetFontStyle(font.get(), getFontStyle(fontConfig));
TTF_SetFontHinting(font.get(),TTF_HINTING_MONO);
std::string fallbackName = fontConfig["fallback"].String();
if (!fallbackName.empty())
fallbackFont = std::make_unique<CBitmapFont>(fallbackName);
}
CTrueTypeFont::~CTrueTypeFont() = default;
size_t CTrueTypeFont::getLineHeight() const
size_t CTrueTypeFont::getFontAscentScaled() const
{
if (fallbackFont)
return fallbackFont->getLineHeight();
return TTF_FontHeight(font.get()) / getScalingFactor();
return TTF_FontAscent(font.get());
}
size_t CTrueTypeFont::getGlyphWidth(const char *data) const
size_t CTrueTypeFont::getLineHeightScaled() const
{
if (fallbackFont && fallbackFont->canRepresentCharacter(data))
return fallbackFont->getGlyphWidth(data);
return getStringWidth(std::string(data, TextOperations::getUnicodeCharacterSize(*data)));
return TTF_FontHeight(font.get());
}
size_t CTrueTypeFont::getStringWidth(const std::string & data) const
size_t CTrueTypeFont::getGlyphWidthScaled(const char *data) const
{
if (fallbackFont && fallbackFont->canRepresentString(data))
return fallbackFont->getStringWidth(data);
return getStringWidthScaled(std::string(data, TextOperations::getUnicodeCharacterSize(*data)));
}
bool CTrueTypeFont::canRepresentCharacter(const char * data) const
{
return TTF_GlyphIsProvided32(font.get(), TextOperations::getUnicodeCodepoint(data, TextOperations::getUnicodeCharacterSize(*data)));
}
size_t CTrueTypeFont::getStringWidthScaled(const std::string & data) const
{
int width;
TTF_SizeUTF8(font.get(), data.c_str(), &width, nullptr);
return width / getScalingFactor();
return width;
}
void CTrueTypeFont::renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const
{
if (fallbackFont && fallbackFont->canRepresentString(data))
{
fallbackFont->renderText(surface, data, color, pos);
return;
}
if (color.r != 0 && color.g != 0 && color.b != 0) // not black - add shadow
{
if (outline)

View File

@ -21,7 +21,6 @@ using TTF_Font = struct _TTF_Font;
class CTrueTypeFont final : public IFont
{
std::unique_ptr<CBitmapFont> fallbackFont;
const std::pair<std::unique_ptr<ui8[]>, ui64> data;
const std::unique_ptr<TTF_Font, void (*)(TTF_Font*)> font;
@ -35,11 +34,14 @@ class CTrueTypeFont final : public IFont
int getFontStyle(const JsonNode & config) const;
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
size_t getFontAscentScaled() const override;
public:
CTrueTypeFont(const JsonNode & fontConfig);
~CTrueTypeFont();
size_t getLineHeight() const override;
size_t getGlyphWidth(const char * data) const override;
size_t getStringWidth(const std::string & data) const override;
bool canRepresentCharacter(const char * data) const override;
size_t getLineHeightScaled() const override;
size_t getGlyphWidthScaled(const char * data) const override;
size_t getStringWidthScaled(const std::string & data) const override;
};

View File

@ -0,0 +1,117 @@
/*
* 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"
#include "../../lib/CConfigHandler.h"
#include "../../lib/texts/TextOperations.h"
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;
}
void FontChain::addTrueTypeFont(const JsonNode & trueTypeConfig)
{
chain.push_back(std::make_unique<CTrueTypeFont>(trueTypeConfig));
}
void FontChain::addBitmapFont(const std::string & bitmapFilename)
{
if (settings["video"]["scalableFonts"].Bool())
chain.push_back(std::make_unique<CBitmapFont>(bitmapFilename));
else
chain.insert(chain.begin(), std::make_unique<CBitmapFont>(bitmapFilename));
}
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;
}

View File

@ -0,0 +1,42 @@
/*
* FontChain.h, 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
*
*/
#pragma once
#include "../render/IFont.h"
VCMI_LIB_NAMESPACE_BEGIN
class JsonNode;
VCMI_LIB_NAMESPACE_END
class FontChain final : public IFont
{
struct TextChunk
{
const IFont * font;
std::string text;
};
std::vector<TextChunk> splitTextToChunks(const std::string & data) const;
std::vector<std::unique_ptr<IFont>> chain;
void renderText(SDL_Surface * surface, const std::string & data, const ColorRGBA & color, const Point & pos) const override;
size_t getFontAscentScaled() const override;
public:
FontChain() = default;
void addTrueTypeFont(const JsonNode & trueTypeConfig);
void addBitmapFont(const std::string & bitmapFilename);
size_t getLineHeightScaled() const override;
size_t getGlyphWidthScaled(const char * data) const override;
size_t getStringWidthScaled(const std::string & data) const override;
bool canRepresentCharacter(const char * data) const override;
};

View File

@ -12,8 +12,7 @@
#include "SDLImage.h"
#include "ImageScaled.h"
#include "CBitmapFont.h"
#include "CTrueTypeFont.h"
#include "FontChain.h"
#include "../gui/CGuiHandler.h"
@ -22,8 +21,6 @@
#include "../render/Colors.h"
#include "../render/ColorFilter.h"
#include "../render/IScreenHandler.h"
#include "../../lib/CConfigHandler.h"
#include "../../lib/json/JsonUtils.h"
#include "../../lib/filesystem/Filesystem.h"
#include "../../lib/VCMIDirs.h"
@ -345,18 +342,23 @@ std::shared_ptr<const IFont> RenderHandler::loadFont(EFonts font)
return fonts.at(font);
const int8_t index = static_cast<int8_t>(font);
const JsonNode config(JsonPath::builtin("config/fonts.json"));
const JsonVector & bmpConf = config["bitmap"].Vector();
const JsonNode & ttfConf = config["trueType"];
auto configList = CResourceHandler::get()->getResourcesWithName(JsonPath::builtin("config/fonts.json"));
std::shared_ptr<FontChain> loadedFont = std::make_shared<FontChain>();
std::string bitmapPath;
std::string filename = bmpConf[index].String();
for(auto & loader : configList)
{
auto stream = loader->load(JsonPath::builtin("config/fonts.json"));
std::unique_ptr<ui8[]> textData(new ui8[stream->getSize()]);
stream->read(textData.get(), stream->getSize());
const JsonNode config(reinterpret_cast<const std::byte*>(textData.get()), stream->getSize(), "config/fonts.json");
const JsonVector & bmpConf = config["bitmap"].Vector();
const JsonNode & ttfConf = config["trueType"];
std::shared_ptr<const IFont> loadedFont;
if(ttfConf[filename].isNull() || settings["video"]["scalableFonts"].Bool() == false)
loadedFont = std::make_shared<CBitmapFont>(filename);
else
loadedFont = std::make_shared<CTrueTypeFont>(ttfConf[filename]);
bitmapPath = bmpConf[index].String();
loadedFont->addTrueTypeFont(ttfConf[bitmapPath]);
}
loadedFont->addBitmapFont(bitmapPath);
fonts[font] = loadedFont;
return loadedFont;

View File

@ -167,6 +167,7 @@
"targetfps",
"vsync",
"scalableFonts",
"fontScalingFactor",
"upscalingFilter",
"fontUpscalingFilter",
"downscalingFilter"
@ -237,6 +238,10 @@
"type" : "boolean",
"default" : false
},
"fontScalingFactor" : {
"type" : "number",
"default" : 1
},
"fontUpscalingFilter" : {
"type" : "string",
"enum" : [ "nearest", "bilinear", "xbrz" ],