Merge branch 'Font-Update'

Former-commit-id: d293f78c891a74554c6b990afafdc1eb1cc0a584
This commit is contained in:
Lynix
2015-01-17 00:36:28 +01:00
108 changed files with 4982 additions and 1081 deletions

View File

@@ -0,0 +1,89 @@
// Copyright (C) 2015 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/AbstractAtlas.hpp>
#include <Nazara/Utility/Debug.hpp>
NzAbstractAtlas::NzAbstractAtlas() :
m_listenersLocked(false)
{
}
NzAbstractAtlas::~NzAbstractAtlas()
{
m_listenersLocked = true;
for (auto& pair : m_listeners)
pair.first->OnAtlasReleased(this, pair.second);
}
void NzAbstractAtlas::AddListener(Listener* listener, void* userdata) const
{
if (!m_listenersLocked)
m_listeners.insert(std::make_pair(listener, userdata));
}
void NzAbstractAtlas::RemoveListener(Listener* listener) const
{
if (!m_listenersLocked)
m_listeners.erase(listener);
}
void NzAbstractAtlas::NotifyCleared()
{
m_listenersLocked = true;
auto it = m_listeners.begin();
while (it != m_listeners.end())
{
if (!it->first->OnAtlasCleared(this, it->second))
m_listeners.erase(it++);
else
++it;
}
m_listenersLocked = false;
}
void NzAbstractAtlas::NotifyLayerChange(NzAbstractImage* oldLayer, NzAbstractImage* newLayer)
{
m_listenersLocked = true;
auto it = m_listeners.begin();
while (it != m_listeners.end())
{
if (!it->first->OnAtlasLayerChange(this, oldLayer, newLayer, it->second))
m_listeners.erase(it++);
else
++it;
}
m_listenersLocked = false;
}
NzAbstractAtlas::Listener::~Listener() = default;
bool NzAbstractAtlas::Listener::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(userdata);
return true;
}
bool NzAbstractAtlas::Listener::OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(oldLayer);
NazaraUnused(newLayer);
NazaraUnused(userdata);
return true;
}
void NzAbstractAtlas::Listener::OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(userdata);
}

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2015 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/AbstractImage.hpp>
#include <Nazara/Utility/PixelFormat.hpp>
#include <Nazara/Utility/Debug.hpp>
NzAbstractImage::~NzAbstractImage() = default;
nzUInt8 NzAbstractImage::GetBytesPerPixel() const
{
return NzPixelFormat::GetBytesPerPixel(GetFormat());
}
bool NzAbstractImage::IsCompressed() const
{
return NzPixelFormat::IsCompressed(GetFormat());
}
bool NzAbstractImage::IsCubemap() const
{
return GetType() == nzImageType_Cubemap;
}

View File

@@ -0,0 +1,9 @@
// Copyright (C) 2015 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/AbstractTextDrawer.hpp>
#include <Nazara/Utility/Debug.hpp>
NzAbstractTextDrawer::~NzAbstractTextDrawer() = default;

View File

@@ -3,6 +3,7 @@
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Utility/Buffer.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Utility/AbstractBuffer.hpp>
@@ -16,7 +17,7 @@
namespace
{
NzAbstractBuffer* SoftwareBufferFunction(NzBuffer* parent, nzBufferType type)
NzAbstractBuffer* SoftwareBufferFactory(NzBuffer* parent, nzBufferType type)
{
return new NzSoftwareBuffer(parent, type);
}
@@ -29,7 +30,7 @@ m_size(0)
{
}
NzBuffer::NzBuffer(nzBufferType type, unsigned int size, nzBufferStorage storage, nzBufferUsage usage) :
NzBuffer::NzBuffer(nzBufferType type, unsigned int size, nzUInt32 storage, nzBufferUsage usage) :
m_type(type),
m_impl(nullptr)
{
@@ -59,22 +60,21 @@ bool NzBuffer::CopyContent(const NzBuffer& buffer)
#endif
NzBufferMapper<NzBuffer> mapper(buffer, nzBufferAccess_ReadOnly);
return Fill(mapper.GetPointer(), 0, buffer.GetSize());
}
bool NzBuffer::Create(unsigned int size, nzBufferStorage storage, nzBufferUsage usage)
bool NzBuffer::Create(unsigned int size, nzUInt32 storage, nzBufferUsage usage)
{
Destroy();
// Notre buffer est-il supporté ?
if (!s_bufferFunctions[storage])
if (!IsStorageSupported(storage))
{
NazaraError("Buffer storage not supported");
return false;
}
std::unique_ptr<NzAbstractBuffer> impl(s_bufferFunctions[storage](this, m_type));
std::unique_ptr<NzAbstractBuffer> impl(s_bufferFactories[storage](this, m_type));
if (!impl->Create(size, usage))
{
NazaraError("Failed to create buffer");
@@ -131,7 +131,7 @@ unsigned int NzBuffer::GetSize() const
return m_size;
}
nzBufferStorage NzBuffer::GetStorage() const
nzUInt32 NzBuffer::GetStorage() const
{
return m_storage;
}
@@ -148,7 +148,7 @@ nzBufferUsage NzBuffer::GetUsage() const
bool NzBuffer::IsHardware() const
{
return m_storage == nzBufferStorage_Hardware;
return m_storage & nzDataStorage_Hardware;
}
bool NzBuffer::IsValid() const
@@ -200,7 +200,7 @@ void* NzBuffer::Map(nzBufferAccess access, unsigned int offset, unsigned int siz
return m_impl->Map(access, offset, (size == 0) ? m_size-offset : size);
}
bool NzBuffer::SetStorage(nzBufferStorage storage)
bool NzBuffer::SetStorage(nzUInt32 storage)
{
#if NAZARA_UTILITY_SAFE
if (!m_impl)
@@ -213,13 +213,11 @@ bool NzBuffer::SetStorage(nzBufferStorage storage)
if (m_storage == storage)
return true;
#if NAZARA_UTILITY_SAFE
if (!IsSupported(storage))
if (!IsStorageSupported(storage))
{
NazaraError("Storage not supported");
return false;
}
#endif
void* ptr = m_impl->Map(nzBufferAccess_ReadOnly, 0, m_size);
if (!ptr)
@@ -228,31 +226,36 @@ bool NzBuffer::SetStorage(nzBufferStorage storage)
return false;
}
NzAbstractBuffer* impl = s_bufferFunctions[storage](this, m_type);
NzCallOnExit unmapMyImpl([this]()
{
m_impl->Unmap();
});
std::unique_ptr<NzAbstractBuffer> impl(s_bufferFactories[storage](this, m_type));
if (!impl->Create(m_size, m_usage))
{
NazaraError("Failed to create buffer");
delete impl;
m_impl->Unmap();
return false;
}
NzCallOnExit destroyImpl([&impl]()
{
impl->Destroy();
});
if (!impl->Fill(ptr, 0, m_size))
{
NazaraError("Failed to fill buffer");
impl->Destroy();
delete impl;
m_impl->Unmap();
return false;
}
m_impl->Unmap();
destroyImpl.Reset();
unmapMyImpl.CallAndReset();
m_impl->Destroy();
delete m_impl;
m_impl = impl;
m_impl = impl.release();
m_storage = storage;
return true;
@@ -269,29 +272,29 @@ void NzBuffer::Unmap() const
#endif
if (!m_impl->Unmap())
NazaraWarning("Failed to unmap buffer (it's content is undefined)"); ///TODO: Unexpected ?
NazaraWarning("Failed to unmap buffer (it's content may be undefined)"); ///TODO: Unexpected ?
}
bool NzBuffer::IsSupported(nzBufferStorage storage)
bool NzBuffer::IsStorageSupported(nzUInt32 storage)
{
return s_bufferFunctions[storage] != nullptr;
return s_bufferFactories[storage] != nullptr;
}
void NzBuffer::SetBufferFunction(nzBufferStorage storage, BufferFunction func)
void NzBuffer::SetBufferFactory(nzUInt32 storage, BufferFactory func)
{
s_bufferFunctions[storage] = func;
s_bufferFactories[storage] = func;
}
bool NzBuffer::Initialize()
{
s_bufferFunctions[nzBufferStorage_Software] = SoftwareBufferFunction;
s_bufferFactories[nzDataStorage_Software] = SoftwareBufferFactory;
return true;
}
void NzBuffer::Uninitialize()
{
std::memset(s_bufferFunctions, 0, (nzBufferStorage_Max+1)*sizeof(NzBuffer::BufferFunction));
std::memset(s_bufferFactories, 0, (nzDataStorage_Max+1)*sizeof(NzBuffer::BufferFactory));
}
NzBuffer::BufferFunction NzBuffer::s_bufferFunctions[nzBufferStorage_Max+1] = {0};
NzBuffer::BufferFactory NzBuffer::s_bufferFactories[nzDataStorage_Max+1] = {0};

484
src/Nazara/Utility/Font.cpp Normal file
View File

@@ -0,0 +1,484 @@
// 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>
bool NzFontParams::IsValid() const
{
return true; // Rien à tester
}
NzFont::NzFont() :
m_glyphBorder(1),
m_minimumSizeStep(1)
{
}
NzFont::~NzFont()
{
Destroy();
SetAtlas(nullptr); // On libère l'atlas par la même occasion
}
void NzFont::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();
NotifyModified(ModificationCode_GlyphCacheCleared);
}
}
}
void NzFont::ClearKerningCache()
{
m_kerningCache.clear();
NotifyModified(ModificationCode_KerningCacheCleared);
}
void NzFont::ClearSizeInfoCache()
{
m_sizeInfoCache.clear();
NotifyModified(ModificationCode_SizeInfoCacheCleared);
}
bool NzFont::Create(NzFontData* data)
{
Destroy();
#if NAZARA_UTILITY_SAFE
if (!data)
{
NazaraError("Invalid font data");
return false;
}
#endif
m_data.reset(data);
return true;
}
void NzFont::Destroy()
{
ClearGlyphCache();
m_data.reset();
m_kerningCache.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, glyph);
}
const NzAbstractAtlas* NzFont::GetAtlas() const
{
return m_atlas.get();
}
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::GetGlyphBorder() const
{
return m_glyphBorder;
}
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);
NzFontGlyph glyph;
if (m_data->ExtractGlyph(characterSize, ' ', nzTextStyle_Regular, &glyph))
sizeInfo.spaceAdvance = glyph.advance;
else
{
NazaraWarning("Failed to extract space character from font, using half the size");
sizeInfo.spaceAdvance = characterSize/2;
}
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::SetAtlas(std::shared_ptr<NzAbstractAtlas> atlas)
{
if (m_atlas != atlas)
{
ClearGlyphCache();
if (m_atlas)
m_atlas->RemoveListener(this);
m_atlas = atlas;
if (m_atlas)
m_atlas->AddListener(this);
NotifyModified(ModificationCode_AtlasChanged);
}
}
void NzFont::SetGlyphBorder(unsigned int borderSize)
{
if (m_glyphBorder != borderSize)
{
m_glyphBorder = borderSize;
ClearGlyphCache();
}
}
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
if (m_minimumSizeStep != minimumStepSize)
{
m_minimumSizeStep = minimumStepSize;
ClearGlyphCache();
}
}
nzUInt64 NzFont::ComputeKey(unsigned int characterSize, nzUInt32 style) const
{
// On prend le pas en compte
nzUInt64 sizePart = static_cast<nzUInt32>((characterSize/m_minimumSizeStep)*m_minimumSizeStep);
// Ainsi que le style (uniquement le gras et l'italique, les autres sont gérés par un TextDrawer)
nzUInt64 stylePart = 0;
if (style & nzTextStyle_Bold)
stylePart |= nzTextStyle_Bold;
if (style & nzTextStyle_Italic)
stylePart |= nzTextStyle_Italic;
return (stylePart << 32) | sizePart;
}
bool NzFont::OnAtlasCleared(const NzAbstractAtlas* atlas, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(userdata);
#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 false; // On ne veut plus être notifié par cette ressource, évidemment
}
#endif
// Notre atlas vient d'être vidé, détruisons le cache de glyphe
m_glyphes.clear();
NotifyModified(ModificationCode_GlyphCacheCleared);
return true;
}
bool NzFont::OnAtlasLayerChange(const NzAbstractAtlas* atlas, NzAbstractImage* oldLayer, NzAbstractImage* newLayer, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(oldLayer);
NazaraUnused(newLayer);
NazaraUnused(userdata);
#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 false; // On ne veut plus être notifié par cette ressource, évidemment
}
#endif
// Pour faciliter le travail des ressources qui nous écoutent
NotifyModified(ModificationCode_AtlasLayerChanged);
return true;
}
void NzFont::OnAtlasReleased(const NzAbstractAtlas* atlas, void* userdata)
{
NazaraUnused(atlas);
NazaraUnused(userdata);
#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 se balancer une erreur à la tête de l'utilisateur avant un potentiel crash...
NazaraError("Atlas has been released while in use");
}
const NzFont::Glyph& NzFont::PrecacheGlyph(GlyphMap& glyphMap, unsigned int characterSize, nzUInt32 style, 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 = glyphMap[character]; // Insertion du glyphe
glyph.requireFauxBold = false;
glyph.requireFauxItalic = false;
glyph.valid = false;
// On vérifie que le style demandé est supporté par la police (dans le cas contraire il devra être simulé au rendu)
nzUInt32 supportedStyle = style;
if (style & nzTextStyle_Bold && !m_data->SupportsStyle(nzTextStyle_Bold))
{
glyph.requireFauxBold = true;
supportedStyle &= ~nzTextStyle_Bold;
}
if (style & nzTextStyle_Italic && !m_data->SupportsStyle(nzTextStyle_Italic))
{
glyph.requireFauxItalic = true;
supportedStyle &= ~nzTextStyle_Italic;
}
// Est-ce que la police supporte le style demandé ?
if (style == supportedStyle)
{
// On extrait le glyphe depuis la police
NzFontGlyph fontGlyph;
if (ExtractGlyph(characterSize, character, style, &fontGlyph))
{
glyph.atlasRect.width = fontGlyph.image.GetWidth();
glyph.atlasRect.height = fontGlyph.image.GetHeight();
// Insertion du rectangle dans l'un des atlas
if (glyph.atlasRect.width > 0 && glyph.atlasRect.height > 0) // Si l'image contient quelque chose
{
// Bordure (pour éviter le débordement lors du filtrage)
glyph.atlasRect.width += m_glyphBorder*2;
glyph.atlasRect.height += m_glyphBorder*2;
// Insertion du rectangle dans l'atlas virtuel
if (!m_atlas->Insert(fontGlyph.image, &glyph.atlasRect, &glyph.flipped, &glyph.layerIndex))
{
NazaraError("Failed to insert glyph into atlas");
return glyph;
}
// Compensation de la bordure (centrage du glyphe)
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 \"" + NzString::Unicode(character) + "\"");
}
}
else
{
// La police ne supporte pas le style demandé, nous allons donc précharger le glyphe supportant le style "minimum" supporté
// et copier ses données
nzUInt64 newKey = ComputeKey(characterSize, supportedStyle);
const Glyph& referenceGlyph = PrecacheGlyph(m_glyphes[newKey], characterSize, supportedStyle, 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;
}
NzFontLoader::LoaderList NzFont::s_loaders;

View File

@@ -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;

View File

@@ -0,0 +1,260 @@
// Copyright (C) 2015 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/GuillotineImageAtlas.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/Debug.hpp>
namespace
{
const unsigned int s_atlasStartSize = 512;
}
NzGuillotineImageAtlas::NzGuillotineImageAtlas() :
m_rectChoiceHeuristic(NzGuillotineBinPack::RectBestAreaFit),
m_rectSplitHeuristic(NzGuillotineBinPack::SplitMinimizeArea)
{
}
NzGuillotineImageAtlas::~NzGuillotineImageAtlas() = default;
void NzGuillotineImageAtlas::Clear()
{
m_layers.clear();
NotifyCleared();
}
void NzGuillotineImageAtlas::Free(NzSparsePtr<const NzRectui> rects, NzSparsePtr<unsigned int> layers, unsigned int count)
{
for (unsigned int i = 0; i < count; ++i)
{
#ifdef NAZARA_DEBUG
if (layers[i] >= m_layers.size())
{
NazaraWarning("Rectangle #" + NzString::Number(i) + " belong to an out-of-bounds layer (" + NzString::Number(i) + " >= " + NzString::Number(m_layers.size()) + ")");
continue;
}
#endif
m_layers[layers[i]].binPack.FreeRectangle(rects[i]);
m_layers[layers[i]].freedRectangles++;
}
}
NzGuillotineBinPack::FreeRectChoiceHeuristic NzGuillotineImageAtlas::GetRectChoiceHeuristic() const
{
return m_rectChoiceHeuristic;
}
NzGuillotineBinPack::GuillotineSplitHeuristic NzGuillotineImageAtlas::GetRectSplitHeuristic() const
{
return m_rectSplitHeuristic;
}
NzAbstractImage* NzGuillotineImageAtlas::GetLayer(unsigned int layerIndex) const
{
#if NAZARA_UTILITY_SAFE
if (layerIndex >= m_layers.size())
{
NazaraError("Layer index out of range (" + NzString::Number(layerIndex) + " >= " + NzString::Number(m_layers.size()) + ')');
return nullptr;
}
#endif
Layer& layer = m_layers[layerIndex];
ProcessGlyphQueue(layer);
return layer.image.get();
}
unsigned int NzGuillotineImageAtlas::GetLayerCount() const
{
return m_layers.size();
}
bool NzGuillotineImageAtlas::Insert(const NzImage& image, NzRectui* rect, bool* flipped, unsigned int* layerIndex)
{
if (m_layers.empty())
{
// On créé une première couche s'il n'y en a pas
m_layers.resize(1);
Layer& layer = m_layers.back();
layer.binPack.Reset(s_atlasStartSize, s_atlasStartSize);
}
// 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_layers.size(); ++i)
{
Layer& layer = m_layers[i];
// Une fois qu'un certain nombre de rectangles ont étés libérés d'une couche, on fusionne les rectangles libres
if (layer.freedRectangles > 10) // Valeur totalement arbitraire
{
while (layer.binPack.MergeFreeRectangles()); // Tant qu'une fusion est possible
layer.freedRectangles = 0; // Et on repart de zéro
}
if (layer.binPack.Insert(rect, flipped, 1, false, m_rectChoiceHeuristic, m_rectSplitHeuristic))
{
// Insertion réussie dans l'une des couches, on place le glyphe en file d'attente
layer.queuedGlyphs.resize(layer.queuedGlyphs.size()+1);
QueuedGlyph& glyph = layer.queuedGlyphs.back();
glyph.flipped = *flipped;
glyph.image = image; // Merci le Copy-On-Write
glyph.rect = *rect;
*layerIndex = i;
return true;
}
else if (i == m_layers.size() - 1) // Dernière itération ?
{
// Dernière couche, et le glyphe ne rentre pas, peut-on agrandir la taille de l'image ?
NzVector2ui newSize = layer.binPack.GetSize()*2;
if (ResizeLayer(layer, newSize))
{
// Oui on peut !
layer.binPack.Expand(newSize); // On ajuste l'atlas virtuel
// Et on relance la boucle sur la nouvelle dernière couche
i--;
}
else
{
// On ne peut plus agrandir la dernière couche, il est temps d'en créer une nouvelle
newSize.Set(s_atlasStartSize);
Layer newLayer;
if (!ResizeLayer(newLayer, newSize))
{
// Impossible d'allouer une nouvelle couche, nous manquons probablement de mémoire (ou le glyphe est trop grand)
NazaraError("Failed to allocate new layer, we are probably out of memory");
return false;
}
newLayer.binPack.Reset(newSize);
m_layers.emplace_back(std::move(newLayer)); // Insertion du layer
// On laisse la boucle insérer toute seule le rectangle à la prochaine itération
}
}
}
NazaraInternalError("Unknown error"); // Normalement on ne peut pas arriver ici
return false;
}
void NzGuillotineImageAtlas::SetRectChoiceHeuristic(NzGuillotineBinPack::FreeRectChoiceHeuristic heuristic)
{
m_rectChoiceHeuristic = heuristic;
}
void NzGuillotineImageAtlas::SetRectSplitHeuristic(NzGuillotineBinPack::GuillotineSplitHeuristic heuristic)
{
m_rectSplitHeuristic = heuristic;
}
NzAbstractImage* NzGuillotineImageAtlas::ResizeImage(NzAbstractImage* oldImage, const NzVector2ui& size) const
{
std::unique_ptr<NzImage> newImage(new NzImage(nzImageType_2D, nzPixelFormat_A8, size.x, size.y));
if (oldImage)
{
NzImage& image = *static_cast<NzImage*>(oldImage);
newImage->Copy(image, NzRectui(size), NzVector2ui(0, 0)); // Copie des anciennes données
}
return newImage.release();
}
bool NzGuillotineImageAtlas::ResizeLayer(Layer& layer, const NzVector2ui& size)
{
NzAbstractImage* oldLayer = layer.image.get();
std::unique_ptr<NzAbstractImage> newImage(ResizeImage(layer.image.get(), size));
if (!newImage)
return false; // Nous n'avons pas pu allouer
if (newImage.get() == oldLayer) // Le layer a été agrandi dans le même objet, pas de souci
{
newImage.release(); // On possède déjà un unique_ptr sur cette ressource
return true;
}
// On indique à ceux que ça intéresse qu'on a changé de pointeur
// (chose très importante pour ceux qui le stockent)
NotifyLayerChange(layer.image.get(), newImage.get());
// Et on ne met à jour le pointeur qu'après (car cette ligne libère également l'ancienne image)
layer.image = std::move(newImage);
return true;
}
void NzGuillotineImageAtlas::ProcessGlyphQueue(Layer& layer) const
{
std::vector<nzUInt8> pixelBuffer;
for (QueuedGlyph& glyph : layer.queuedGlyphs)
{
unsigned int glyphWidth = glyph.image.GetWidth();
unsigned int glyphHeight = glyph.image.GetHeight();
// Calcul de l'éventuel padding (pixels de contour)
unsigned int paddingX;
unsigned int paddingY;
if (glyph.flipped)
{
paddingX = (glyph.rect.height - glyphWidth)/2;
paddingY = (glyph.rect.width - glyphHeight)/2;
}
else
{
paddingX = (glyph.rect.width - glyphWidth)/2;
paddingY = (glyph.rect.height - glyphHeight)/2;
}
if (paddingX > 0 || paddingY > 0)
{
// On remplit les contours
pixelBuffer.resize(glyph.rect.width * glyph.rect.height);
std::memset(pixelBuffer.data(), 0, glyph.rect.width*glyph.rect.height*sizeof(nzUInt8));
layer.image->Update(pixelBuffer.data(), glyph.rect);
}
const nzUInt8* pixels;
// On copie le glyphe dans l'atlas
if (glyph.flipped)
{
pixelBuffer.resize(glyphHeight * glyphWidth);
// On tourne le glyphe pour qu'il rentre dans le rectangle
const nzUInt8* src = glyph.image.GetConstPixels();
nzUInt8* ptr = pixelBuffer.data();
unsigned int lineStride = glyphWidth*sizeof(nzUInt8); // BPP = 1
src += lineStride-1; // Départ en haut à droite
for (unsigned int x = 0; x < glyphWidth; ++x)
{
for (unsigned int y = 0; y < glyphHeight; ++y)
{
*ptr++ = *src;
src += lineStride;
}
src -= glyphHeight*lineStride + 1;
}
pixels = pixelBuffer.data();
std::swap(glyphWidth, glyphHeight);
}
else
pixels = glyph.image.GetConstPixels();
layer.image->Update(pixels, NzRectui(glyph.rect.x + paddingX, glyph.rect.y + paddingY, glyphWidth, glyphHeight), 0, glyphWidth, glyphHeight);
glyph.image.Destroy(); // On libère l'image dès que possible (pour réduire la consommation)
}
layer.queuedGlyphs.clear();
}

View File

@@ -4,7 +4,9 @@
#include <Nazara/Utility/Image.hpp>
#include <Nazara/Core/Error.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/PixelFormat.hpp>
#include <cmath>
#include <memory>
#include <stdexcept>
@@ -43,15 +45,8 @@ m_sharedImage(&emptyImage)
NzImage::NzImage(nzImageType type, nzPixelFormat format, unsigned int width, unsigned int height, unsigned int depth, nzUInt8 levelCount) :
m_sharedImage(&emptyImage)
{
NzErrorFlags flags(nzErrorFlag_ThrowException);
Create(type, format, width, height, depth, levelCount);
#ifdef NAZARA_DEBUG
if (!m_sharedImage)
{
NazaraError("Failed to create image");
throw std::runtime_error("Constructor failed");
}
#endif
}
NzImage::NzImage(const NzImage& image) :
@@ -62,12 +57,6 @@ m_sharedImage(image.m_sharedImage)
m_sharedImage->refCount++;
}
NzImage::NzImage(NzImage&& image) noexcept :
m_sharedImage(image.m_sharedImage)
{
image.m_sharedImage = &emptyImage;
}
NzImage::NzImage(SharedImage* sharedImage) :
m_sharedImage(sharedImage)
{
@@ -590,11 +579,6 @@ bool NzImage::FlipVertically()
return true;
}
nzUInt8 NzImage::GetBytesPerPixel() const
{
return NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}
const nzUInt8* NzImage::GetConstPixels(unsigned int x, unsigned int y, unsigned int z, nzUInt8 level) const
{
#if NAZARA_UTILITY_SAFE
@@ -680,6 +664,49 @@ nzUInt8 NzImage::GetMaxLevel() const
return GetMaxLevel(m_sharedImage->type, m_sharedImage->width, m_sharedImage->height, m_sharedImage->depth);
}
unsigned int NzImage::GetMemoryUsage() const
{
unsigned int width = m_sharedImage->width;
unsigned int height = m_sharedImage->height;
unsigned int depth = m_sharedImage->depth;
unsigned int size = 0;
for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
{
size += width * height * depth;
if (width > 1)
width >>= 1;
if (height > 1)
height >>= 1;
if (depth > 1)
depth >>= 1;
}
if (m_sharedImage->type == nzImageType_Cubemap)
size *= 6;
return size * NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}
unsigned int NzImage::GetMemoryUsage(nzUInt8 level) const
{
#if NAZARA_UTILITY_SAFE
if (level >= m_sharedImage->levelCount)
{
NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
return 0;
}
#endif
return (GetLevelSize(m_sharedImage->width, level)) *
(GetLevelSize(m_sharedImage->height, level)) *
((m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level)) *
NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}
NzColor NzImage::GetPixelColor(unsigned int x, unsigned int y, unsigned int z) const
{
#if NAZARA_UTILITY_SAFE
@@ -776,34 +803,7 @@ nzUInt8* NzImage::GetPixels(unsigned int x, unsigned int y, unsigned int z, nzUI
return GetPixelPtr(m_sharedImage->pixels[level], NzPixelFormat::GetBytesPerPixel(m_sharedImage->format), x, y, z, width, height);
}
unsigned int NzImage::GetSize() const
{
unsigned int width = m_sharedImage->width;
unsigned int height = m_sharedImage->height;
unsigned int depth = m_sharedImage->depth;
unsigned int size = 0;
for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
{
size += width * height * depth;
if (width > 1)
width >>= 1;
if (height > 1)
height >>= 1;
if (depth > 1)
depth >>= 1;
}
if (m_sharedImage->type == nzImageType_Cubemap)
size *= 6;
return size * NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
}
unsigned int NzImage::GetSize(nzUInt8 level) const
NzVector3ui NzImage::GetSize(nzUInt8 level) const
{
#if NAZARA_UTILITY_SAFE
if (level >= m_sharedImage->levelCount)
@@ -813,10 +813,7 @@ unsigned int NzImage::GetSize(nzUInt8 level) const
}
#endif
return (GetLevelSize(m_sharedImage->width, level)) *
(GetLevelSize(m_sharedImage->height, level)) *
((m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level)) *
NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
return NzVector3ui(GetLevelSize(m_sharedImage->width, level), GetLevelSize(m_sharedImage->height, level), GetLevelSize(m_sharedImage->depth, level));
}
nzImageType NzImage::GetType() const
@@ -837,16 +834,6 @@ unsigned int NzImage::GetWidth(nzUInt8 level) const
return GetLevelSize(m_sharedImage->width, level);
}
bool NzImage::IsCompressed() const
{
return NzPixelFormat::IsCompressed(m_sharedImage->format);
}
bool NzImage::IsCubemap() const
{
return m_sharedImage->type == nzImageType_Cubemap;
}
bool NzImage::IsValid() const
{
return m_sharedImage != &emptyImage;
@@ -1122,7 +1109,7 @@ void NzImage::SetLevelCount(nzUInt8 levelCount)
nzUInt8 oldLevelCount = m_sharedImage->levelCount;
nzUInt8 maxLevelCount = std::max(levelCount, oldLevelCount);
m_sharedImage->levelCount = levelCount; // Pour faire fonctionner GetSize
m_sharedImage->levelCount = levelCount; // Pour faire fonctionner GetMemoryUsage
nzUInt8** pixels = new nzUInt8*[levelCount];
for (unsigned int i = 0; i < maxLevelCount; ++i)
@@ -1130,7 +1117,7 @@ void NzImage::SetLevelCount(nzUInt8 levelCount)
if (i < oldLevelCount)
pixels[i] = m_sharedImage->pixels[i];
else if (i < levelCount)
pixels[i] = new nzUInt8[GetSize(i)];
pixels[i] = new nzUInt8[GetMemoryUsage(i)];
else
delete[] m_sharedImage->pixels[i];
}
@@ -1186,25 +1173,25 @@ bool NzImage::SetPixelColor(const NzColor& color, unsigned int x, unsigned int y
return true;
}
void NzImage::Update(const nzUInt8* pixels, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
bool NzImage::Update(const nzUInt8* pixels, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
#if NAZARA_UTILITY_SAFE
if (m_sharedImage == &emptyImage)
{
NazaraError("Image must be valid");
return;
return false;
}
if (!pixels)
{
NazaraError("Invalid pixel source");
return;
return false;
}
if (level >= m_sharedImage->levelCount)
{
NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
return;
return false;
}
#endif
@@ -1216,27 +1203,29 @@ void NzImage::Update(const nzUInt8* pixels, unsigned int srcWidth, unsigned int
GetLevelSize(m_sharedImage->depth, level),
0, 0,
srcWidth, srcHeight);
return true;
}
void NzImage::Update(const nzUInt8* pixels, const NzBoxui& box, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
bool NzImage::Update(const nzUInt8* pixels, const NzBoxui& box, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
#if NAZARA_UTILITY_SAFE
if (m_sharedImage == &emptyImage)
{
NazaraError("Image must be valid");
return;
return false;
}
if (!pixels)
{
NazaraError("Invalid pixel source");
return;
return false;
}
if (level >= m_sharedImage->levelCount)
{
NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
return;
return false;
}
#endif
@@ -1247,15 +1236,15 @@ void NzImage::Update(const nzUInt8* pixels, const NzBoxui& box, unsigned int src
if (!box.IsValid())
{
NazaraError("Invalid box");
return;
return false;
}
// Nous n'autorisons pas de modifier plus d'une face du cubemap à la fois (Nous prenons donc la profondeur de base)
///FIXME: Ce code n'autorise même pas la modification d'une autre face du cubemap Oo
if (box.x+box.width > width || box.y+box.height > height || box.z+box.depth > GetLevelSize(m_sharedImage->depth, level))
unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level);
if (box.x+box.width > width || box.y+box.height > height || box.z+box.depth > depth ||
(m_sharedImage->type == nzImageType_Cubemap && box.depth > 1)) // Nous n'autorisons pas de modifier plus d'une face du cubemap à la fois
{
NazaraError("Box dimensions are out of bounds");
return;
return false;
}
#endif
@@ -1268,64 +1257,13 @@ void NzImage::Update(const nzUInt8* pixels, const NzBoxui& box, unsigned int src
box.width, box.height, box.depth,
width, height,
srcWidth, srcHeight);
return true;
}
void NzImage::Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
bool NzImage::Update(const nzUInt8* pixels, const NzRectui& rect, unsigned int z, unsigned int srcWidth, unsigned int srcHeight, nzUInt8 level)
{
///FIXME: Cette surcharge possède-t-elle la moindre utilité ? (Update(pixels, NzBoxui(rect.x, rect.y, z, rect.width, rect.height, 1), srcWidth, ..) devrait donner le même résultat
#if NAZARA_UTILITY_SAFE
if (m_sharedImage == &emptyImage)
{
NazaraError("Image must be valid");
return;
}
if (!pixels)
{
NazaraError("Invalid pixel source");
return;
}
if (!rect.IsValid())
{
NazaraError("Invalid rectangle");
return;
}
if (level >= m_sharedImage->levelCount)
{
NazaraError("Level out of bounds (" + NzString::Number(level) + " >= " + NzString::Number(m_sharedImage->levelCount) + ')');
return;
}
#endif
unsigned int width = GetLevelSize(m_sharedImage->width, level);
unsigned int height = GetLevelSize(m_sharedImage->height, level);
#if NAZARA_UTILITY_SAFE
if (rect.x+rect.width > width || rect.y+rect.height > height)
{
NazaraError("Rectangle dimensions are out of bounds");
return;
}
unsigned int depth = (m_sharedImage->type == nzImageType_Cubemap) ? 6 : GetLevelSize(m_sharedImage->depth, level);
if (z >= depth)
{
NazaraError("Z value exceeds depth (" + NzString::Number(z) + " >= " + NzString::Number(depth) + ')');
return;
}
#endif
EnsureOwnership();
nzUInt8 bpp = NzPixelFormat::GetBytesPerPixel(m_sharedImage->format);
nzUInt8* dstPixels = GetPixelPtr(m_sharedImage->pixels[level], bpp, rect.x, rect.y, z, width, height);
Copy(dstPixels, pixels, bpp,
rect.width, rect.height, 1,
width, height,
srcWidth, srcHeight);
return Update(pixels, NzBoxui(rect.x, rect.y, z, rect.width, rect.height, 1), srcWidth, srcHeight, level);
}
NzImage& NzImage::operator=(const NzImage& image)
@@ -1339,13 +1277,6 @@ NzImage& NzImage::operator=(const NzImage& image)
return *this;
}
NzImage& NzImage::operator=(NzImage&& image) noexcept
{
std::swap(m_sharedImage, image.m_sharedImage);
return *this;
}
void NzImage::Copy(nzUInt8* destination, const nzUInt8* source, nzUInt8 bpp, unsigned int width, unsigned int height, unsigned int depth, unsigned int dstWidth, unsigned int dstHeight, unsigned int srcWidth, unsigned int srcHeight)
{
if (dstWidth == 0)
@@ -1434,7 +1365,7 @@ void NzImage::EnsureOwnership()
nzUInt8** pixels = new nzUInt8*[m_sharedImage->levelCount];
for (unsigned int i = 0; i < m_sharedImage->levelCount; ++i)
{
unsigned int size = GetSize(i);
unsigned int size = GetMemoryUsage(i);
pixels[i] = new nzUInt8[size];
std::memcpy(pixels[i], m_sharedImage->pixels[i], size);
}

View File

@@ -24,7 +24,7 @@ NzIndexBuffer::NzIndexBuffer(bool largeIndices, NzBuffer* buffer, unsigned int s
Reset(largeIndices, buffer, startOffset, endOffset);
}
NzIndexBuffer::NzIndexBuffer(bool largeIndices, unsigned int length, nzBufferStorage storage, nzBufferUsage usage)
NzIndexBuffer::NzIndexBuffer(bool largeIndices, unsigned int length, nzUInt32 storage, nzBufferUsage usage)
{
NzErrorFlags(nzErrorFlag_ThrowException, true);
Reset(largeIndices, length, storage, usage);
@@ -220,7 +220,7 @@ void NzIndexBuffer::Reset(bool largeIndices, NzBuffer* buffer, unsigned int star
m_startOffset = startOffset;
}
void NzIndexBuffer::Reset(bool largeIndices, unsigned int length, nzBufferStorage storage, nzBufferUsage usage)
void NzIndexBuffer::Reset(bool largeIndices, unsigned int length, nzUInt32 storage, nzBufferUsage usage)
{
unsigned int stride = (largeIndices) ? sizeof(nzUInt32) : sizeof(nzUInt16);
@@ -251,7 +251,7 @@ void NzIndexBuffer::Reset(NzIndexBuffer&& indexBuffer) noexcept
m_startOffset = indexBuffer.m_startOffset;
}
bool NzIndexBuffer::SetStorage(nzBufferStorage storage)
bool NzIndexBuffer::SetStorage(nzUInt32 storage)
{
return m_buffer->SetStorage(storage);
}

View File

@@ -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

View File

@@ -0,0 +1,403 @@
// 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, nzUInt32 style, NzFontGlyph* dst) override
{
#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 = 2 << 6;
bool embolden = (style & nzTextStyle_Bold);
dst->advance = (embolden) ? boldStrength >> 6 : 0;
if (embolden && glyph->format == FT_GLYPH_FORMAT_OUTLINE)
{
// http://www.freetype.org/freetype2/docs/reference/ft2-outline_processing.html#FT_Outline_Embolden
FT_Outline_Embolden(&glyph->outline, boldStrength);
embolden = false;
}
// 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 (embolden)
{
// 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);
embolden = false;
}
dst->advance += glyph->metrics.horiAdvance >> 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 override
{
return m_face->family_name;
}
NzString GetStyleName() const override
{
return m_face->style_name;
}
bool HasKerning() const override
{
return FT_HAS_KERNING(m_face);
}
bool IsScalable() const override
{
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 override
{
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 override
{
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 override
{
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 override
{
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;
}
bool SupportsStyle(nzUInt32 style) const override
{
///TODO
return style == nzTextStyle_Regular || style == nzTextStyle_Bold;
}
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 unes..
static std::set<NzString> supportedExtensions = {
"afm", "bdf", "cff", "cid", "dfont", "fnt", "fon", "otf", "pfa", "pfb", "pfm", "pfr", "sfnt", "ttc", "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;
}
}

View File

@@ -25,13 +25,13 @@
NzMeshParams::NzMeshParams()
{
if (!NzBuffer::IsSupported(storage))
storage = nzBufferStorage_Software;
if (!NzBuffer::IsStorageSupported(storage))
storage = nzDataStorage_Software;
}
bool NzMeshParams::IsValid() const
{
if (!NzBuffer::IsSupported(storage))
if (!NzBuffer::IsStorageSupported(storage))
{
NazaraError("Storage not supported");
return false;

View File

@@ -270,7 +270,7 @@ NzNode& NzNode::Move(const NzVector3f& movement, nzCoordSys coordSys)
}
case nzCoordSys_Local:
m_position += m_scale * (m_rotation * movement);
m_position += m_rotation * movement;
break;
}
@@ -685,7 +685,7 @@ void NzNode::UpdateDerived() const
m_derivedRotation.Normalize();
}
else
m_derivedRotation = m_initialRotation * m_rotation; ///FIXME: Besoin d'une normalisation ?
m_derivedRotation = m_initialRotation * m_rotation;
m_derivedScale = m_initialScale * m_scale;
if (m_inheritScale)

View File

@@ -50,6 +50,94 @@ namespace
return nullptr;
}
/**********************************A8***********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_A8, nzPixelFormat_BGRA8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = 0xFF;
*dst++ = 0xFF;
*dst++ = 0xFF;
*dst++ = *start;
start += 1;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_A8, nzPixelFormat_LA8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = 0xFF;
*dst++ = *start;
start += 1;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_A8, nzPixelFormat_RGB5A1>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
nzUInt16* ptr = reinterpret_cast<nzUInt16*>(dst);
while (start < end)
{
*ptr = (static_cast<nzUInt16>(0x1F) << 11) |
(static_cast<nzUInt16>(0x1F) << 6) |
(static_cast<nzUInt16>(0x1F) << 1) |
((*start > 0xF) ? 1 : 0); // > 128
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
#endif
ptr++;
start += 1;
}
return reinterpret_cast<nzUInt8*>(ptr);
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_A8, nzPixelFormat_RGBA4>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
nzUInt16* ptr = reinterpret_cast<nzUInt16*>(dst);
while (start < end)
{
*ptr = 0xFFF0 | c8to4(*start);
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
#endif
ptr++;
start += 1;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_A8, nzPixelFormat_RGBA8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = 0xFF;
*dst++ = 0xFF;
*dst++ = 0xFF;
*dst++ = *start;
start += 1;
}
return dst;
}
/**********************************BGR8***********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_BGR8, nzPixelFormat_BGRA8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
@@ -170,6 +258,19 @@ namespace
}
/**********************************BGRA8**********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_BGRA8, nzPixelFormat_A8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = start[3];
start += 4;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_BGRA8, nzPixelFormat_BGR8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
@@ -243,7 +344,7 @@ namespace
*ptr = (static_cast<nzUInt16>(c8to5(start[2])) << 11) |
(static_cast<nzUInt16>(c8to5(start[1])) << 6) |
(static_cast<nzUInt16>(c8to5(start[0])) << 1) |
((start[3] == 0xFF) ? 1 : 0);
((start[3] > 0xF) ? 1 : 0); // > 128
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
@@ -413,6 +514,19 @@ namespace
}
/***********************************LA8***********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_LA8, nzPixelFormat_A8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = start[1];
start += 2;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_LA8, nzPixelFormat_BGR8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
@@ -465,7 +579,7 @@ namespace
{
nzUInt16 l = static_cast<nzUInt16>(c8to5(start[0]));
*ptr = (l << 11) | (l << 6) | (l << 1) | ((start[1] == 0xFF) ? 1 : 0);
*ptr = (l << 11) | (l << 6) | (l << 1) | ((start[1] > 0xF) ? 1 : 0);
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
@@ -531,6 +645,25 @@ namespace
}
/*********************************RGBA4***********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGBA4, nzPixelFormat_A8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
nzUInt16 pixel = *reinterpret_cast<const nzUInt16*>(start);
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&pixel, sizeof(nzUInt16));
#endif
*dst++ = c4to8(pixel & 0x000F);
start += 2;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGBA4, nzPixelFormat_BGR8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
@@ -638,7 +771,7 @@ namespace
nzUInt16 b = c4to5((pixel & 0x00F0) >> 4);
nzUInt16 a = c4to5((pixel & 0x000F) >> 0);
*ptr = (r << 11) | (g << 6) | (b << 1) | ((a == 0xFF) ? 1 : 0);
*ptr = (r << 11) | (g << 6) | (b << 1) | ((a > 0x3) ? 1 : 0);
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
@@ -695,6 +828,25 @@ namespace
}
/*********************************RGB5A1**********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGB5A1, nzPixelFormat_A8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
nzUInt16 pixel = *reinterpret_cast<const nzUInt16*>(start);
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(&pixel, sizeof(nzUInt16));
#endif
*dst++ = static_cast<nzUInt8>((pixel & 0x1)*0xFF);
start += 2;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGB5A1, nzPixelFormat_BGR8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
@@ -977,6 +1129,19 @@ namespace
}
/**********************************RGBA8**********************************/
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGBA8, nzPixelFormat_A8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
while (start < end)
{
*dst++ = start[3];
start += 4;
}
return dst;
}
template<>
nzUInt8* ConvertPixels<nzPixelFormat_RGBA8, nzPixelFormat_BGR8>(const nzUInt8* start, const nzUInt8* end, nzUInt8* dst)
{
@@ -1044,7 +1209,7 @@ namespace
*ptr = (static_cast<nzUInt16>(c8to5(start[0])) << 11) |
(static_cast<nzUInt16>(c8to5(start[1])) << 6) |
(static_cast<nzUInt16>(c8to5(start[2])) << 1) |
((start[3] == 0xFF) ? 1 : 0);
((start[3] > 0xF) ? 1 : 0); // > 128
#ifdef NAZARA_BIG_ENDIAN
NzByteSwap(ptr, sizeof(nzUInt16));
@@ -1106,6 +1271,13 @@ bool NzPixelFormat::Initialize()
// Réinitialisation
std::memset(s_convertFunctions, 0, (nzPixelFormat_Max+1)*(nzPixelFormat_Max+1)*sizeof(NzPixelFormat::ConvertFunction));
/***********************************A8************************************/
RegisterConverter<nzPixelFormat_A8, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_A8, nzPixelFormat_LA8>();
RegisterConverter<nzPixelFormat_A8, nzPixelFormat_RGB5A1>();
RegisterConverter<nzPixelFormat_A8, nzPixelFormat_RGBA4>();
RegisterConverter<nzPixelFormat_A8, nzPixelFormat_RGBA8>();
/**********************************BGR8***********************************/
RegisterConverter<nzPixelFormat_BGR8, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_BGR8, nzPixelFormat_L8>();
@@ -1124,6 +1296,7 @@ bool NzPixelFormat::Initialize()
RegisterConverter<nzPixelFormat_BGR8, nzPixelFormat_RGBA8>();
/**********************************BGRA8**********************************/
RegisterConverter<nzPixelFormat_BGRA8, nzPixelFormat_A8>();
RegisterConverter<nzPixelFormat_BGRA8, nzPixelFormat_BGR8>();
RegisterConverter<nzPixelFormat_BGRA8, nzPixelFormat_L8>();
RegisterConverter<nzPixelFormat_BGRA8, nzPixelFormat_LA8>();/*
@@ -1227,6 +1400,7 @@ bool NzPixelFormat::Initialize()
RegisterConverter<nzPixelFormat_L8, nzPixelFormat_RGBA8>();
/***********************************LA8***********************************/
RegisterConverter<nzPixelFormat_LA8, nzPixelFormat_A8>();
RegisterConverter<nzPixelFormat_LA8, nzPixelFormat_BGR8>();
RegisterConverter<nzPixelFormat_LA8, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_LA8, nzPixelFormat_L8>();/*
@@ -1244,6 +1418,7 @@ bool NzPixelFormat::Initialize()
RegisterConverter<nzPixelFormat_LA8, nzPixelFormat_RGBA8>();
/**********************************RGBA4**********************************/
RegisterConverter<nzPixelFormat_RGBA4, nzPixelFormat_A8>();
RegisterConverter<nzPixelFormat_RGBA4, nzPixelFormat_BGR8>();
RegisterConverter<nzPixelFormat_RGBA4, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_RGBA4, nzPixelFormat_L8>();
@@ -1261,6 +1436,7 @@ bool NzPixelFormat::Initialize()
RegisterConverter<nzPixelFormat_RGBA4, nzPixelFormat_RGBA8>();
/*********************************RGB5A1**********************************/
RegisterConverter<nzPixelFormat_RGB5A1, nzPixelFormat_A8>();
RegisterConverter<nzPixelFormat_RGB5A1, nzPixelFormat_BGR8>();
RegisterConverter<nzPixelFormat_RGB5A1, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_RGB5A1, nzPixelFormat_L8>();
@@ -1295,6 +1471,7 @@ bool NzPixelFormat::Initialize()
RegisterConverter<nzPixelFormat_RGB8, nzPixelFormat_RGBA8>();
/**********************************RGBA8**********************************/
RegisterConverter<nzPixelFormat_RGBA8, nzPixelFormat_A8>();
RegisterConverter<nzPixelFormat_RGBA8, nzPixelFormat_BGR8>();
RegisterConverter<nzPixelFormat_RGBA8, nzPixelFormat_BGRA8>();
RegisterConverter<nzPixelFormat_RGBA8, nzPixelFormat_L8>();

View File

@@ -0,0 +1,305 @@
// Copyright (C) 2015 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/SimpleTextDrawer.hpp>
#include <memory>
#include <Nazara/Utility/Debug.hpp>
///TODO: Listener de font (cas où l'atlas a changé)
NzSimpleTextDrawer::NzSimpleTextDrawer() :
m_color(NzColor::White),
m_style(nzTextStyle_Regular)
{
// SetFont(NzFont::GetDefault());
}
NzSimpleTextDrawer::~NzSimpleTextDrawer()
{
if (m_font)
m_font->RemoveResourceListener(this);
}
const NzRectui& NzSimpleTextDrawer::GetBounds() const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_bounds;
}
unsigned int NzSimpleTextDrawer::GetCharacterSize() const
{
return m_characterSize;
}
const NzColor& NzSimpleTextDrawer::GetColor() const
{
return m_color;
}
NzFont* NzSimpleTextDrawer::GetFont() const
{
return m_font;
}
nzUInt32 NzSimpleTextDrawer::GetStyle() const
{
return m_style;
}
void NzSimpleTextDrawer::SetCharacterSize(unsigned int characterSize)
{
m_characterSize = characterSize;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetColor(const NzColor& color)
{
m_color = color;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetFont(NzFont* font)
{
if (m_font)
m_font->RemoveResourceListener(this);
m_font = font;
if (m_font)
m_font->AddResourceListener(this);
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetStyle(nzUInt32 style)
{
m_style = style;
m_glyphUpdated = false;
}
void NzSimpleTextDrawer::SetText(const NzString& str)
{
m_text = str;
m_glyphUpdated = false;
}
NzSimpleTextDrawer NzSimpleTextDrawer::Draw(unsigned int characterSize, const NzString& str, nzUInt32 style, const NzColor& color)
{
///FIXME: Sans default font ça n'a aucun intérêt
NzSimpleTextDrawer drawer;
drawer.SetCharacterSize(characterSize);
drawer.SetColor(color);
drawer.SetStyle(style);
drawer.SetText(str);
return drawer;
}
NzSimpleTextDrawer NzSimpleTextDrawer::Draw(NzFont* font, unsigned int characterSize, const NzString& str, nzUInt32 style, const NzColor& color)
{
NzSimpleTextDrawer drawer;
drawer.SetCharacterSize(characterSize);
drawer.SetColor(color);
drawer.SetFont(font);
drawer.SetStyle(style);
drawer.SetText(str);
return drawer;
}
NzFont* NzSimpleTextDrawer::GetFont(unsigned int index) const
{
#if NAZARA_UTILITY_SAFE
if (index > 0)
{
NazaraError("Font index out of range (" + NzString::Number(index) + " >= 1)");
return nullptr;
}
#endif
return m_font;
}
unsigned int NzSimpleTextDrawer::GetFontCount() const
{
return 1;
}
const NzAbstractTextDrawer::Glyph& NzSimpleTextDrawer::GetGlyph(unsigned int index) const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_glyphs[index];
}
unsigned int NzSimpleTextDrawer::GetGlyphCount() const
{
if (!m_glyphUpdated)
UpdateGlyphs();
return m_glyphs.size();
}
bool NzSimpleTextDrawer::OnResourceModified(const NzResource* resource, int index, unsigned int code)
{
NazaraUnused(resource);
NazaraUnused(index);
#ifdef NAZARA_DEBUG
if (m_font != resource)
{
NazaraInternalError("Not listening to " + NzString::Pointer(resource));
return false;
}
#endif
if (code == NzFont::ModificationCode_AtlasChanged ||
code == NzFont::ModificationCode_AtlasLayerChanged ||
code == NzFont::ModificationCode_GlyphCacheCleared)
{
m_glyphUpdated = false;
}
return true;
}
void NzSimpleTextDrawer::OnResourceReleased(const NzResource* resource, int index)
{
NazaraUnused(resource);
NazaraUnused(index);
#ifdef NAZARA_DEBUG
if (m_font != resource)
{
NazaraInternalError("Not listening to " + NzString::Pointer(resource));
return;
}
#endif
SetFont(nullptr);
}
void NzSimpleTextDrawer::UpdateGlyphs() const
{
m_bounds.MakeZero();
m_glyphs.clear();
m_glyphUpdated = true;
#if NAZARA_UTILITY_SAFE
if (!m_font || !m_font->IsValid())
{
NazaraError("Invalid font");
return;
}
#endif
if (m_text.IsEmpty())
return;
///TODO: Itération UTF-8 => UTF-32 sans allocation de buffer (Exposer utf8cpp ?)
unsigned int size;
std::unique_ptr<char32_t[]> characters(m_text.GetUtf32Buffer(&size));
if (!characters)
{
NazaraError("Invalid character set");
return;
}
const NzFont::SizeInfo& sizeInfo = m_font->GetSizeInfo(m_characterSize);
// "Curseur" de dessin
NzVector2ui drawPos(0, m_characterSize);
NzVector2ui lastPos(0, 0);
m_glyphs.reserve(size);
nzUInt32 previousCharacter = 0;
for (unsigned int i = 0; i < size; ++i)
{
char32_t character = characters[i];
if (previousCharacter != 0)
drawPos.x += m_font->GetKerning(m_characterSize, previousCharacter, character);
previousCharacter = character;
bool whitespace = true;
switch (character)
{
case ' ':
drawPos.x += sizeInfo.spaceAdvance;
break;
case '\n':
drawPos.x = 0;
drawPos.y += sizeInfo.lineHeight;
break;
case '\t':
drawPos.x += sizeInfo.spaceAdvance*4;
break;
default:
whitespace = false;
break;
}
if (whitespace)
continue; // Inutile d'avoir un glyphe pour un espace blanc
const NzFont::Glyph& fontGlyph = m_font->GetGlyph(m_characterSize, m_style, character);
if (!fontGlyph.valid)
continue; // Le glyphe n'a pas été correctement chargé, que pouvons-nous faire d'autre que le passer
Glyph glyph;
glyph.atlas = m_font->GetAtlas()->GetLayer(fontGlyph.layerIndex);
glyph.atlasRect = fontGlyph.atlasRect;
glyph.color = m_color;
glyph.flipped = fontGlyph.flipped;
float advance = fontGlyph.advance;
NzRectf bounds(fontGlyph.aabb);
bounds.x += drawPos.x;
bounds.y += drawPos.y;
if (fontGlyph.requireFauxBold)
{
// On va agrandir le glyphe pour simuler le gras (idée moisie, mais idée quand même)
NzVector2f center = bounds.GetCenter();
bounds.width *= 1.1f;
bounds.height *= 1.1f;
// On le replace à la bonne hauteur
NzVector2f offset(bounds.GetCenter() - center);
bounds.y -= offset.y;
// On ajuste l'espacement
advance *= 1.1f;
}
// On "penche" le glyphe pour obtenir un semblant d'italique
float italic = (fontGlyph.requireFauxItalic) ? 0.208f : 0.f;
float italicTop = italic * bounds.y;
float italicBottom = italic * bounds.GetMaximum().y;
glyph.corners[0].Set(bounds.x - italicTop, bounds.y);
glyph.corners[1].Set(bounds.x + bounds.width - italicTop, bounds.y);
glyph.corners[2].Set(bounds.x - italicBottom, bounds.y + bounds.height);
glyph.corners[3].Set(bounds.x + bounds.width - italicBottom, bounds.y + bounds.height);
m_glyphs.push_back(glyph);
lastPos = drawPos;
drawPos.x += advance;
}
m_bounds.ExtendTo(lastPos);
}

View File

@@ -12,6 +12,7 @@
#include <Nazara/Core/Thread.hpp>
#include <Nazara/Utility/Buffer.hpp>
#include <Nazara/Utility/Config.hpp>
#include <Nazara/Utility/Loaders/FreeType.hpp>
#include <Nazara/Utility/Loaders/MD2.hpp>
#include <Nazara/Utility/Loaders/MD5Anim.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é
/// Loaders génériques
// Font
NzLoaders_FreeType_Register();
// Image
NzLoaders_STB_Register(); // Loader générique (STB)
@@ -109,6 +113,7 @@ void NzUtility::Uninitialize()
// Libération du module
s_moduleReferenceCounter = 0;
NzLoaders_FreeType_Unregister();
NzLoaders_MD2_Unregister();
NzLoaders_MD5Anim_Unregister();
NzLoaders_MD5Mesh_Unregister();

View File

@@ -20,7 +20,7 @@ NzVertexBuffer::NzVertexBuffer(const NzVertexDeclaration* vertexDeclaration, NzB
Reset(vertexDeclaration, buffer, startOffset, endOffset);
}
NzVertexBuffer::NzVertexBuffer(const NzVertexDeclaration* vertexDeclaration, unsigned int length, nzBufferStorage storage, nzBufferUsage usage)
NzVertexBuffer::NzVertexBuffer(const NzVertexDeclaration* vertexDeclaration, unsigned int length, nzUInt32 storage, nzBufferUsage usage)
{
NzErrorFlags(nzErrorFlag_ThrowException, true);
Reset(vertexDeclaration, length, storage, usage);
@@ -230,7 +230,7 @@ void NzVertexBuffer::Reset(const NzVertexDeclaration* vertexDeclaration, NzBuffe
m_vertexDeclaration = vertexDeclaration;
}
void NzVertexBuffer::Reset(const NzVertexDeclaration* vertexDeclaration, unsigned int length, nzBufferStorage storage, nzBufferUsage usage)
void NzVertexBuffer::Reset(const NzVertexDeclaration* vertexDeclaration, unsigned int length, nzUInt32 storage, nzBufferUsage usage)
{
m_endOffset = length * ((vertexDeclaration) ? vertexDeclaration->GetStride() : 1);
m_startOffset = 0;
@@ -259,7 +259,7 @@ void NzVertexBuffer::Reset(NzVertexBuffer&& vertexBuffer) noexcept
m_vertexDeclaration = std::move(vertexBuffer.m_vertexDeclaration);
}
bool NzVertexBuffer::SetStorage(nzBufferStorage storage)
bool NzVertexBuffer::SetStorage(nzUInt32 storage)
{
return m_buffer->SetStorage(storage);
}

View File

@@ -192,6 +192,13 @@ bool NzVertexDeclaration::Initialize()
NazaraAssert(declaration->GetStride() == sizeof(NzVertexStruct_XY), "Invalid stride for declaration nzVertexLayout_XY");
// nzVertexLayout_XY_Color : NzVertexStruct_XY_Color
declaration = &s_declarations[nzVertexLayout_XY_Color];
declaration->EnableComponent(nzVertexComponent_Position, nzComponentType_Float2, NzOffsetOf(NzVertexStruct_XY_Color, position));
declaration->EnableComponent(nzVertexComponent_Color, nzComponentType_Color, NzOffsetOf(NzVertexStruct_XY_Color, color));
NazaraAssert(declaration->GetStride() == sizeof(NzVertexStruct_XY_Color), "Invalid stride for declaration nzVertexLayout_XY_Color");
// nzVertexLayout_XY_UV : NzVertexStruct_XY_UV
declaration = &s_declarations[nzVertexLayout_XY_UV];
declaration->EnableComponent(nzVertexComponent_Position, nzComponentType_Float2, NzOffsetOf(NzVertexStruct_XY_UV, position));
@@ -205,6 +212,21 @@ bool NzVertexDeclaration::Initialize()
NazaraAssert(declaration->GetStride() == sizeof(NzVertexStruct_XYZ), "Invalid stride for declaration nzVertexLayout_XYZ");
// nzVertexLayout_XYZ_Color : NzVertexStruct_XYZ_Color
declaration = &s_declarations[nzVertexLayout_XYZ_Color];
declaration->EnableComponent(nzVertexComponent_Position, nzComponentType_Float3, NzOffsetOf(NzVertexStruct_XYZ_Color, position));
declaration->EnableComponent(nzVertexComponent_Color, nzComponentType_Color, NzOffsetOf(NzVertexStruct_XYZ_Color, color));
NazaraAssert(declaration->GetStride() == sizeof(NzVertexStruct_XYZ_Color), "Invalid stride for declaration nzVertexLayout_XYZ_Color");
// nzVertexLayout_XYZ_Color_UV : NzVertexStruct_XYZ_Color_UV
declaration = &s_declarations[nzVertexLayout_XYZ_Color_UV];
declaration->EnableComponent(nzVertexComponent_Position, nzComponentType_Float3, NzOffsetOf(NzVertexStruct_XYZ_Color_UV, position));
declaration->EnableComponent(nzVertexComponent_Color, nzComponentType_Color, NzOffsetOf(NzVertexStruct_XYZ_Color_UV, color));
declaration->EnableComponent(nzVertexComponent_TexCoord, nzComponentType_Float2, NzOffsetOf(NzVertexStruct_XYZ_Color_UV, uv));
NazaraAssert(declaration->GetStride() == sizeof(NzVertexStruct_XYZ_Color_UV), "Invalid stride for declaration nzVertexLayout_XYZ_Color_UV");
// nzVertexLayout_XYZ_Normal : NzVertexStruct_XYZ_Normal
declaration = &s_declarations[nzVertexLayout_XYZ_Normal];
declaration->EnableComponent(nzVertexComponent_Position, nzComponentType_Float3, NzOffsetOf(NzVertexStruct_XYZ_Normal, position));