Files
OpenVulkano/openVulkanoCpp/Scene/BitmapFontAtlasGenerator.cpp
2025-01-05 18:14:29 +02:00

113 lines
4.7 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 "BitmapFontAtlasGenerator.hpp"
#include "Base/Logger.hpp"
namespace OpenVulkano::Scene
{
void BitmapFontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
Generate(fontFile, charset, pngOutput);
}
void BitmapFontAtlasGenerator::GenerateAtlas(const Array<char>& fontData, const std::set<uint32_t>& charset,
const std::optional<std::string>& pngOutput)
{
Generate(fontData, charset, pngOutput);
}
void BitmapFontAtlasGenerator::Generate(const std::variant<std::string, Array<char>>& source,
const std::set<uint32_t>& chset,
const std::optional<std::string>& pngOutput)
{
if (chset.empty())
{
Logger::APP->info("Charset is empty. Nothing to generate.");
return;
}
m_atlasData = std::make_shared<AtlasData>();
const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source);
FT_Set_Pixel_Sizes(face.get(), 0, m_pixelSizeConfig.CalculatePixelSize());
auto [allGlyphs, area] = InitGlyphsForPacking(chset, face);
const double atlasWidth = ceil(sqrt(area));
std::vector<Shelf> shelves = Shelf::CreateShelves(atlasWidth, allGlyphs, face);
uint32_t atlasHeight = 0;
std::for_each(shelves.begin(), shelves.end(), [&](const Shelf& shelf) { atlasHeight += shelf.GetHeight(); });
const Math::Vector2ui atlasResolution = { atlasWidth, atlasHeight };
// same as in msdfgen lib by default. see import-font.h for reference
// TODO: probably also support keeping coordinates as the integer values native to the font file
// but since some algorithms have already been implemented for EM_NORMALIZED mode, currently there is no support for default font metrics (ints)
// The coordinates will be normalized to the em size, i.e. 1 = 1 em
const double scaleFactor = (1. / face->units_per_EM);
SetupAtlasData(atlasResolution, face->height * scaleFactor, FontAtlasType::BITMAP);
size_t loadedGlyphs = 0;
for (const GlyphForPacking& glyph : allGlyphs)
{
FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER);
if (error)
{
Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", glyph.code,
GetFreetypeErrorDescription(error));
continue;
}
FT_GlyphSlot slot = face->glyph;
for (int row = 0; row < slot->bitmap.rows; row++)
{
std::memcpy(&m_atlasData->img->data[glyph.firstGlyphByteInAtlas + row * atlasResolution.x],
&slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch],
slot->bitmap.width);
}
GlyphInfo& glyphInfo = m_atlasData->glyphs[glyph.code];
const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor,
slot->metrics.height * scaleFactor };
const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor,
slot->metrics.horiBearingY * scaleFactor };
Math::AABB glyphAtlasAABB(Math::Vector3f(glyph.atlasPos.x, glyph.atlasPos.y, 0), Math::Vector3f(glyph.atlasPos.x + slot->bitmap.width, glyph.atlasPos.y + slot->bitmap.rows, 0));
SetGlyphData(glyphInfo, glyphBearing, glyphMetrics, glyphAtlasAABB, slot->advance.x * scaleFactor);
loadedGlyphs++;
}
if (pngOutput)
{
SavePng(*pngOutput);
}
Logger::SCENE->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs);
}
std::pair<std::vector<GlyphForPacking>, double>
BitmapFontAtlasGenerator::InitGlyphsForPacking(const std::set<uint32_t>& chset, const FtFaceRecPtr& face)
{
FT_Error error = 0;
double area = 0;
std::vector<GlyphForPacking> allGlyphs;
allGlyphs.reserve(chset.size());
for (uint32_t codepoint : chset)
{
error = FT_Load_Char(face.get(), codepoint, FT_LOAD_RENDER);
if (error)
{
Logger::SCENE->error("FT_Load_Char for codepoint {} has failed. {}", codepoint,
GetFreetypeErrorDescription(error));
continue;
}
FT_GlyphSlot slot = face->glyph;
GlyphForPacking& glyph = allGlyphs.emplace_back(codepoint, Math::Vector2ui(slot->bitmap.width, slot->bitmap.rows));
area += slot->bitmap.rows * slot->bitmap.width;
}
std::sort(allGlyphs.begin(), allGlyphs.end(),
[](const GlyphForPacking& a, const GlyphForPacking& b) { return a.size.y > b.size.y; });
return { allGlyphs, area };
}
}