Wow! I can't believe I haven't started this project again!
This commit is contained in:
@ -6,8 +6,10 @@
|
||||
# Libraries
|
||||
target_link_libraries(${DAWN_TARGET_NAME}
|
||||
PUBLIC
|
||||
glm
|
||||
archive_static
|
||||
glm::glm
|
||||
nlohmann_json::nlohmann_json
|
||||
freetype
|
||||
)
|
||||
|
||||
# Includes
|
||||
|
@ -67,6 +67,35 @@ AssetDataLoader::AssetDataLoader(std::string fileName) : fileName(fileName) {
|
||||
);
|
||||
}
|
||||
|
||||
size_t AssetDataLoader::getSize() {
|
||||
assertTrue(this->assetArchiveEntry != nullptr, "AssetDataLoader::getSize: Entry is NULL!");
|
||||
assertTrue(archive_entry_size_is_set(assetArchiveEntry), "assetGetSize: Entry size is not set!");
|
||||
return archive_entry_size(assetArchiveEntry);
|
||||
}
|
||||
|
||||
size_t AssetDataLoader::getPosition() {
|
||||
assertNotNull(this->assetArchiveFile, "AssetDataLoader::getPosition: File is not open!");
|
||||
return this->position;
|
||||
}
|
||||
|
||||
std::string AssetDataLoader::getEntireContentsAsString() {
|
||||
if(!this->isOpen()) {
|
||||
this->open();
|
||||
} else {
|
||||
this->rewind();
|
||||
}
|
||||
|
||||
std::string buffer;
|
||||
buffer.resize(this->getSize());
|
||||
this->read((uint8_t*)buffer.data(), buffer.size());
|
||||
this->close();
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool_t AssetDataLoader::isOpen() {
|
||||
return this->assetArchive != nullptr;
|
||||
}
|
||||
|
||||
void AssetDataLoader::open() {
|
||||
assertNull(this->assetArchiveFile, "AssetDataLoader::open: File is already open");
|
||||
assertNull(this->assetArchive, "AssetDataLoader::open: Archive is already open");
|
||||
@ -156,12 +185,6 @@ size_t AssetDataLoader::readUntil(
|
||||
return i;
|
||||
}
|
||||
|
||||
size_t AssetDataLoader::getSize() {
|
||||
assertTrue(this->assetArchiveEntry != nullptr, "AssetDataLoader::getSize: Entry is NULL!");
|
||||
assertTrue(archive_entry_size_is_set(assetArchiveEntry), "assetGetSize: Entry size is not set!");
|
||||
return archive_entry_size(assetArchiveEntry);
|
||||
}
|
||||
|
||||
size_t AssetDataLoader::skip(size_t n) {
|
||||
assertTrue(n >= 0, "AssetDataLoader::skip: Byte count must be greater than 0.");
|
||||
|
||||
@ -185,16 +208,14 @@ size_t AssetDataLoader::setPosition(const size_t position) {
|
||||
}
|
||||
|
||||
void AssetDataLoader::rewind() {
|
||||
assertTrue(this->isOpen(), "Asset is not open!");
|
||||
if(this->position == 0) return;
|
||||
|
||||
// TODO: See if I can optimize this
|
||||
this->close();
|
||||
this->open();
|
||||
}
|
||||
|
||||
size_t AssetDataLoader::getPosition() {
|
||||
assertNotNull(this->assetArchiveFile, "AssetDataLoader::getPosition: File is not open!");
|
||||
return this->position;
|
||||
}
|
||||
|
||||
AssetDataLoader::~AssetDataLoader() {
|
||||
if(this->assetArchiveFile != nullptr) this->close();
|
||||
}
|
@ -89,6 +89,33 @@ namespace Dawn {
|
||||
* @param fileName File name of the asset that is to be loaded.
|
||||
*/
|
||||
AssetDataLoader(std::string filename);
|
||||
|
||||
/**
|
||||
* Get the size of the asset.
|
||||
* @return The size of the asset in bytes.
|
||||
*/
|
||||
size_t getSize();
|
||||
|
||||
/**
|
||||
* Returns the current position of the read head.
|
||||
*
|
||||
* @return The current read head position.
|
||||
*/
|
||||
size_t getPosition();
|
||||
|
||||
/**
|
||||
* Get the entire contents of the asset as a string.
|
||||
*
|
||||
* @return The entire contents of the asset as a string.
|
||||
*/
|
||||
std::string getEntireContentsAsString();
|
||||
|
||||
/**
|
||||
* Check if the asset is open.
|
||||
*
|
||||
* @return True if the asset is open, otherwise false.
|
||||
*/
|
||||
bool_t isOpen();
|
||||
|
||||
/**
|
||||
* Platform-centric method to open a file buffer to an asset.
|
||||
@ -123,12 +150,6 @@ namespace Dawn {
|
||||
const size_t maxSize,
|
||||
const char_t delimiter
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the size of the asset.
|
||||
* @return The size of the asset in bytes.
|
||||
*/
|
||||
size_t getSize();
|
||||
|
||||
/**
|
||||
* Skips the read head forward to a given position.
|
||||
@ -151,13 +172,6 @@ namespace Dawn {
|
||||
*/
|
||||
size_t setPosition(const size_t absolutePosition);
|
||||
|
||||
/**
|
||||
* Returns the current position of the read head.
|
||||
*
|
||||
* @return The current read head position.
|
||||
*/
|
||||
size_t getPosition();
|
||||
|
||||
/**
|
||||
* Cleanup the asset loader.
|
||||
*/
|
||||
|
@ -92,11 +92,24 @@ namespace Dawn {
|
||||
* @param fontSize The font size to get the truetype asset of.
|
||||
* @return The asset loader for the given asset.
|
||||
*/
|
||||
template<class T>
|
||||
std::shared_ptr<T> get(
|
||||
const std::string filename,
|
||||
const uint32_t fontSize
|
||||
);
|
||||
// std::shared_ptr<TrueTypeTexture> get(
|
||||
// const std::string filename,
|
||||
// const uint32_t fontSize
|
||||
// ) {
|
||||
// auto existing = this->getExisting<TrueTypeLoader>(filename);
|
||||
// if(existing) {
|
||||
// // Check pointer hasn't gone stale, if it has remove it and create new.
|
||||
// auto texture = existing->getTexture(fontSize);
|
||||
// if(texture) return texture;
|
||||
// this->removeExisting(filename);
|
||||
// }
|
||||
|
||||
// std::shared_ptr<TrueTypeLoader> loader = std::make_shared<TrueTypeLoader>(
|
||||
// filename
|
||||
// );
|
||||
// pendingAssetLoaders.push_back(std::static_pointer_cast<AssetLoader>(loader));
|
||||
// return loader->getTexture(fontSize);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Dispose the asset manager, and all attached assets.
|
||||
|
@ -7,4 +7,6 @@
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
TextureLoader.cpp
|
||||
JSONLoader.cpp
|
||||
TrueTypeLoader.cpp
|
||||
)
|
33
src/dawn/asset/loaders/JSONLoader.cpp
Normal file
33
src/dawn/asset/loaders/JSONLoader.cpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2024 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "JSONLoader.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
JSONLoader::JSONLoader(const std::string name) :
|
||||
AssetLoader(name),
|
||||
loader(name),
|
||||
state(JSONLoaderState::INITIAL)
|
||||
{
|
||||
}
|
||||
|
||||
void JSONLoader::updateAsync() {
|
||||
if(this->state != JSONLoaderState::INITIAL) return;
|
||||
|
||||
this->state = JSONLoaderState::LOADING_JSON;
|
||||
std::string jsonContents = loader.getEntireContentsAsString();
|
||||
this->data = json::parse(jsonContents);
|
||||
this->state = JSONLoaderState::DONE;
|
||||
this->loaded = true;
|
||||
}
|
||||
|
||||
void JSONLoader::updateSync() {
|
||||
}
|
||||
|
||||
JSONLoader::~JSONLoader() {
|
||||
|
||||
}
|
||||
|
33
src/dawn/asset/loaders/JSONLoader.hpp
Normal file
33
src/dawn/asset/loaders/JSONLoader.hpp
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2024 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "asset/AssetLoader.hpp"
|
||||
#include "asset/AssetDataLoader.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Dawn {
|
||||
enum class JSONLoaderState {
|
||||
INITIAL,
|
||||
LOADING_JSON,
|
||||
DONE
|
||||
};
|
||||
|
||||
class JSONLoader : public AssetLoader {
|
||||
protected:
|
||||
AssetDataLoader loader;
|
||||
enum JSONLoaderState state;
|
||||
|
||||
public:
|
||||
json data;
|
||||
|
||||
JSONLoader(const std::string name);
|
||||
void updateSync() override;
|
||||
void updateAsync() override;
|
||||
~JSONLoader();
|
||||
};
|
||||
}
|
97
src/dawn/asset/loaders/TrueTypeLoader.cpp
Normal file
97
src/dawn/asset/loaders/TrueTypeLoader.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "TrueTypeLoader.hpp"
|
||||
#include "assert/assert.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
TrueTypeLoader::TrueTypeLoader(const std::string name) :
|
||||
AssetLoader(name),
|
||||
loader(name + ".ttf")
|
||||
{
|
||||
// Init the font.
|
||||
auto ret = FT_Init_FreeType(&fontLibrary);
|
||||
assertTrue(ret == 0, "Failed to initialize FreeType library.");
|
||||
}
|
||||
|
||||
void TrueTypeLoader::updateSync() {
|
||||
if(state != TrueTypeLoaderState::ASYNC_DONE) return;
|
||||
state = TrueTypeLoaderState::SYNC_LOADING;
|
||||
|
||||
// Init all the textures.
|
||||
auto it = textures.begin();
|
||||
while(it != textures.end()) {
|
||||
auto texture = it->second.lock();
|
||||
|
||||
if(texture) {
|
||||
texture->setFace(face);
|
||||
it++;
|
||||
continue;
|
||||
}
|
||||
|
||||
it = textures.erase(it);
|
||||
}
|
||||
|
||||
// Done
|
||||
state = TrueTypeLoaderState::SYNC_DONE;
|
||||
this->loaded = true;
|
||||
}
|
||||
|
||||
void TrueTypeLoader::updateAsync() {
|
||||
if(state != TrueTypeLoaderState::INITIAL) return;
|
||||
state = TrueTypeLoaderState::ASYNC_LOADING;
|
||||
|
||||
// Load the data.
|
||||
this->loader.open();
|
||||
size_t size = loader.getSize();
|
||||
buffer = new uint8_t[size];
|
||||
|
||||
// Read the data.
|
||||
size_t readSize = loader.read(buffer, size);
|
||||
assertTrue(readSize == size, "Failed to read all data from TrueTypeLoader.");
|
||||
|
||||
// Init the font.
|
||||
auto ret = FT_New_Memory_Face(fontLibrary, buffer, size, 0, &face);
|
||||
assertTrue(ret == 0, "Failed to load font face.");
|
||||
|
||||
// Now close the asset loader
|
||||
loader.close();
|
||||
state = TrueTypeLoaderState::ASYNC_DONE;
|
||||
}
|
||||
|
||||
std::shared_ptr<TrueTypeTexture> TrueTypeLoader::getTexture(
|
||||
const uint32_t fontSize
|
||||
) {
|
||||
// Check if we have the texture already and it hasn't gone stale.
|
||||
auto it = textures.find(fontSize);
|
||||
if(it != textures.end()) {
|
||||
if(!it->second.expired()) return it->second.lock();
|
||||
textures.erase(it);
|
||||
}
|
||||
|
||||
// Create the texture.
|
||||
auto texture = std::make_shared<TrueTypeTexture>(fontSize);
|
||||
textures[fontSize] = texture;
|
||||
if(this->loaded) texture->setFace(face);
|
||||
return texture;
|
||||
}
|
||||
|
||||
TrueTypeLoader::~TrueTypeLoader() {
|
||||
if(
|
||||
this->state == TrueTypeLoaderState::SYNC_DONE ||
|
||||
this->state == TrueTypeLoaderState::SYNC_LOADING ||
|
||||
this->state == TrueTypeLoaderState::ASYNC_DONE
|
||||
) {
|
||||
FT_Done_Face(face);
|
||||
}
|
||||
|
||||
FT_Done_FreeType(fontLibrary);
|
||||
|
||||
if(buffer != nullptr) {
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
}
|
||||
}
|
57
src/dawn/asset/loaders/TrueTypeLoader.hpp
Normal file
57
src/dawn/asset/loaders/TrueTypeLoader.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "asset/AssetLoader.hpp"
|
||||
#include "asset/AssetDataLoader.hpp"
|
||||
#include "display/font/TrueTypeTexture.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
enum class TrueTypeLoaderState {
|
||||
INITIAL,
|
||||
ASYNC_LOADING,
|
||||
ASYNC_DONE,
|
||||
SYNC_LOADING,
|
||||
SYNC_DONE
|
||||
};
|
||||
|
||||
class TrueTypeLoader : public AssetLoader {
|
||||
protected:
|
||||
FT_Library fontLibrary;
|
||||
FT_Face face;
|
||||
AssetDataLoader loader;
|
||||
std::unordered_map<uint32_t, std::weak_ptr<TrueTypeTexture>> textures;
|
||||
enum TrueTypeLoaderState state = TrueTypeLoaderState::INITIAL;
|
||||
uint8_t *buffer = nullptr;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Constructs a TrueTypeLoader. You should instead use the parent
|
||||
* asset managers' abstracted load method
|
||||
*
|
||||
* @param name File name asset to load, omitting the extension.
|
||||
*/
|
||||
TrueTypeLoader(const std::string name);
|
||||
|
||||
void updateSync() override;
|
||||
void updateAsync() override;
|
||||
|
||||
/**
|
||||
* Returns the texture for the given font size.
|
||||
*
|
||||
* @param fontSize Font size to get the texture for.
|
||||
* @return Texture for the given character.
|
||||
*/
|
||||
std::shared_ptr<TrueTypeTexture> getTexture(
|
||||
const uint32_t fontSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Dispose / Cleanup the truetype asset. Will also dispose the underlying
|
||||
* truetype itself.
|
||||
*/
|
||||
~TrueTypeLoader();
|
||||
};
|
||||
}
|
@ -13,4 +13,5 @@ target_sources(${DAWN_TARGET_NAME}
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(mesh)
|
||||
add_subdirectory(shader)
|
||||
add_subdirectory(shader)
|
||||
add_subdirectory(font)
|
9
src/dawn/display/font/CMakeLists.txt
Normal file
9
src/dawn/display/font/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
TrueTypeTexture.cpp
|
||||
)
|
16
src/dawn/display/font/TrueTypeCharacter.hpp
Normal file
16
src/dawn/display/font/TrueTypeCharacter.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "dawnlibs.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
struct TrueTypeCharacter {
|
||||
glm::vec2 advance;
|
||||
glm::vec2 size;
|
||||
glm::vec2 offset;
|
||||
glm::vec4 quad;
|
||||
};
|
||||
}
|
198
src/dawn/display/font/TrueTypeTexture.cpp
Normal file
198
src/dawn/display/font/TrueTypeTexture.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "TrueTypeTexture.hpp"
|
||||
#include "assert/assert.hpp"
|
||||
#include "util/Math.hpp"
|
||||
#include "display/mesh/QuadMesh.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
TrueTypeTexture::TrueTypeTexture(const uint32_t fontSize) :
|
||||
fontSize(fontSize)
|
||||
{
|
||||
assertTrue(fontSize > 0, "Font size cannot be zero");
|
||||
texture = std::make_shared<Texture>();
|
||||
}
|
||||
|
||||
void TrueTypeTexture::setFace(const FT_Face face) {
|
||||
this->face = face;
|
||||
assertTrue(fontSize < 256, "Font size cannot be greater than 256");
|
||||
|
||||
// Set freetype font size prior to baking.
|
||||
auto ret = FT_Set_Pixel_Sizes(face, 0, fontSize);
|
||||
if(ret != 0) {
|
||||
assertUnreachable("Failed to set font size %i", ret);
|
||||
}
|
||||
|
||||
// Set the texture size
|
||||
texture->setSize(
|
||||
fontSize * 24,
|
||||
fontSize * 24,
|
||||
TextureFormat::R,
|
||||
TextureDataFormat::UNSIGNED_BYTE
|
||||
);
|
||||
|
||||
// Texture buffer
|
||||
uint8_t *buffer = new uint8_t[texture->getWidth() * texture->getHeight()];
|
||||
// Fill with zeros
|
||||
std::memset(buffer, 0, texture->getWidth() * texture->getHeight());
|
||||
|
||||
size_t offset = 0;
|
||||
struct TrueTypeCharacter info;
|
||||
int32_t textureX = 0, textureY = 0;
|
||||
int32_t rowHeight = 0;
|
||||
|
||||
// Character sets
|
||||
std::vector<wchar_t> characterBlocks;
|
||||
// Latin
|
||||
for(wchar_t c = 0x0020; c < 0x007F; c++) characterBlocks.push_back(c);
|
||||
// Latin-1 Supplement
|
||||
for(wchar_t c = 0x00A0; c < 0x00FF; c++) characterBlocks.push_back(c);
|
||||
// Latin Extended-A
|
||||
for(wchar_t c = 0x0100; c < 0x017F; c++) characterBlocks.push_back(c);
|
||||
// Latin Extended-B
|
||||
for(wchar_t c = 0x0180; c < 0x024F; c++) characterBlocks.push_back(c);
|
||||
// Hiragana
|
||||
for(wchar_t c = 0x3040; c < 0x309F; c++) characterBlocks.push_back(c);
|
||||
// Katakana
|
||||
for(wchar_t c = 0x30A0; c < 0x30FF; c++) characterBlocks.push_back(c);
|
||||
|
||||
// For each character in the character set
|
||||
for(wchar_t c : characterBlocks) {
|
||||
// Load the character
|
||||
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||
assertUnreachable("Failed to load character (1)");
|
||||
}
|
||||
|
||||
// Store the character information
|
||||
info.advance.x = (float_t)(face->glyph->advance.x >> 6);
|
||||
info.advance.y = (float_t)(face->glyph->advance.y >> 6);
|
||||
info.size = glm::vec2(face->glyph->bitmap.width, face->glyph->bitmap.rows);
|
||||
|
||||
// Determine the texture position
|
||||
if(textureX + face->glyph->bitmap.width >= texture->getWidth()) {
|
||||
textureX = 0;
|
||||
textureY += rowHeight + 2;// Tiny gap between rows
|
||||
rowHeight = face->glyph->bitmap.rows;
|
||||
} else {
|
||||
rowHeight = Math::max<int32_t>(rowHeight, face->glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
// Set the quad positions
|
||||
info.offset = glm::vec2(
|
||||
face->glyph->bitmap_left,
|
||||
-face->glyph->bitmap_top
|
||||
);
|
||||
info.quad = glm::vec4(
|
||||
textureX,
|
||||
textureY,
|
||||
textureX + face->glyph->bitmap.width,
|
||||
textureY + face->glyph->bitmap.rows
|
||||
) / glm::vec4(
|
||||
texture->getWidth(),
|
||||
texture->getHeight(),
|
||||
texture->getWidth(),
|
||||
texture->getHeight()
|
||||
);
|
||||
|
||||
// Store the cached character data.
|
||||
this->characterData[c] = info;
|
||||
|
||||
// Determine pixel offset.
|
||||
offset = textureX + (textureY * texture->getWidth());
|
||||
assertTrue(
|
||||
offset + (face->glyph->bitmap.rows * texture->getWidth()) <=
|
||||
texture->getWidth() * texture->getHeight(),
|
||||
"Font texture buffer overflow will occur."
|
||||
);
|
||||
|
||||
// Buffer pixels, we have to do this one row at a time due to the
|
||||
// differences in width between the glyph and the texture.
|
||||
const size_t countPerRow = face->glyph->bitmap.width;
|
||||
int32_t i = 0;
|
||||
while(i != face->glyph->bitmap.rows) {
|
||||
std::memcpy(
|
||||
buffer + offset + (i * texture->getWidth()),
|
||||
face->glyph->bitmap.buffer + (i * countPerRow),
|
||||
countPerRow
|
||||
);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Increment textureX
|
||||
textureX += face->glyph->bitmap.width + 2;// I add a tiny gap between chars
|
||||
}
|
||||
|
||||
this->texture->buffer(buffer);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
struct TrueTypeCharacter TrueTypeTexture::getCharacterData(wchar_t c) {
|
||||
return this->characterData[c];
|
||||
}
|
||||
|
||||
glm::vec2 TrueTypeTexture::bufferStringToMesh(
|
||||
std::shared_ptr<Mesh> mesh,
|
||||
const std::wstring text,
|
||||
glm::vec2 &position,
|
||||
bool_t flipY
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh must be supplied and not null");
|
||||
assertTrue(text.size() > 0, "Text must be at least one character long.");
|
||||
|
||||
// Create mesh buffers
|
||||
mesh->createBuffers(
|
||||
text.length() * QUAD_VERTICE_COUNT,
|
||||
text.length() * QUAD_INDICE_COUNT
|
||||
);
|
||||
|
||||
// Foreach char
|
||||
size_t i = 0;
|
||||
glm::vec2 size = { 0, 0 };
|
||||
for(wchar_t c : text) {
|
||||
// Get the character data
|
||||
auto info = this->getCharacterData(c);
|
||||
|
||||
// Buffer the quad
|
||||
glm::vec4 quad = glm::vec4(
|
||||
position.x,
|
||||
position.y,
|
||||
position.x + info.size.x,
|
||||
position.y + info.size.y
|
||||
);
|
||||
if(flipY) {
|
||||
QuadMesh::buffer(
|
||||
mesh,
|
||||
quad,
|
||||
glm::vec4(
|
||||
info.quad.x,
|
||||
info.quad.w,
|
||||
info.quad.z,
|
||||
info.quad.y
|
||||
),
|
||||
i * QUAD_VERTICE_COUNT,
|
||||
i * QUAD_INDICE_COUNT
|
||||
);
|
||||
} else {
|
||||
QuadMesh::buffer(
|
||||
mesh,
|
||||
quad,
|
||||
info.quad,
|
||||
i * QUAD_VERTICE_COUNT,
|
||||
i * QUAD_INDICE_COUNT
|
||||
);
|
||||
}
|
||||
position += info.advance;
|
||||
size += info.advance;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
TrueTypeTexture::~TrueTypeTexture() {
|
||||
}
|
66
src/dawn/display/font/TrueTypeTexture.hpp
Normal file
66
src/dawn/display/font/TrueTypeTexture.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "display/Texture.hpp"
|
||||
#include "TrueTypeCharacter.hpp"
|
||||
#include "display/mesh/Mesh.hpp"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
namespace Dawn {
|
||||
class TrueTypeTexture final {
|
||||
private:
|
||||
FT_Face face;
|
||||
|
||||
public:
|
||||
uint32_t fontSize;
|
||||
std::shared_ptr<Texture> texture;
|
||||
std::unordered_map<wchar_t, struct TrueTypeCharacter> characterData;
|
||||
|
||||
/**
|
||||
* Construct a new New True Type Face Texture object
|
||||
*
|
||||
* @param fontSize Size of the font.
|
||||
*/
|
||||
TrueTypeTexture(const uint32_t fontSize);
|
||||
|
||||
/**
|
||||
* Sets the face for this texture.
|
||||
*
|
||||
* @param face Face to set.
|
||||
*/
|
||||
void setFace(const FT_Face face);
|
||||
|
||||
/**
|
||||
* Returns the character data for the given character.
|
||||
*
|
||||
* @param c Character to get data for.
|
||||
* @return The Character data for the given character.
|
||||
*/
|
||||
struct TrueTypeCharacter getCharacterData(wchar_t c);
|
||||
|
||||
/**
|
||||
* Buffers a string to the given mesh.
|
||||
*
|
||||
* @param mesh Mesh to buffer to.
|
||||
* @param text Text to buffer.
|
||||
* @param position Position to buffer to.
|
||||
* @param flipY Whether or not to flip the Y axis.
|
||||
* @return The size of the string.
|
||||
*/
|
||||
glm::vec2 bufferStringToMesh(
|
||||
std::shared_ptr<Mesh> mesh,
|
||||
const std::wstring text,
|
||||
glm::vec2 &position,
|
||||
bool_t flipY = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Destroys this true type face texture.
|
||||
*/
|
||||
~TrueTypeTexture();
|
||||
};
|
||||
}
|
@ -5,5 +5,6 @@
|
||||
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
UILabel.cpp
|
||||
UIRectangle.cpp
|
||||
)
|
175
src/dawn/ui/elements/UILabel.cpp
Normal file
175
src/dawn/ui/elements/UILabel.cpp
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "UILabel.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
void UILabel::getSelfQuads(UICanvas &ctx) {
|
||||
std::vector<struct UIShaderQuad> quads;
|
||||
if(this->texture == nullptr || this->text.empty()) return;
|
||||
|
||||
glm::vec4 quad;
|
||||
glm::vec2 pos = glm::vec2(0, this->texture->fontSize);
|
||||
bool_t lastCharWasSpace = false;
|
||||
|
||||
for(size_t i = 0; i < text.size(); i++) {
|
||||
wchar_t c = text[i];
|
||||
auto info = texture->getCharacterData(c);
|
||||
|
||||
// Newline(s)
|
||||
if(c == L'\n') {
|
||||
pos.x = 0;
|
||||
pos.y += this->texture->fontSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Spaces
|
||||
if(c == L' ') {
|
||||
pos.x += info.advance.x;
|
||||
lastCharWasSpace = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Word Wrap
|
||||
if(wordWrap) {
|
||||
if(lastCharWasSpace) {
|
||||
// Scan ahead to next space
|
||||
float_t wordWidth = pos.x;// Start at current position and scan ahead.
|
||||
for(size_t j = i; j < text.size(); j++) {
|
||||
wchar_t c2 = text[j];
|
||||
if(c2 == L' ' || c2 == L'\n') {
|
||||
break;// If we hit another space, we are OK.
|
||||
}
|
||||
|
||||
// Will this character fit on the row? If not the whole word will wrap.
|
||||
auto info2 = texture->getCharacterData(c);
|
||||
wordWidth += info.advance.x;
|
||||
if(wordWidth > size.x) {
|
||||
pos.x = 0;
|
||||
pos.y += this->texture->fontSize;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lastCharWasSpace = false;
|
||||
}
|
||||
// } else if(pos.x + info.size.x > subAlignedPosition.x + size.x) {
|
||||
// // Not word wrap, but instead just overflow characters.
|
||||
// pos.x = 0;
|
||||
// pos.y += this->texture->fontSize;
|
||||
}
|
||||
|
||||
ctx.addQuad(
|
||||
{
|
||||
subAlignedPosition.x + pos.x + info.offset.x,
|
||||
subAlignedPosition.y + pos.y + info.offset.y,
|
||||
subAlignedPosition.x + pos.x + info.size.x + info.offset.x,
|
||||
subAlignedPosition.y + pos.y + info.size.y + info.offset.y
|
||||
},
|
||||
{
|
||||
info.quad.x,
|
||||
info.quad.y,
|
||||
info.quad.z,
|
||||
info.quad.w
|
||||
},
|
||||
this->color,
|
||||
UIShaderQuadStyle::FONT,
|
||||
texture->texture
|
||||
);
|
||||
pos += info.advance;
|
||||
}
|
||||
}
|
||||
|
||||
float_t UILabel::getContentWidth() {
|
||||
if(this->texture == nullptr || this->text.empty()) return 0.0f;
|
||||
|
||||
float_t lineWidth = 0.0f;
|
||||
float_t width = 0.0f;
|
||||
for(wchar_t c : text) {
|
||||
if(c == L'\n') {
|
||||
width = Math::max<float_t>(width, lineWidth);
|
||||
lineWidth = 0.0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto info = texture->getCharacterData(c);
|
||||
lineWidth += info.advance.x;
|
||||
if(
|
||||
this->hasExplicitWidth() &&
|
||||
lineWidth >= size.x
|
||||
) return size.x;
|
||||
}
|
||||
width = Math::max<float_t>(width, lineWidth);
|
||||
return width;
|
||||
}
|
||||
|
||||
float_t UILabel::getContentHeight() {
|
||||
if(this->texture == nullptr || this->text.empty()) return 0.0f;
|
||||
|
||||
float_t height = this->texture->fontSize;
|
||||
float_t lineWidth = 0.0f;
|
||||
bool_t lastCharWasSpace = false;
|
||||
|
||||
for(wchar_t c : text) {
|
||||
if(c == L'\n') {
|
||||
height += this->texture->fontSize;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto info = texture->getCharacterData(c);
|
||||
|
||||
if(c == L' ') {
|
||||
lineWidth += info.advance.x;
|
||||
lastCharWasSpace = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(wordWrap) {
|
||||
if(lastCharWasSpace) {
|
||||
// Scan ahead to next space
|
||||
float_t wordWidth = lineWidth;// Start at current position and scan ahead.
|
||||
for(size_t j = 0; j < text.size(); j++) {
|
||||
wchar_t c2 = text[j];
|
||||
if(c2 == L' ' || c2 == L'\n') {
|
||||
break;// If we hit another space, we are OK.
|
||||
}
|
||||
|
||||
// Will this character fit on the row? If not the whole word will wrap.
|
||||
auto info2 = texture->getCharacterData(c);
|
||||
wordWidth += info.advance.x;
|
||||
if(wordWidth > size.x) {
|
||||
height += this->texture->fontSize;
|
||||
lineWidth = 0.0f;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
lastCharWasSpace = false;
|
||||
}
|
||||
// } else if(lineWidth + info.size.x > size.x) {
|
||||
// height += this->texture->fontSize;
|
||||
// lineWidth = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return height;
|
||||
}
|
||||
|
||||
std::shared_ptr<TrueTypeTexture> UILabel::getFont() {
|
||||
return this->texture;
|
||||
}
|
||||
|
||||
std::wstring UILabel::getText() {
|
||||
return this->text;
|
||||
}
|
||||
|
||||
void UILabel::setFont(std::shared_ptr<TrueTypeTexture> texture) {
|
||||
this->texture = texture;
|
||||
}
|
||||
|
||||
void UILabel::setText(const std::wstring &text) {
|
||||
this->text = text;
|
||||
}
|
54
src/dawn/ui/elements/UILabel.hpp
Normal file
54
src/dawn/ui/elements/UILabel.hpp
Normal file
@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "ui/UISubAlignableElement.hpp"
|
||||
#include "display/font/TrueTypeTexture.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
class UILabel final : public UISubAlignableElement {
|
||||
private:
|
||||
std::shared_ptr<TrueTypeTexture> texture = nullptr;
|
||||
std::wstring text = L"Hello World";
|
||||
|
||||
protected:
|
||||
void getSelfQuads(UICanvas &ctx) override;
|
||||
|
||||
public:
|
||||
bool_t wordWrap = true;
|
||||
struct Color color = COLOR_WHITE;
|
||||
|
||||
float_t getContentWidth() override;
|
||||
float_t getContentHeight() override;
|
||||
|
||||
/**
|
||||
* Returns the font used for this label.
|
||||
*
|
||||
* @return The font used for this label.
|
||||
*/
|
||||
std::shared_ptr<TrueTypeTexture> getFont();
|
||||
|
||||
/**
|
||||
* Returns the text used for this label.
|
||||
*
|
||||
* @return The text used for this label.
|
||||
*/
|
||||
std::wstring getText();
|
||||
|
||||
/**
|
||||
* Sets the font to use for this label.
|
||||
*
|
||||
* @param texture TrueType texture to use for this label.
|
||||
*/
|
||||
void setFont(std::shared_ptr<TrueTypeTexture> texture);
|
||||
|
||||
/**
|
||||
* Sets the text to use for this label.
|
||||
*
|
||||
* @param text The text to use for this label.
|
||||
*/
|
||||
void setText(const std::wstring &text);
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user