175 lines
6.3 KiB
C++
175 lines
6.3 KiB
C++
/*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#include "FontAtlasGeneratorBase.hpp"
|
|
#include "Base/Logger.hpp"
|
|
#include "Extensions/STBZlibCompressor.hpp"
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#define STBI_MSC_SECURE_CRT
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#define STBIW_ZLIB_COMPRESS Extensions::STBZlibCompressor
|
|
#include <stb_image_write.h>
|
|
|
|
namespace OpenVulkano::Scene
|
|
{
|
|
std::pair<FtLibraryRecPtr, FtFaceRecPtr>
|
|
FontAtlasGeneratorBase::InitFreetype(const std::variant<std::string, Array<char>>& source)
|
|
{
|
|
FT_Library library;
|
|
auto error = FT_Init_FreeType(&library);
|
|
if (error)
|
|
{
|
|
throw std::runtime_error(fmt::format("Could not initalize freetype library. {}", GetFreetypeErrorDescription(error)));
|
|
}
|
|
FT_Face face;
|
|
if (std::holds_alternative<std::string>(source))
|
|
{
|
|
error = FT_New_Face(library, std::get<0>(source).c_str(), 0, &face);
|
|
}
|
|
else
|
|
{
|
|
auto& arr = std::get<1>(source);
|
|
error = FT_New_Memory_Face(library, (const FT_Byte*) (arr.Data()), arr.Size(), 0, &face);
|
|
}
|
|
if (error == FT_Err_Unknown_File_Format)
|
|
{
|
|
throw std::runtime_error("Unknown font file format\n");
|
|
}
|
|
else if (error)
|
|
{
|
|
throw std::runtime_error(fmt::format("Font file could not be opened or read or it's corrupted. {}", GetFreetypeErrorDescription(error)));
|
|
}
|
|
|
|
// some fancy font without unicode charmap
|
|
if (face->charmap == nullptr)
|
|
{
|
|
throw std::runtime_error("Selected font doesn't contain unicode charmap");
|
|
}
|
|
return { FtLibraryRecPtr(library), FtFaceRecPtr(face) };
|
|
}
|
|
|
|
std::string FontAtlasGeneratorBase::GetFreetypeErrorDescription(FT_Error error)
|
|
{
|
|
if (const char* s = FT_Error_String(error))
|
|
{
|
|
return s;
|
|
}
|
|
return fmt::format("Error code is {}", error);
|
|
}
|
|
|
|
void FontAtlasGeneratorBase::SaveAtlasMetadataInfo(const std::string& outputFile, bool packIntoSingleFile) const
|
|
{
|
|
if (m_atlasData->glyphs.empty())
|
|
{
|
|
Logger::DATA->info("No glyphs loaded. Nothing to save.");
|
|
return;
|
|
}
|
|
std::string fileName = outputFile;
|
|
uint32_t packedFlag = packIntoSingleFile;
|
|
if (packIntoSingleFile)
|
|
{
|
|
std::filesystem::path fPath(fileName);
|
|
fileName = (fPath.parent_path() / fPath.stem()).string() + "_packed.png";
|
|
SavePng(fileName);
|
|
}
|
|
std::fstream fs(fileName.c_str(), std::ios_base::out | std::ios_base::binary | (packedFlag ? std::ios_base::app : std::ios_base::trunc));
|
|
fs.write(reinterpret_cast<const char*>(&m_atlasData->meta), sizeof(AtlasMetadata));
|
|
uint64_t metadataBytes = sizeof(AtlasMetadata);
|
|
for (const auto& [key, val] : m_atlasData->glyphs)
|
|
{
|
|
fs.write(reinterpret_cast<const char*>(&key), sizeof(uint32_t));
|
|
fs.write(reinterpret_cast<const char*>(&val.pos), sizeof(GlyphInfo::pos));
|
|
fs.write(reinterpret_cast<const char*>(&val.pos), sizeof(GlyphInfo::pos)); // TODO remove this after cleaning up the atlas writing code
|
|
fs.write(reinterpret_cast<const char*>(&val.uv), sizeof(GlyphInfo::uv));
|
|
fs.write(reinterpret_cast<const char*>(&val.advance), sizeof(GlyphInfo::advance));
|
|
fs.write(reinterpret_cast<const char*>(&val.advance), sizeof(GlyphInfo::advance)); // TODO remove this afer cleaning up the atlas writing code
|
|
metadataBytes += sizeof(uint32_t);
|
|
metadataBytes += sizeof(GlyphInfo);
|
|
}
|
|
fs.write(reinterpret_cast<const char*>(&metadataBytes), sizeof(uint64_t));
|
|
fs.write(reinterpret_cast<const char*>(&packedFlag), sizeof(uint32_t));
|
|
fs.close();
|
|
}
|
|
|
|
void FontAtlasGeneratorBase::SavePng(std::string output) const
|
|
{
|
|
stbi_flip_vertically_on_write(1);
|
|
if (std::filesystem::path(output).extension() != ".png")
|
|
{
|
|
output += ".png";
|
|
}
|
|
stbi_write_png(output.c_str(), m_atlasData->img->resolution.x, m_atlasData->img->resolution.y, m_channelsCount,
|
|
m_atlasData->img->data.Data(), m_channelsCount * m_atlasData->img->resolution.x);
|
|
}
|
|
|
|
void FontAtlasGeneratorBase::SetupAtlasData(Math::Vector2ui textureResolution, double lineHeight,
|
|
FontAtlasType::Type atlasType)
|
|
{
|
|
// generate texture
|
|
m_atlasData->img = std::make_unique<Image::Image>();
|
|
m_atlasData->img->resolution = Math::Vector3ui(textureResolution, 1);
|
|
m_atlasData->img->dataFormat = m_channelsCount == 1 ? DataFormat::R8_UNORM : DataFormat::R8G8B8A8_UNORM;
|
|
m_atlasData->img->data = Array<uint8_t>(m_atlasData->img->dataFormat.CalculatedSize(textureResolution.x, textureResolution.y));
|
|
m_atlasData->texture.resolution = m_atlasData->img->resolution;
|
|
m_atlasData->texture.textureBuffer = m_atlasData->img->data.Data();
|
|
m_atlasData->texture.format = m_atlasData->img->dataFormat;
|
|
m_atlasData->texture.size = m_atlasData->img->data.Size();
|
|
m_atlasData->meta.atlasType = atlasType;
|
|
m_atlasData->meta.lineHeight = lineHeight;
|
|
if (atlasType == FontAtlasType::BITMAP)
|
|
{
|
|
m_atlasData->texture.m_samplerConfig = &SamplerConfig::NEAREST;
|
|
}
|
|
}
|
|
|
|
void FontAtlasGeneratorBase::SetGlyphData(GlyphInfo& info, Math::Vector2d bearing, Math::Vector2d size,
|
|
const Math::AABB& aabb, double advance)
|
|
{
|
|
const double l = aabb.min.x;
|
|
const double r = aabb.max.x;
|
|
const double t = aabb.max.y;
|
|
const double b = aabb.min.y;
|
|
|
|
info.pos[0].x = bearing.x;
|
|
info.pos[0].y = size.y - bearing.y;
|
|
info.uv[0].x = l / m_atlasData->texture.resolution.x;
|
|
info.uv[0].y = b / m_atlasData->texture.resolution.y;
|
|
|
|
info.pos[1].x = bearing.x + size.x;
|
|
info.pos[1].y = size.y - bearing.y;
|
|
info.uv[1].x = r / m_atlasData->texture.resolution.x;
|
|
info.uv[1].y = b / m_atlasData->texture.resolution.y;
|
|
|
|
info.pos[2].x = bearing.x + size.x;
|
|
info.pos[2].y = bearing.y;
|
|
info.uv[2].x = r / m_atlasData->texture.resolution.x;
|
|
info.uv[2].y = t / m_atlasData->texture.resolution.y;
|
|
|
|
info.pos[3].x = bearing.x;
|
|
info.pos[3].y = bearing.y;
|
|
info.uv[3].x = l / m_atlasData->texture.resolution.x;
|
|
info.uv[3].y = t / m_atlasData->texture.resolution.y;
|
|
|
|
info.advance = advance;
|
|
}
|
|
|
|
std::set<uint32_t> FontAtlasGeneratorBase::LoadAllGlyphs(const std::variant<std::string, Array<char>>& data)
|
|
{
|
|
const auto& [lib, face] = InitFreetype(data);
|
|
std::set<uint32_t> s;
|
|
FT_UInt glyphIndex;
|
|
FT_ULong unicode = FT_Get_First_Char(face.get(), &glyphIndex);
|
|
while (glyphIndex != 0)
|
|
{
|
|
s.insert(unicode);
|
|
unicode = FT_Get_Next_Char(face.get(), unicode, &glyphIndex);
|
|
}
|
|
return s;
|
|
}
|
|
|
|
}
|