/* * 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& charset, const std::optional& pngOutput) { Generate(fontFile, charset, pngOutput); } void BitmapFontAtlasGenerator::GenerateAtlas(const Array& fontData, const std::set& charset, const std::optional& pngOutput) { Generate(fontData, charset, pngOutput); } void BitmapFontAtlasGenerator::Generate(const std::variant>& source, const std::set& chset, const std::optional& pngOutput) { if (chset.empty()) { Logger::APP->info("Charset is empty. Nothing to generate."); return; } m_atlasData = std::make_shared(); const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source); if (m_pixelSizeConfig.isPixelSize) { FT_Set_Pixel_Sizes(face.get(), 0, m_pixelSizeConfig.size); } else { const float pixelSize = (m_pixelSizeConfig.size * m_pixelSizeConfig.dpi) / 72.0f; FT_Set_Pixel_Sizes(face.get(), 0, pixelSize); } auto [allGlyphs, area] = InitGlyphsForPacking(chset, face); const double atlasWidth = ceil(sqrt(area)); std::vector 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, double> BitmapFontAtlasGenerator::InitGlyphsForPacking(const std::set& chset, const FtFaceRecPtr& face) { FT_Error error = 0; double area = 0; std::vector 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 }; } }