/* * 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 { using namespace OpenVulkano; struct GlyphForPacking { uint32_t code; size_t firstGlyphByteInAtlas; Math::Vector2d atlasPos; Math::Vector2i wh; }; struct Shelf { Shelf(uint32_t width, uint32_t height) : m_width(width), m_height(height), m_remainingWidth(width) {} bool HasSpaceForGlyph(uint32_t glyphWidth, uint32_t glyphHeight) const { return m_remainingWidth >= glyphWidth && m_height >= glyphHeight; } uint32_t GetWidth() const { return m_width; } uint32_t GetHeight() const { return m_height; } uint32_t GetNextGlyphPos() const { return m_nextGlyphPos; }; uint32_t GetOccupiedSize() const { return ((m_width - m_remainingWidth) * m_height); } std::optional AddGlyph(uint32_t glyphWidth, uint32_t glyphHeight) { if (!HasSpaceForGlyph(glyphWidth, glyphHeight)) { return {}; } uint32_t insertionPos = m_nextGlyphPos; m_nextGlyphPos += glyphWidth; m_remainingWidth -= glyphWidth; return insertionPos; } private: uint32_t m_width; uint32_t m_height; uint32_t m_remainingWidth; uint32_t m_nextGlyphPos = 0; }; std::vector CreateShelves(uint32_t atlasWidth, std::vector& glyphs, const Scene::FtFaceRecPtr& face) { std::vector shelves; for (GlyphForPacking& glyph : glyphs) { FT_Error error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER); if (error) { continue; } FT_GlyphSlot slot = face->glyph; bool needNewShelf = true; uint32_t totalPrevShelvesHeight = 0; for (Shelf& shelf : shelves) { if (std::optional insertionPosX = shelf.AddGlyph(glyph.wh.x, glyph.wh.y)) { glyph.firstGlyphByteInAtlas = *insertionPosX + (totalPrevShelvesHeight * atlasWidth); glyph.atlasPos.x = *insertionPosX; glyph.atlasPos.y = totalPrevShelvesHeight; needNewShelf = false; break; } totalPrevShelvesHeight += shelf.GetHeight(); } if (needNewShelf) { shelves.emplace_back(atlasWidth, glyph.wh.y); shelves.back().AddGlyph(glyph.wh.x, glyph.wh.y); glyph.firstGlyphByteInAtlas = totalPrevShelvesHeight * atlasWidth; glyph.atlasPos.x = 0; glyph.atlasPos.y = totalPrevShelvesHeight; } } return shelves; } } 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); } 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::APP->error("FT_Load_Char for codepoint {} has failed. {}", codepoint, GetFreetypeErrorDescription(error)); continue; } FT_GlyphSlot slot = face->glyph; unsigned int h = slot->bitmap.rows; unsigned int w = slot->bitmap.width; GlyphForPacking& glyph = allGlyphs.emplace_back(); glyph.code = codepoint; glyph.wh = { slot->bitmap.width, slot->bitmap.rows }; area += h * w; } const double sq = ceil(sqrt(area)); std::sort(allGlyphs.begin(), allGlyphs.end(), [](const GlyphForPacking& a, const GlyphForPacking& b) { return a.wh.y > b.wh.y; }); std::vector shelves = ::CreateShelves(sq, allGlyphs, face); uint32_t atlasHeight = 0; std::for_each(shelves.begin(), shelves.end(), [&](const Shelf& shelf) { atlasHeight += shelf.GetHeight(); }); const Math::Vector2ui atlasResolution = { sq, 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) { error = FT_Load_Char(face.get(), glyph.code, FT_LOAD_RENDER); if (error) { Logger::APP->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++) { for (int col = 0; col < slot->bitmap.width; col++) { m_atlasData->img->data[glyph.firstGlyphByteInAtlas + row * atlasResolution.x + col] = slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch + col]; } } 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::APP->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs); } }