Added Font class (+ FreeType loader)
Former-commit-id: 1811304cd0efe9a86cbae83faaf4c39d9fae248f
This commit is contained in:
parent
4de17fdffb
commit
8a836b2060
|
|
@ -218,6 +218,27 @@ enum nzPrimitiveMode
|
||||||
nzPrimitiveMode_Max = nzPrimitiveMode_TriangleFan
|
nzPrimitiveMode_Max = nzPrimitiveMode_TriangleFan
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum nzTextAlign
|
||||||
|
{
|
||||||
|
nzTextAlign_Left,
|
||||||
|
nzTextAlign_Middle,
|
||||||
|
nzTextAlign_Right,
|
||||||
|
|
||||||
|
nzTextAlign_Max = nzTextAlign_Right
|
||||||
|
};
|
||||||
|
|
||||||
|
enum nzTextStyleFlags
|
||||||
|
{
|
||||||
|
nzTextStyle_None = 0x0,
|
||||||
|
|
||||||
|
nzTextStyle_Bold = 0x1,
|
||||||
|
nzTextStyle_Italic = 0x2,
|
||||||
|
nzTextStyle_StrikeThrough = 0x4,
|
||||||
|
nzTextStyle_Underlined = 0x8,
|
||||||
|
|
||||||
|
nzTextStyle_Max = nzTextStyle_Underlined*2-1
|
||||||
|
};
|
||||||
|
|
||||||
enum nzVertexComponent
|
enum nzVertexComponent
|
||||||
{
|
{
|
||||||
nzVertexComponent_Unused = -1,
|
nzVertexComponent_Unused = -1,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright (C) 2014 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_FONT_HPP
|
||||||
|
#define NAZARA_FONT_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
#include <Nazara/Core/GuillotineBinPack.hpp>
|
||||||
|
#include <Nazara/Core/NonCopyable.hpp>
|
||||||
|
#include <Nazara/Core/ResourceRef.hpp>
|
||||||
|
#include <Nazara/Core/ResourceLoader.hpp>
|
||||||
|
#include <Nazara/Utility/Image.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
struct NAZARA_API NzFontParams
|
||||||
|
{
|
||||||
|
bool IsValid() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NzFont;
|
||||||
|
class NzFontData;
|
||||||
|
|
||||||
|
struct NzFontGlyph; // TEMP
|
||||||
|
|
||||||
|
using NzFontConstRef = NzResourceRef<const NzFont>;
|
||||||
|
using NzFontLoader = NzResourceLoader<NzFont, NzFontParams>;
|
||||||
|
using NzFontRef = NzResourceRef<NzFont>;
|
||||||
|
|
||||||
|
class NAZARA_API NzFont : public NzResource, NzNonCopyable
|
||||||
|
{
|
||||||
|
friend NzFontLoader;
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Atlas
|
||||||
|
{
|
||||||
|
NzGuillotineBinPack binPack;
|
||||||
|
NzImage image;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Glyph
|
||||||
|
{
|
||||||
|
NzRecti aabb;
|
||||||
|
NzRectui atlasRect;
|
||||||
|
bool flipped;
|
||||||
|
bool valid;
|
||||||
|
int advance;
|
||||||
|
unsigned int atlasIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SizeInfo
|
||||||
|
{
|
||||||
|
unsigned int lineHeight;
|
||||||
|
float underlinePosition;
|
||||||
|
float underlineThickness;
|
||||||
|
};
|
||||||
|
|
||||||
|
NzFont();
|
||||||
|
NzFont(NzFont&& font) = default;
|
||||||
|
~NzFont();
|
||||||
|
|
||||||
|
void ClearGlyphCache();
|
||||||
|
void ClearKerningCache();
|
||||||
|
void ClearSizeInfoCache();
|
||||||
|
|
||||||
|
bool Create(NzFontData* data);
|
||||||
|
void Destroy();
|
||||||
|
|
||||||
|
bool ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt32 style, NzFontGlyph* glyph) const;
|
||||||
|
|
||||||
|
const Atlas& GetAtlas(unsigned int atlasIndex) const;
|
||||||
|
unsigned int GetAtlasCount() const;
|
||||||
|
unsigned int GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const;
|
||||||
|
unsigned int GetCachedGlyphCount() const;
|
||||||
|
NzString GetFamilyName() const;
|
||||||
|
int GetKerning(unsigned int characterSize, char32_t first, char32_t second) const;
|
||||||
|
const Glyph& GetGlyph(unsigned int characterSize, nzUInt32 style, char32_t character) const;
|
||||||
|
unsigned int GetMaxAtlasSize() const;
|
||||||
|
unsigned int GetMinimumStepSize() const;
|
||||||
|
const SizeInfo& GetSizeInfo(unsigned int characterSize) const;
|
||||||
|
NzString GetStyleName() const;
|
||||||
|
|
||||||
|
bool IsValid() const;
|
||||||
|
|
||||||
|
bool Precache(unsigned int characterSize, nzUInt32 style, char32_t character) const;
|
||||||
|
bool Precache(unsigned int characterSize, nzUInt32 style, const NzString& characterSet) const;
|
||||||
|
|
||||||
|
// Open
|
||||||
|
bool OpenFromFile(const NzString& filePath, const NzFontParams& params = NzFontParams());
|
||||||
|
bool OpenFromMemory(const void* data, std::size_t size, const NzFontParams& params = NzFontParams());
|
||||||
|
bool OpenFromStream(NzInputStream& stream, const NzFontParams& params = NzFontParams());
|
||||||
|
|
||||||
|
void SetMaxAtlasSize(unsigned int maxAtlasSize);
|
||||||
|
void SetMinimumStepSize(unsigned int minimumSizeStep);
|
||||||
|
|
||||||
|
NzFont& operator=(NzFont&& font) = default;
|
||||||
|
|
||||||
|
static unsigned int GetDefaultMaxAtlasSize();
|
||||||
|
static void SetDefaultMaxAtlasSize(unsigned int maxAtlasSize);
|
||||||
|
|
||||||
|
private:
|
||||||
|
using GlyphMap = std::unordered_map<char32_t, Glyph>;
|
||||||
|
|
||||||
|
struct QueuedGlyph
|
||||||
|
{
|
||||||
|
char32_t codepoint;
|
||||||
|
NzImage image;
|
||||||
|
GlyphMap* map;
|
||||||
|
};
|
||||||
|
|
||||||
|
nzUInt64 ComputeKey(unsigned int characterSize, nzUInt32 style) const;
|
||||||
|
unsigned int GetRealMaxAtlasSize() const;
|
||||||
|
unsigned int InsertRect(NzRectui* rect, bool* flipped) const;
|
||||||
|
const Glyph& PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const;
|
||||||
|
void ProcessGlyphQueue() const;
|
||||||
|
|
||||||
|
std::unique_ptr<NzFontData> m_data;
|
||||||
|
mutable std::unordered_map<nzUInt64, std::unordered_map<nzUInt64, int>> m_kerningCache;
|
||||||
|
mutable std::unordered_map<nzUInt64, GlyphMap> m_glyphes;
|
||||||
|
mutable std::unordered_map<nzUInt64, SizeInfo> m_sizeInfoCache;
|
||||||
|
mutable std::vector<Atlas> m_atlases;
|
||||||
|
mutable std::vector<QueuedGlyph> m_glyphQueue;
|
||||||
|
unsigned int m_maxAtlasSize;
|
||||||
|
unsigned int m_minimumSizeStep;
|
||||||
|
|
||||||
|
static NzFontLoader::LoaderList s_loaders;
|
||||||
|
static unsigned int s_maxAtlasSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_FONT_HPP
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright (C) 2014 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_FONTDATA_HPP
|
||||||
|
#define NAZARA_FONTDATA_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
#include <Nazara/Core/String.hpp>
|
||||||
|
|
||||||
|
struct NzFontGlyph;
|
||||||
|
|
||||||
|
class NAZARA_API NzFontData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
NzFontData() = default;
|
||||||
|
virtual ~NzFontData();
|
||||||
|
|
||||||
|
virtual bool ExtractGlyph(unsigned int characterSize, char32_t character, bool bold, NzFontGlyph* dst) = 0;
|
||||||
|
|
||||||
|
virtual NzString GetFamilyName() const = 0;
|
||||||
|
virtual NzString GetStyleName() const = 0;
|
||||||
|
|
||||||
|
virtual bool HasKerning() const = 0;
|
||||||
|
|
||||||
|
virtual bool IsScalable() const = 0;
|
||||||
|
|
||||||
|
virtual int QueryKerning(unsigned int characterSize, char32_t first, char32_t second) const = 0;
|
||||||
|
virtual unsigned int QueryLineHeight(unsigned int characterSize) const = 0;
|
||||||
|
virtual float QueryUnderlinePosition(unsigned int characterSize) const = 0;
|
||||||
|
virtual float QueryUnderlineThickness(unsigned int characterSize) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_FONTDATA_HPP
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
// Copyright (C) 2014 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_FONTGLYPH_HPP
|
||||||
|
#define NAZARA_FONTGLYPH_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Utility/Image.hpp>
|
||||||
|
|
||||||
|
struct NzFontGlyph
|
||||||
|
{
|
||||||
|
NzImage image;
|
||||||
|
NzRecti aabb;
|
||||||
|
int advance;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NAZARA_FONTGLYPH_HPP
|
||||||
|
|
@ -0,0 +1,507 @@
|
||||||
|
// Copyright (C) 2014 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/Debug.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const unsigned int s_atlasStartSize = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFontParams::IsValid() const
|
||||||
|
{
|
||||||
|
return true; // Rien à tester
|
||||||
|
}
|
||||||
|
|
||||||
|
NzFont::NzFont() :
|
||||||
|
m_maxAtlasSize(0),
|
||||||
|
m_minimumSizeStep(1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
NzFont::~NzFont()
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::ClearGlyphCache()
|
||||||
|
{
|
||||||
|
// Destruction des atlas et glyphes mémorisés
|
||||||
|
m_atlases.clear();
|
||||||
|
m_glyphes.clear();
|
||||||
|
m_glyphQueue.clear();
|
||||||
|
|
||||||
|
// Création du premier atlas
|
||||||
|
m_atlases.resize(1);
|
||||||
|
Atlas& atlas = m_atlases.back();
|
||||||
|
atlas.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::ClearKerningCache()
|
||||||
|
{
|
||||||
|
m_kerningCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::ClearSizeInfoCache()
|
||||||
|
{
|
||||||
|
m_sizeInfoCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::Create(NzFontData* data)
|
||||||
|
{
|
||||||
|
Destroy();
|
||||||
|
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (!data)
|
||||||
|
{
|
||||||
|
NazaraError("Invalid font data");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_data.reset(data);
|
||||||
|
|
||||||
|
ClearGlyphCache(); // Création du premier atlas en mémoire
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::Destroy()
|
||||||
|
{
|
||||||
|
m_atlases.clear();
|
||||||
|
m_data.reset();
|
||||||
|
m_kerningCache.clear();
|
||||||
|
m_glyphes.clear();
|
||||||
|
m_glyphQueue.clear();
|
||||||
|
m_sizeInfoCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::ExtractGlyph(unsigned int characterSize, char32_t character, nzUInt32 style, NzFontGlyph* glyph) const
|
||||||
|
{
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
NazaraError("Invalid font");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return m_data->ExtractGlyph(characterSize, character, style & nzTextStyle_Bold, glyph);
|
||||||
|
}
|
||||||
|
|
||||||
|
const NzFont::Atlas& NzFont::GetAtlas(unsigned int atlasIndex) const
|
||||||
|
{
|
||||||
|
if (!m_glyphQueue.empty())
|
||||||
|
ProcessGlyphQueue();
|
||||||
|
|
||||||
|
return m_atlases.at(atlasIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetAtlasCount() const
|
||||||
|
{
|
||||||
|
return m_atlases.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetCachedGlyphCount(unsigned int characterSize, nzUInt32 style) const
|
||||||
|
{
|
||||||
|
nzUInt64 key = ComputeKey(characterSize, style);
|
||||||
|
auto it = m_glyphes.find(key);
|
||||||
|
if (it == m_glyphes.end())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return it->second.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetCachedGlyphCount() const
|
||||||
|
{
|
||||||
|
unsigned int count = 0;
|
||||||
|
for (auto& pair : m_glyphes)
|
||||||
|
count += pair.second.size();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzString NzFont::GetFamilyName() const
|
||||||
|
{
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
NazaraError("Invalid font");
|
||||||
|
return NzString("Invalid font");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return m_data->GetFamilyName();
|
||||||
|
}
|
||||||
|
|
||||||
|
int NzFont::GetKerning(unsigned int characterSize, char32_t first, char32_t second) const
|
||||||
|
{
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
NazaraError("Invalid font");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// On utilise un cache car la méthode interne QueryKerning peut se révéler coûteuse (car pouvant induire un changement de taille)
|
||||||
|
auto& map = m_kerningCache[characterSize];
|
||||||
|
|
||||||
|
nzUInt64 key = (static_cast<nzUInt64>(first) << 32) | second; // Combinaison de deux caractères 32 bits dans un nombre 64 bits
|
||||||
|
|
||||||
|
auto it = map.find(key);
|
||||||
|
if (it == map.end())
|
||||||
|
{
|
||||||
|
// Absent du cache: on va demander l'information à la police
|
||||||
|
int kerning = m_data->QueryKerning(characterSize, first, second);
|
||||||
|
map.insert(std::make_pair(key, kerning));
|
||||||
|
|
||||||
|
return kerning;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return it->second; // Présent dans le cache, tout va bien
|
||||||
|
}
|
||||||
|
|
||||||
|
const NzFont::Glyph& NzFont::GetGlyph(unsigned int characterSize, nzUInt32 style, char32_t character) const
|
||||||
|
{
|
||||||
|
nzUInt64 key = ComputeKey(characterSize, style);
|
||||||
|
return PrecacheGlyph(m_glyphes[key], characterSize, style, character);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetMaxAtlasSize() const
|
||||||
|
{
|
||||||
|
return m_maxAtlasSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetMinimumStepSize() const
|
||||||
|
{
|
||||||
|
return m_minimumSizeStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NzFont::SizeInfo& NzFont::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);
|
||||||
|
|
||||||
|
it = m_sizeInfoCache.insert(std::make_pair(characterSize, sizeInfo)).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzString NzFont::GetStyleName() const
|
||||||
|
{
|
||||||
|
#if NAZARA_UTILITY_SAFE
|
||||||
|
if (!IsValid())
|
||||||
|
{
|
||||||
|
NazaraError("Invalid font");
|
||||||
|
return NzString("Invalid font");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return m_data->GetStyleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::IsValid() const
|
||||||
|
{
|
||||||
|
return m_data != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::Precache(unsigned int characterSize, nzUInt32 style, char32_t character) const
|
||||||
|
{
|
||||||
|
nzUInt64 key = ComputeKey(characterSize, style);
|
||||||
|
return PrecacheGlyph(m_glyphes[key], characterSize, style, character).valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::Precache(unsigned int characterSize, nzUInt32 style, const NzString& characterSet) const
|
||||||
|
{
|
||||||
|
unsigned int size;
|
||||||
|
std::unique_ptr<char32_t[]> characters(characterSet.GetUtf32Buffer(&size));
|
||||||
|
if (!characters)
|
||||||
|
{
|
||||||
|
NazaraError("Invalid character set");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nzUInt64 key = ComputeKey(characterSize, style);
|
||||||
|
auto& glyphMap = m_glyphes[key];
|
||||||
|
for (unsigned int i = 0; i < size; ++i)
|
||||||
|
PrecacheGlyph(glyphMap, characterSize, style, characters[i]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::OpenFromFile(const NzString& filePath, const NzFontParams& params)
|
||||||
|
{
|
||||||
|
return NzFontLoader::LoadFromFile(this, filePath, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::OpenFromMemory(const void* data, std::size_t size, const NzFontParams& params)
|
||||||
|
{
|
||||||
|
return NzFontLoader::LoadFromMemory(this, data, size, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NzFont::OpenFromStream(NzInputStream& stream, const NzFontParams& params)
|
||||||
|
{
|
||||||
|
return NzFontLoader::LoadFromStream(this, stream, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::SetMaxAtlasSize(unsigned int maxAtlasSize)
|
||||||
|
{
|
||||||
|
unsigned int oldMaxAtlasSize = GetRealMaxAtlasSize();
|
||||||
|
m_maxAtlasSize = maxAtlasSize;
|
||||||
|
|
||||||
|
// Si l'un de nos atlas dépasse la nouvelle taille, on doit vider le cache
|
||||||
|
maxAtlasSize = GetRealMaxAtlasSize();
|
||||||
|
if (maxAtlasSize < oldMaxAtlasSize)
|
||||||
|
{
|
||||||
|
for (Atlas& atlas : m_atlases)
|
||||||
|
{
|
||||||
|
unsigned int atlasSize = atlas.binPack.GetWidth();
|
||||||
|
if (atlasSize > maxAtlasSize)
|
||||||
|
{
|
||||||
|
NazaraWarning("At least one atlas was over new max atlas size (" + NzString::Number(atlasSize) + " > " + NzString::Number(maxAtlasSize) + "), clearing glyph cache...");
|
||||||
|
ClearGlyphCache();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::SetMinimumStepSize(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
|
||||||
|
|
||||||
|
m_minimumSizeStep = minimumStepSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetDefaultMaxAtlasSize()
|
||||||
|
{
|
||||||
|
return s_maxAtlasSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::SetDefaultMaxAtlasSize(unsigned int maxAtlasSize)
|
||||||
|
{
|
||||||
|
s_maxAtlasSize = maxAtlasSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
nzUInt64 NzFont::ComputeKey(unsigned int characterSize, nzUInt32 style) const
|
||||||
|
{
|
||||||
|
nzUInt64 sizePart = static_cast<nzUInt32>((characterSize/m_minimumSizeStep)*m_minimumSizeStep);
|
||||||
|
nzUInt64 stylePart = 0;
|
||||||
|
|
||||||
|
if (style & nzTextStyle_Bold) // Les caractères gras sont générés différemment
|
||||||
|
stylePart |= nzTextStyle_Bold;
|
||||||
|
|
||||||
|
// Les caractères italiques peuvent venir d'une autre police, dans le cas contraire ils sont générés au runtime
|
||||||
|
//if (style & nzTextStyle_Italic)
|
||||||
|
// stylePart |= nzTextStyle_Italic;
|
||||||
|
|
||||||
|
return (stylePart << 32) | sizePart;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::GetRealMaxAtlasSize() const
|
||||||
|
{
|
||||||
|
unsigned int maxAtlasSize = (m_maxAtlasSize == 0) ? s_maxAtlasSize : m_maxAtlasSize;
|
||||||
|
if (maxAtlasSize == 0)
|
||||||
|
maxAtlasSize = std::numeric_limits<unsigned int>::max();
|
||||||
|
|
||||||
|
return maxAtlasSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int NzFont::InsertRect(NzRectui* rect, bool* flipped) const
|
||||||
|
{
|
||||||
|
///DOC: Tous les pointeurs doivent être valides
|
||||||
|
// Précondition: Un rectangle ne peut pas être plus grand dans une dimension que la taille maximale de l'atlas
|
||||||
|
|
||||||
|
unsigned int maxAtlasSize = GetRealMaxAtlasSize();
|
||||||
|
|
||||||
|
// Cette fonction ne fait qu'insérer un rectangle de façon virtuelle, l'insertion des images se fait après
|
||||||
|
for (unsigned int i = 0; i < m_atlases.size(); ++i)
|
||||||
|
{
|
||||||
|
Atlas& atlas = m_atlases[i];
|
||||||
|
if (atlas.binPack.Insert(rect, flipped, 1, false, NzGuillotineBinPack::RectBestAreaFit, NzGuillotineBinPack::SplitMinimizeArea))
|
||||||
|
// Insertion réussie dans l'un des atlas, pas de question à se poser
|
||||||
|
return i;
|
||||||
|
else if (i == m_atlases.size() - 1) // Dernière itération ?
|
||||||
|
{
|
||||||
|
// Dernier atlas, et le glyphe ne rentre pas, peut-on agrandir la taille de l'atlas ?
|
||||||
|
unsigned int size = atlas.binPack.GetWidth(); // l'atlas étant carré, on ne teste qu'une dimension
|
||||||
|
if (size < maxAtlasSize)
|
||||||
|
{
|
||||||
|
// On peut encore agrandir l'atlas
|
||||||
|
size = std::min(size*2, maxAtlasSize);
|
||||||
|
atlas.binPack.Expand(size, size);
|
||||||
|
|
||||||
|
// On relance la boucle sur le dernier atlas
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// On ne peut plus agrandir le dernier atlas, il est temps d'en créer un nouveau
|
||||||
|
m_atlases.resize(m_atlases.size() + 1);
|
||||||
|
Atlas& newAtlas = m_atlases.back();
|
||||||
|
|
||||||
|
newAtlas.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
|
||||||
|
|
||||||
|
// On laisse la boucle insérer toute seule le rectangle à la prochaine itération
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si nous arrivons ici, c'est qu'une erreur a eu lieu en amont
|
||||||
|
NazaraInternalError("This shouldn't happen");
|
||||||
|
return std::numeric_limits<unsigned int>::max();
|
||||||
|
}
|
||||||
|
|
||||||
|
const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, bool bold, char32_t character) const
|
||||||
|
{
|
||||||
|
auto it = glyphMap.find(character);
|
||||||
|
if (it != glyphMap.end()) // Si le glyphe n'est pas déjà chargé
|
||||||
|
return it->second;
|
||||||
|
|
||||||
|
Glyph glyph;
|
||||||
|
glyph.valid = false;
|
||||||
|
|
||||||
|
// On extrait le glyphe depuis la police
|
||||||
|
NzFontGlyph fontGlyph;
|
||||||
|
if (ExtractGlyph(characterSize, character, bold, &fontGlyph))
|
||||||
|
{
|
||||||
|
glyph.atlasRect.width = fontGlyph.image.GetWidth();
|
||||||
|
glyph.atlasRect.height = fontGlyph.image.GetHeight();
|
||||||
|
|
||||||
|
unsigned int maxAtlasSize = GetRealMaxAtlasSize();
|
||||||
|
if (glyph.atlasRect.width <= maxAtlasSize && glyph.atlasRect.height <= maxAtlasSize)
|
||||||
|
{
|
||||||
|
// Insertion du rectangle dans l'un des atlas
|
||||||
|
glyph.aabb = fontGlyph.aabb;
|
||||||
|
glyph.advance = fontGlyph.advance;
|
||||||
|
glyph.valid = true;
|
||||||
|
|
||||||
|
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0) // Si l'image contient quelque chose
|
||||||
|
{
|
||||||
|
// Padding (pour éviter le débordement lors du filtrage)
|
||||||
|
const unsigned int padding = 1; // Un pixel entre chaque glyphe
|
||||||
|
|
||||||
|
glyph.atlasRect.width += padding;
|
||||||
|
glyph.atlasRect.height += padding;
|
||||||
|
|
||||||
|
// Insertion du rectangle dans l'atlas virtuel
|
||||||
|
glyph.atlasIndex = InsertRect(&glyph.atlasRect, &glyph.flipped);
|
||||||
|
|
||||||
|
glyph.atlasRect.width -= padding;
|
||||||
|
glyph.atlasRect.height -= padding;
|
||||||
|
|
||||||
|
// Mise en queue pour insertion dans l'atlas réel
|
||||||
|
m_glyphQueue.resize(m_glyphQueue.size()+1);
|
||||||
|
QueuedGlyph& queuedGlyph = m_glyphQueue.back();
|
||||||
|
queuedGlyph.codepoint = character;
|
||||||
|
queuedGlyph.image = std::move(fontGlyph.image);
|
||||||
|
queuedGlyph.map = &glyphMap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NazaraWarning("Glyph \"" + NzString::Unicode(character) + "\" is bigger than max atlas size");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NazaraWarning("Failed to extract glyph \"" + NzString::Unicode(character) + "\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
return glyphMap.insert(std::make_pair(character, std::move(glyph))).first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzFont::ProcessGlyphQueue() const
|
||||||
|
{
|
||||||
|
for (QueuedGlyph& queuedGlyph : m_glyphQueue)
|
||||||
|
{
|
||||||
|
GlyphMap& glyphMap = *queuedGlyph.map;
|
||||||
|
auto glyphIt = glyphMap.find(queuedGlyph.codepoint);
|
||||||
|
if (glyphIt == glyphMap.end())
|
||||||
|
continue; // Le glyphe a certainement été supprimé du cache avant la mise à jour de l'atlas
|
||||||
|
|
||||||
|
Glyph& glyph = glyphIt->second;
|
||||||
|
Atlas& atlas = m_atlases[glyph.atlasIndex];
|
||||||
|
|
||||||
|
// On s'assure que l'atlas est de la bonne taille
|
||||||
|
NzVector2ui atlasSize(atlas.image.GetWidth(), atlas.image.GetHeight());
|
||||||
|
NzVector2ui binPackSize = atlas.binPack.GetSize();
|
||||||
|
if (atlasSize != binPackSize)
|
||||||
|
{
|
||||||
|
// Création d'une nouvelle image
|
||||||
|
NzImage newAtlas(nzImageType_2D, nzPixelFormat_A8, binPackSize.x, binPackSize.y);
|
||||||
|
if (atlas.image.IsValid())
|
||||||
|
{
|
||||||
|
newAtlas.Copy(atlas.image, NzRectui(atlasSize), NzVector2ui(0, 0)); // On copie les anciennes données
|
||||||
|
|
||||||
|
// On initialise les nouvelles régions
|
||||||
|
newAtlas.Fill(NzColor(255, 255, 255, 0), NzRectui(0, atlasSize.y, binPackSize.x, binPackSize.y - atlasSize.y));
|
||||||
|
newAtlas.Fill(NzColor(255, 255, 255, 0), NzRectui(atlasSize.x, 0, binPackSize.x - atlasSize.x, atlasSize.y));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
newAtlas.Fill(NzColor(255, 255, 255, 0)); // On initialise les pixels
|
||||||
|
|
||||||
|
atlas.image = std::move(newAtlas); // On déplace la nouvelle image vers l'atlas
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int glyphWidth = queuedGlyph.image.GetWidth();
|
||||||
|
unsigned int glyphHeight = queuedGlyph.image.GetHeight();
|
||||||
|
|
||||||
|
// On copie le glyphe dans l'atlas
|
||||||
|
if (glyph.flipped)
|
||||||
|
{
|
||||||
|
// On tourne le glyphe pour qu'il rentre dans le rectangle
|
||||||
|
const nzUInt8* src = queuedGlyph.image.GetConstPixels();
|
||||||
|
nzUInt8* ptr = atlas.image.GetPixels(glyph.atlasRect.x, glyph.atlasRect.y + glyphWidth - 1);
|
||||||
|
|
||||||
|
unsigned int lineStride = atlas.image.GetWidth(); // BPP = 1
|
||||||
|
for (unsigned int y = 0; y < glyphHeight; ++y)
|
||||||
|
{
|
||||||
|
for (unsigned int x = 0; x < glyphWidth; ++x)
|
||||||
|
{
|
||||||
|
*ptr = *src++; // On copie et on avance dans le glyphe
|
||||||
|
ptr -= lineStride; // On remonte d'une ligne
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += lineStride*glyphWidth + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
atlas.image.Copy(queuedGlyph.image, NzRectui(glyphWidth, glyphHeight), glyph.atlasRect.GetPosition());
|
||||||
|
|
||||||
|
queuedGlyph.image.Destroy(); // On libère l'image dès que possible (pour réduire la consommation)
|
||||||
|
}
|
||||||
|
|
||||||
|
m_glyphQueue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
NzFontLoader::LoaderList NzFont::s_loaders;
|
||||||
|
unsigned int NzFont::s_maxAtlasSize = 8192; // Valeur totalement arbitraire
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
// Copyright (C) 2014 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/FontData.hpp>
|
||||||
|
#include <Nazara/Utility/Debug.hpp>
|
||||||
|
|
||||||
|
NzFontData::~NzFontData() = default;
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright (C) 2014 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
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef NAZARA_LOADERS_FREETYPE_HPP
|
||||||
|
#define NAZARA_LOADERS_FREETYPE_HPP
|
||||||
|
|
||||||
|
#include <Nazara/Prerequesites.hpp>
|
||||||
|
|
||||||
|
void NzLoaders_FreeType_Register();
|
||||||
|
void NzLoaders_FreeType_Unregister();
|
||||||
|
|
||||||
|
#endif // NAZARA_LOADERS_FREETYPE_HPP
|
||||||
|
|
@ -0,0 +1,393 @@
|
||||||
|
// Copyright (C) 2014 Jérôme Leclercq - 2009 Cruden BV
|
||||||
|
// 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/Loaders/FreeType.hpp>
|
||||||
|
#include <freetype/ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
#include FT_BITMAP_H
|
||||||
|
#include FT_OUTLINE_H
|
||||||
|
#include <Nazara/Core/Error.hpp>
|
||||||
|
#include <Nazara/Core/InputStream.hpp>
|
||||||
|
#include <Nazara/Utility/Font.hpp>
|
||||||
|
#include <Nazara/Utility/FontData.hpp>
|
||||||
|
#include <Nazara/Utility/FontGlyph.hpp>
|
||||||
|
#include <memory>
|
||||||
|
#include <set>
|
||||||
|
#include <Nazara/Utility/Debug.hpp>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
FT_Library s_library = nullptr;
|
||||||
|
float s_invScaleFactor = 1.f / (1 << 6); // 1/64
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
unsigned long FT_StreamRead(FT_Stream stream, unsigned long offset, unsigned char* buffer, unsigned long count)
|
||||||
|
{
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_IoFunc
|
||||||
|
NzInputStream& inputStream = *static_cast<NzInputStream*>(stream->descriptor.pointer);
|
||||||
|
|
||||||
|
// La valeur de count indique une opération de lecture ou de positionnement
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
// Dans le premier cas, une erreur est symbolisée par un retour nul
|
||||||
|
if (inputStream.SetCursorPos(offset))
|
||||||
|
return static_cast<unsigned long>(inputStream.Read(buffer, count));
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Dans le second cas, une erreur est symbolisée par un retour non-nul
|
||||||
|
if (inputStream.SetCursorPos(offset))
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return 42; // La réponse à la grande question
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
void FT_StreamClose(FT_Stream stream)
|
||||||
|
{
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-system_interface.html#FT_Stream_CloseFunc
|
||||||
|
// Les streams dans Nazara ne se ferment pas explicitement
|
||||||
|
NazaraUnused(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FreeTypeStream : public NzFontData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
FreeTypeStream() :
|
||||||
|
m_face(nullptr),
|
||||||
|
m_characterSize(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
~FreeTypeStream()
|
||||||
|
{
|
||||||
|
if (m_face)
|
||||||
|
FT_Done_Face(m_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Check()
|
||||||
|
{
|
||||||
|
// Test d'ouverture (http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Open_Face)
|
||||||
|
return FT_Open_Face(s_library, &m_args, -1, nullptr) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExtractGlyph(unsigned int characterSize, char32_t character, bool bold, NzFontGlyph* dst)
|
||||||
|
{
|
||||||
|
#ifdef NAZARA_DEBUG
|
||||||
|
if (!dst)
|
||||||
|
{
|
||||||
|
NazaraError("Glyph destination cannot be null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
SetCharacterSize(characterSize);
|
||||||
|
|
||||||
|
if (FT_Load_Char(m_face, character, FT_LOAD_FORCE_AUTOHINT | FT_LOAD_TARGET_NORMAL) != 0)
|
||||||
|
{
|
||||||
|
NazaraError("Failed to load character");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_GlyphSlot& glyph = m_face->glyph;
|
||||||
|
|
||||||
|
const FT_Pos boldStrength = 1 << 6;
|
||||||
|
|
||||||
|
bool outlineFormat = (glyph->format == FT_GLYPH_FORMAT_OUTLINE);
|
||||||
|
if (outlineFormat && bold)
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden
|
||||||
|
FT_Outline_Embolden(&glyph->outline, boldStrength);
|
||||||
|
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-glyph_management.html#FT_Glyph_To_Bitmap
|
||||||
|
// Conversion du glyphe vers le format bitmap
|
||||||
|
// Cette fonction ne fait rien dans le cas où le glyphe est déjà un bitmap
|
||||||
|
if (FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL) != 0)
|
||||||
|
{
|
||||||
|
NazaraError("Failed to convert glyph to bitmap");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dans le cas où nous voulons des caractères gras mais que nous n'avons pas pu agir plus tôt
|
||||||
|
// nous demandons à FreeType d'agir directement sur le bitmap généré
|
||||||
|
if (!outlineFormat && bold)
|
||||||
|
{
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-bitmap_handling.html#FT_Bitmap_Embolden
|
||||||
|
// "If you want to embolden the bitmap owned by a FT_GlyphSlot_Rec, you should call FT_GlyphSlot_Own_Bitmap on the slot first"
|
||||||
|
FT_GlyphSlot_Own_Bitmap(glyph);
|
||||||
|
FT_Bitmap_Embolden(s_library, &glyph->bitmap, boldStrength, boldStrength);
|
||||||
|
}
|
||||||
|
|
||||||
|
dst->advance = glyph->metrics.horiAdvance >> 6;
|
||||||
|
if (bold)
|
||||||
|
dst->advance += boldStrength >> 6;
|
||||||
|
|
||||||
|
dst->aabb.x = glyph->metrics.horiBearingX >> 6;
|
||||||
|
dst->aabb.y = -(glyph->metrics.horiBearingY >> 6); // Inversion du repère
|
||||||
|
dst->aabb.width = glyph->metrics.width >> 6;
|
||||||
|
dst->aabb.height = glyph->metrics.height >> 6;
|
||||||
|
|
||||||
|
unsigned int width = glyph->bitmap.width;
|
||||||
|
unsigned int height = glyph->bitmap.rows;
|
||||||
|
|
||||||
|
if (width > 0 && height > 0)
|
||||||
|
{
|
||||||
|
dst->image.Create(nzImageType_2D, nzPixelFormat_A8, width, height);
|
||||||
|
nzUInt8* pixels = dst->image.GetPixels();
|
||||||
|
|
||||||
|
const nzUInt8* data = glyph->bitmap.buffer;
|
||||||
|
|
||||||
|
// Selon la documentation FreeType, le glyphe peut être encodé en format A8 (huit bits d'alpha par pixel)
|
||||||
|
// ou au format A1 (un bit d'alpha par pixel).
|
||||||
|
// Cependant dans un cas comme dans l'autre, il nous faut gérer le pitch (les données peuvent ne pas être contigues)
|
||||||
|
// ainsi que le padding dans le cas du format A1 (Chaque ligne prends un nombre fixe d'octets)
|
||||||
|
if (glyph->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
|
||||||
|
{
|
||||||
|
// Format A1
|
||||||
|
for (unsigned int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
for (unsigned int x = 0; x < width; ++x)
|
||||||
|
*pixels++ = (data[x/8] & (1 << (7 - x%8)) ? 255 : 0);
|
||||||
|
|
||||||
|
data += glyph->bitmap.pitch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Format A8
|
||||||
|
if (glyph->bitmap.pitch == static_cast<int>(width*sizeof(nzUInt8))) // Pouvons-nous copier directement ?
|
||||||
|
dst->image.Update(glyph->bitmap.buffer);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (unsigned int y = 0; y < height; ++y)
|
||||||
|
{
|
||||||
|
std::memcpy(pixels, data, width*sizeof(nzUInt8));
|
||||||
|
data += glyph->bitmap.pitch;
|
||||||
|
pixels += width*sizeof(nzUInt8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dst->image.Destroy(); // On s'assure que l'image ne contient alors rien
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzString GetFamilyName() const
|
||||||
|
{
|
||||||
|
return m_face->family_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
NzString GetStyleName() const
|
||||||
|
{
|
||||||
|
return m_face->style_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HasKerning() const
|
||||||
|
{
|
||||||
|
return FT_HAS_KERNING(m_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsScalable() const
|
||||||
|
{
|
||||||
|
return FT_IS_SCALABLE(m_face);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Open()
|
||||||
|
{
|
||||||
|
return FT_Open_Face(s_library, &m_args, 0, &m_face) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int QueryKerning(unsigned int characterSize, char32_t first, char32_t second) const
|
||||||
|
{
|
||||||
|
if (FT_HAS_KERNING(m_face))
|
||||||
|
{
|
||||||
|
SetCharacterSize(characterSize);
|
||||||
|
|
||||||
|
FT_Vector kerning;
|
||||||
|
FT_Get_Kerning(m_face, FT_Get_Char_Index(m_face, first), FT_Get_Char_Index(m_face, second), FT_KERNING_DEFAULT, &kerning);
|
||||||
|
|
||||||
|
if (!FT_IS_SCALABLE(m_face))
|
||||||
|
return kerning.x; // Taille déjà précisée en pixels dans ce cas
|
||||||
|
|
||||||
|
return kerning.x >> 6;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int QueryLineHeight(unsigned int characterSize) const
|
||||||
|
{
|
||||||
|
SetCharacterSize(characterSize);
|
||||||
|
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_Size_Metrics
|
||||||
|
return m_face->size->metrics.height >> 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
float QueryUnderlinePosition(unsigned int characterSize) const
|
||||||
|
{
|
||||||
|
if (FT_IS_SCALABLE(m_face))
|
||||||
|
{
|
||||||
|
SetCharacterSize(characterSize);
|
||||||
|
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
|
||||||
|
return static_cast<float>(FT_MulFix(m_face->underline_position, m_face->size->metrics.y_scale)) * s_invScaleFactor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return characterSize / 10.f; // Joker ?
|
||||||
|
}
|
||||||
|
|
||||||
|
float QueryUnderlineThickness(unsigned int characterSize) const
|
||||||
|
{
|
||||||
|
if (FT_IS_SCALABLE(m_face))
|
||||||
|
{
|
||||||
|
SetCharacterSize(characterSize);
|
||||||
|
|
||||||
|
// http://www.freetype.org/freetype2/docs/reference/ft2-base_interface.html#FT_FaceRec
|
||||||
|
return static_cast<float>(FT_MulFix(m_face->underline_thickness, m_face->size->metrics.y_scale)) * s_invScaleFactor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return characterSize/15.f; // Joker ?
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SetFile(const NzString& filePath)
|
||||||
|
{
|
||||||
|
if (!m_file.Open(filePath, NzFile::ReadOnly))
|
||||||
|
{
|
||||||
|
NazaraError("Failed to open stream from file: " + NzError::GetLastError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStream(m_file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetStream(NzInputStream& stream)
|
||||||
|
{
|
||||||
|
m_stream.base = nullptr;
|
||||||
|
m_stream.close = FT_StreamClose;
|
||||||
|
m_stream.descriptor.pointer = &stream;
|
||||||
|
m_stream.read = FT_StreamRead;
|
||||||
|
m_stream.pos = 0;
|
||||||
|
m_stream.size = stream.GetSize();
|
||||||
|
|
||||||
|
m_args.driver = 0;
|
||||||
|
m_args.flags = FT_OPEN_STREAM;
|
||||||
|
m_args.stream = &m_stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void SetCharacterSize(unsigned int characterSize) const
|
||||||
|
{
|
||||||
|
if (m_characterSize != characterSize)
|
||||||
|
{
|
||||||
|
FT_Set_Pixel_Sizes(m_face, 0, characterSize);
|
||||||
|
m_characterSize = characterSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Open_Args m_args;
|
||||||
|
FT_Face m_face;
|
||||||
|
FT_StreamRec m_stream;
|
||||||
|
NzFile m_file;
|
||||||
|
mutable unsigned int m_characterSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool IsSupported(const NzString& extension)
|
||||||
|
{
|
||||||
|
///FIXME: Je suppose qu'il en manque quelques uns..
|
||||||
|
static std::set<NzString> supportedExtensions = {
|
||||||
|
"afm", "bdf", "cff", "cid", "dfont", "fnt", "pfa", "pfb", "pfm", "pfr", "sfnt", "tte", "ttf"
|
||||||
|
};
|
||||||
|
|
||||||
|
return supportedExtensions.find(extension) != supportedExtensions.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
nzTernary Check(NzInputStream& stream, const NzFontParams& parameters)
|
||||||
|
{
|
||||||
|
NazaraUnused(parameters);
|
||||||
|
|
||||||
|
FreeTypeStream face;
|
||||||
|
face.SetStream(stream);
|
||||||
|
|
||||||
|
if (face.Check())
|
||||||
|
return nzTernary_True;
|
||||||
|
else
|
||||||
|
return nzTernary_False;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadFile(NzFont* font, const NzString& filePath, const NzFontParams& parameters)
|
||||||
|
{
|
||||||
|
NazaraUnused(parameters);
|
||||||
|
|
||||||
|
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
|
||||||
|
|
||||||
|
if (!face->SetFile(filePath))
|
||||||
|
{
|
||||||
|
NazaraError("Failed to open file");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!face->Open())
|
||||||
|
{
|
||||||
|
NazaraError("Failed to open face");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font->Create(face.get()))
|
||||||
|
{
|
||||||
|
face.release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LoadStream(NzFont* font, NzInputStream& stream, const NzFontParams& parameters)
|
||||||
|
{
|
||||||
|
NazaraUnused(parameters);
|
||||||
|
|
||||||
|
std::unique_ptr<FreeTypeStream> face(new FreeTypeStream);
|
||||||
|
face->SetStream(stream);
|
||||||
|
|
||||||
|
if (!face->Open())
|
||||||
|
{
|
||||||
|
NazaraError("Failed to open face");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (font->Create(face.get()))
|
||||||
|
{
|
||||||
|
face.release();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzLoaders_FreeType_Register()
|
||||||
|
{
|
||||||
|
if (FT_Init_FreeType(&s_library) == 0)
|
||||||
|
NzFontLoader::RegisterLoader(IsSupported, Check, LoadStream, LoadFile);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_library = nullptr; // On s'assure que le pointeur ne pointe pas sur n'importe quoi
|
||||||
|
NazaraWarning("Failed to initialize FreeType library");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NzLoaders_FreeType_Unregister()
|
||||||
|
{
|
||||||
|
if (s_library)
|
||||||
|
{
|
||||||
|
NzFontLoader::UnregisterLoader(IsSupported, Check, LoadStream, LoadFile);
|
||||||
|
|
||||||
|
FT_Done_FreeType(s_library);
|
||||||
|
s_library = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <Nazara/Core/Thread.hpp>
|
#include <Nazara/Core/Thread.hpp>
|
||||||
#include <Nazara/Utility/Buffer.hpp>
|
#include <Nazara/Utility/Buffer.hpp>
|
||||||
#include <Nazara/Utility/Config.hpp>
|
#include <Nazara/Utility/Config.hpp>
|
||||||
|
#include <Nazara/Utility/Loaders/FreeType.hpp>
|
||||||
#include <Nazara/Utility/Loaders/MD2.hpp>
|
#include <Nazara/Utility/Loaders/MD2.hpp>
|
||||||
#include <Nazara/Utility/Loaders/MD5Anim.hpp>
|
#include <Nazara/Utility/Loaders/MD5Anim.hpp>
|
||||||
#include <Nazara/Utility/Loaders/MD5Mesh.hpp>
|
#include <Nazara/Utility/Loaders/MD5Mesh.hpp>
|
||||||
|
|
@ -70,6 +71,9 @@ bool NzUtility::Initialize()
|
||||||
// Il s'agit ici d'une liste LIFO, le dernier loader enregistré possède la priorité
|
// Il s'agit ici d'une liste LIFO, le dernier loader enregistré possède la priorité
|
||||||
|
|
||||||
/// Loaders génériques
|
/// Loaders génériques
|
||||||
|
// Font
|
||||||
|
NzLoaders_FreeType_Register();
|
||||||
|
|
||||||
// Image
|
// Image
|
||||||
NzLoaders_STB_Register(); // Loader générique (STB)
|
NzLoaders_STB_Register(); // Loader générique (STB)
|
||||||
|
|
||||||
|
|
@ -109,6 +113,7 @@ void NzUtility::Uninitialize()
|
||||||
// Libération du module
|
// Libération du module
|
||||||
s_moduleReferenceCounter = 0;
|
s_moduleReferenceCounter = 0;
|
||||||
|
|
||||||
|
NzLoaders_FreeType_Unregister();
|
||||||
NzLoaders_MD2_Unregister();
|
NzLoaders_MD2_Unregister();
|
||||||
NzLoaders_MD5Anim_Unregister();
|
NzLoaders_MD5Anim_Unregister();
|
||||||
NzLoaders_MD5Mesh_Unregister();
|
NzLoaders_MD5Mesh_Unregister();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue