NazaraEngine/src/Nazara/Utility/Font.cpp

601 lines
15 KiB
C++

// Copyright (C) 2017 Jérôme Leclercq
// This file is part of the "Nazara Engine - Utility module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Utility/Font.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/FontData.hpp>
#include <Nazara/Utility/FontGlyph.hpp>
#include <Nazara/Utility/GuillotineImageAtlas.hpp>
#include <Nazara/Utility/Debug.hpp>
namespace Nz
{
namespace
{
const UInt8 r_sansationRegular[] = {
#include <Nazara/Utility/Resources/Fonts/OpenSans-Regular.ttf.h>
};
}
bool FontParams::IsValid() const
{
return true; // Rien à tester
}
Font::Font() :
m_glyphBorder(s_defaultGlyphBorder),
m_minimumStepSize(s_defaultMinimumStepSize)
{
SetAtlas(s_defaultAtlas);
}
Font::~Font()
{
OnFontRelease(this);
Destroy();
SetAtlas(nullptr); // On libère l'atlas proprement
}
void Font::ClearGlyphCache()
{
if (m_atlas)
{
if (m_atlas.unique())
m_atlas->Clear(); // Appellera OnAtlasCleared
else
{
// Au moins une autre police utilise cet atlas, on vire nos glyphes un par un
for (auto mapIt = m_glyphes.begin(); mapIt != m_glyphes.end(); ++mapIt)
{
GlyphMap& glyphMap = mapIt->second;
for (auto glyphIt = glyphMap.begin(); glyphIt != glyphMap.end(); ++glyphIt)
{
Glyph& glyph = glyphIt->second;
m_atlas->Free(&glyph.atlasRect, &glyph.layerIndex, 1);
}
}
// Destruction des glyphes mémorisés et notification
m_glyphes.clear();
OnFontGlyphCacheCleared(this);
}
}
}
void Font::ClearKerningCache()
{
m_kerningCache.clear();
OnFontKerningCacheCleared(this);
}
void Font::ClearSizeInfoCache()
{
m_sizeInfoCache.clear();
OnFontSizeInfoCacheCleared(this);
}
bool Font::Create(FontData* data)
{
Destroy();
#if NAZARA_UTILITY_SAFE
if (!data)
{
NazaraError("Invalid font data");
return false;
}
#endif
m_data.reset(data);
return true;
}
void Font::Destroy()
{
if (m_data)
{
OnFontDestroy(this);
ClearGlyphCache();
m_data.reset();
m_kerningCache.clear();
m_sizeInfoCache.clear();
}
}
bool Font::ExtractGlyph(unsigned int characterSize, char32_t character, TextStyleFlags style, float outlineThickness, FontGlyph* glyph) const
{
#if NAZARA_UTILITY_SAFE
if (!IsValid())
{
NazaraError("Invalid font");
return false;
}
#endif
return m_data->ExtractGlyph(characterSize, character, style, outlineThickness, glyph);
}
const std::shared_ptr<AbstractAtlas>& Font::GetAtlas() const
{
return m_atlas;
}
std::size_t Font::GetCachedGlyphCount(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const
{
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
auto it = m_glyphes.find(key);
if (it == m_glyphes.end())
return 0;
return it->second.size();
}
std::size_t Font::GetCachedGlyphCount() const
{
std::size_t count = 0;
for (auto& pair : m_glyphes)
count += pair.second.size();
return count;
}
String Font::GetFamilyName() const
{
#if NAZARA_UTILITY_SAFE
if (!IsValid())
{
NazaraError("Invalid font");
return String("Invalid font");
}
#endif
return m_data->GetFamilyName();
}
int Font::GetKerning(unsigned int characterSize, char32_t first, char32_t second) const
{
#if NAZARA_UTILITY_SAFE
if (!IsValid())
{
NazaraError("Invalid font");
return 0;
}
#endif
// Use a cache as QueryKerning may be costly (may induce an internal size change)
auto& map = m_kerningCache[characterSize];
UInt64 key = (static_cast<UInt64>(first) << 32) | second;
auto it = map.find(key);
if (it == map.end())
{
int kerning = m_data->QueryKerning(characterSize, first, second);
map.insert(std::make_pair(key, kerning));
return kerning;
}
else
return it->second;
}
const Font::Glyph& Font::GetGlyph(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
{
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character);
}
unsigned int Font::GetGlyphBorder() const
{
return m_glyphBorder;
}
unsigned int Font::GetMinimumStepSize() const
{
return m_minimumStepSize;
}
const Font::SizeInfo& Font::GetSizeInfo(unsigned int characterSize) const
{
#if NAZARA_UTILITY_SAFE
if (!IsValid())
{
NazaraError("Invalid font");
static SizeInfo dummy;
return dummy;
}
#endif
auto it = m_sizeInfoCache.find(characterSize);
if (it == m_sizeInfoCache.end())
{
SizeInfo sizeInfo;
sizeInfo.lineHeight = m_data->QueryLineHeight(characterSize);
sizeInfo.underlinePosition = m_data->QueryUnderlinePosition(characterSize);
sizeInfo.underlineThickness = m_data->QueryUnderlineThickness(characterSize);
FontGlyph glyph;
if (m_data->ExtractGlyph(characterSize, ' ', TextStyle_Regular, 0.f, &glyph))
sizeInfo.spaceAdvance = glyph.advance;
else
{
NazaraWarning("Failed to extract space character from font, using half the character size");
sizeInfo.spaceAdvance = characterSize/2;
}
it = m_sizeInfoCache.insert(std::make_pair(characterSize, sizeInfo)).first;
}
return it->second;
}
String Font::GetStyleName() const
{
#if NAZARA_UTILITY_SAFE
if (!IsValid())
{
NazaraError("Invalid font");
return String("Invalid font");
}
#endif
return m_data->GetStyleName();
}
bool Font::IsValid() const
{
return m_data != nullptr;
}
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
{
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
return PrecacheGlyph(m_glyphes[key], characterSize, style, outlineThickness, character).valid;
}
bool Font::Precache(unsigned int characterSize, TextStyleFlags style, float outlineThickness, const String& characterSet) const
{
///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?)
std::u32string set = characterSet.GetUtf32String();
if (set.empty())
{
NazaraError("Invalid character set");
return false;
}
UInt64 key = ComputeKey(characterSize, style, outlineThickness);
auto& glyphMap = m_glyphes[key];
for (char32_t character : set)
PrecacheGlyph(glyphMap, characterSize, style, outlineThickness, character);
return true;
}
void Font::SetAtlas(const std::shared_ptr<AbstractAtlas>& atlas)
{
if (m_atlas != atlas)
{
ClearGlyphCache();
m_atlas = atlas;
if (m_atlas)
{
m_atlasClearedSlot.Connect(m_atlas->OnAtlasCleared, this, &Font::OnAtlasCleared);
m_atlasLayerChangeSlot.Connect(m_atlas->OnAtlasLayerChange, this, &Font::OnAtlasLayerChange);
m_atlasReleaseSlot.Connect(m_atlas->OnAtlasRelease, this, &Font::OnAtlasRelease);
}
else
{
m_atlasClearedSlot.Disconnect();
m_atlasLayerChangeSlot.Disconnect();
m_atlasReleaseSlot.Disconnect();
}
OnFontAtlasChanged(this);
}
}
void Font::SetGlyphBorder(unsigned int borderSize)
{
if (m_glyphBorder != borderSize)
{
m_glyphBorder = borderSize;
ClearGlyphCache();
}
}
void Font::SetMinimumStepSize(unsigned int minimumStepSize)
{
if (m_minimumStepSize != minimumStepSize)
{
NazaraAssert(minimumStepSize != 0, "Minimum step size cannot be zero");
m_minimumStepSize = minimumStepSize;
ClearGlyphCache();
}
}
std::shared_ptr<AbstractAtlas> Font::GetDefaultAtlas()
{
return s_defaultAtlas;
}
const FontRef& Font::GetDefault()
{
// Nous n'initialisons la police par défaut qu'à la demande pour qu'elle prenne
// les paramètres par défaut (qui peuvent avoir étés changés par l'utilisateur),
// et pour ne pas consommer de la mémoire vive inutilement (si elle n'est jamais utilisée, elle n'est jamais ouverte).
if (!s_defaultFont)
{
s_defaultFont = Font::OpenFromMemory(r_sansationRegular, sizeof(r_sansationRegular));
if (!s_defaultFont)
NazaraError("Failed to open default font");
}
return s_defaultFont;
}
unsigned int Font::GetDefaultGlyphBorder()
{
return s_defaultGlyphBorder;
}
unsigned int Font::GetDefaultMinimumStepSize()
{
return s_defaultMinimumStepSize;
}
FontRef Font::OpenFromFile(const String& filePath, const FontParams& params)
{
return FontLoader::LoadFromFile(filePath, params);
}
FontRef Font::OpenFromMemory(const void* data, std::size_t size, const FontParams& params)
{
return FontLoader::LoadFromMemory(data, size, params);
}
FontRef Font::OpenFromStream(Stream& stream, const FontParams& params)
{
return FontLoader::LoadFromStream(stream, params);
}
void Font::SetDefaultAtlas(const std::shared_ptr<AbstractAtlas>& atlas)
{
s_defaultAtlas = atlas;
}
void Font::SetDefaultGlyphBorder(unsigned int borderSize)
{
s_defaultGlyphBorder = borderSize;
}
void Font::SetDefaultMinimumStepSize(unsigned int minimumStepSize)
{
#if NAZARA_UTILITY_SAFE
if (minimumStepSize == 0)
{
NazaraError("Minimum step size cannot be zero as it implies division by zero");
return;
}
#endif
s_defaultMinimumStepSize = minimumStepSize;
}
UInt64 Font::ComputeKey(unsigned int characterSize, TextStyleFlags style, float outlineThickness) const
{
// Adjust size to step size
UInt64 sizeStylePart = static_cast<UInt32>((characterSize/m_minimumStepSize)*m_minimumStepSize);
sizeStylePart = std::min<UInt64>(sizeStylePart, Nz::IntegralPow(2, 30)); //< 2^30 should be more than enough as a max size
sizeStylePart <<= 2;
// Store bold and italic flags (other style are handled directly by a TextDrawer)
if (style & TextStyle_Bold)
sizeStylePart |= 1 << 0;
if (style & TextStyle_Italic)
sizeStylePart |= 1 << 1;
return (sizeStylePart << 32) | reinterpret_cast<Nz::UInt32&>(outlineThickness);
}
void Font::OnAtlasCleared(const AbstractAtlas* atlas)
{
NazaraUnused(atlas);
#ifdef NAZARA_DEBUG
// Est-ce qu'il s'agit bien de notre atlas ?
if (m_atlas.get() != atlas)
{
NazaraInternalError("Notified by a non-listening-to resource");
return;
}
#endif
// Notre atlas vient d'être vidé, détruisons le cache de glyphe
m_glyphes.clear();
OnFontGlyphCacheCleared(this);
}
void Font::OnAtlasLayerChange(const AbstractAtlas* atlas, AbstractImage* oldLayer, AbstractImage* newLayer)
{
NazaraUnused(atlas);
NazaraUnused(oldLayer);
NazaraUnused(newLayer);
#ifdef NAZARA_DEBUG
// Est-ce qu'il s'agit bien de notre atlas ?
if (m_atlas.get() != atlas)
{
NazaraInternalError("Notified by a non-listening-to resource");
return;
}
#endif
// Pour faciliter le travail des ressources qui nous écoutent
OnFontAtlasLayerChanged(this, oldLayer, newLayer);
}
void Font::OnAtlasRelease(const AbstractAtlas* atlas)
{
NazaraUnused(atlas);
#ifdef NAZARA_DEBUG
// Est-ce qu'il s'agit bien de notre atlas ?
if (m_atlas.get() != atlas)
{
NazaraInternalError("Notified by a non-listening-to resource");
return;
}
#endif
// Nous ne pouvons pas faire grand chose d'autre que de balancer une erreur à la tête de l'utilisateur avant un potentiel crash...
NazaraError("Atlas has been released while in use");
}
const Font::Glyph& Font::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, TextStyleFlags style, float outlineThickness, char32_t character) const
{
auto it = glyphMap.find(character);
if (it != glyphMap.end())
return it->second;
Glyph& glyph = glyphMap[character]; //< Insert a new glyph
glyph.valid = false;
#if NAZARA_UTILITY_SAFE
if (!m_atlas)
{
NazaraError("Font has no atlas");
return glyph;
}
#endif
// Check if requested style is supported by our font (otherwise it will need to be simulated)
glyph.fauxOutlineThickness = 0.f;
glyph.requireFauxBold = false;
glyph.requireFauxItalic = false;
TextStyleFlags supportedStyle = style;
if (style & TextStyle_Bold && !m_data->SupportsStyle(TextStyle_Bold))
{
glyph.requireFauxBold = true;
supportedStyle &= ~TextStyle_Bold;
}
if (style & TextStyle_Italic && !m_data->SupportsStyle(TextStyle_Italic))
{
glyph.requireFauxItalic = true;
supportedStyle &= ~TextStyle_Italic;
}
float supportedOutlineThickness = outlineThickness;
if (outlineThickness > 0.f && !m_data->SupportsOutline(outlineThickness))
{
glyph.fauxOutlineThickness = supportedOutlineThickness;
supportedOutlineThickness = 0.f;
}
// Does font support requested style?
if (style == supportedStyle && outlineThickness == supportedOutlineThickness)
{
FontGlyph fontGlyph;
if (ExtractGlyph(characterSize, character, style, outlineThickness, &fontGlyph))
{
if (fontGlyph.image.IsValid())
{
glyph.atlasRect.width = fontGlyph.image.GetWidth();
glyph.atlasRect.height = fontGlyph.image.GetHeight();
}
else
{
glyph.atlasRect.width = 0;
glyph.atlasRect.height = 0;
}
// Insert rectangle (if not empty) into our atlas
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0)
{
// Add a small border to prevent GPU to sample another glyph pixel
glyph.atlasRect.width += m_glyphBorder*2;
glyph.atlasRect.height += m_glyphBorder*2;
if (!m_atlas->Insert(fontGlyph.image, &glyph.atlasRect, &glyph.flipped, &glyph.layerIndex))
{
NazaraError("Failed to insert glyph into atlas");
return glyph;
}
// Recenter and remove glyph border
glyph.atlasRect.x += m_glyphBorder;
glyph.atlasRect.y += m_glyphBorder;
glyph.atlasRect.width -= m_glyphBorder*2;
glyph.atlasRect.height -= m_glyphBorder*2;
}
glyph.aabb = fontGlyph.aabb;
glyph.advance = fontGlyph.advance;
glyph.valid = true;
}
else
NazaraWarning("Failed to extract glyph \"" + String::Unicode(character) + "\"");
}
else
{
// Font doesn't support request style, precache the minimal supported version and copy its data
UInt64 newKey = ComputeKey(characterSize, supportedStyle, supportedOutlineThickness);
const Glyph& referenceGlyph = PrecacheGlyph(m_glyphes[newKey], characterSize, supportedStyle, supportedOutlineThickness, character);
if (referenceGlyph.valid)
{
glyph.aabb = referenceGlyph.aabb;
glyph.advance = referenceGlyph.advance;
glyph.atlasRect = referenceGlyph.atlasRect;
glyph.flipped = referenceGlyph.flipped;
glyph.layerIndex = referenceGlyph.layerIndex;
glyph.valid = true;
}
}
return glyph;
}
bool Font::Initialize()
{
if (!FontLibrary::Initialize())
{
NazaraError("Failed to initialise library");
return false;
}
s_defaultAtlas.reset(new GuillotineImageAtlas);
s_defaultGlyphBorder = 1;
s_defaultMinimumStepSize = 1;
return true;
}
void Font::Uninitialize()
{
s_defaultAtlas.reset();
s_defaultFont.Reset();
FontLibrary::Uninitialize();
}
std::shared_ptr<AbstractAtlas> Font::s_defaultAtlas;
FontRef Font::s_defaultFont;
FontLibrary::LibraryMap Font::s_library;
FontLoader::LoaderList Font::s_loaders;
unsigned int Font::s_defaultGlyphBorder;
unsigned int Font::s_defaultMinimumStepSize;
}