/* * 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.reset(new AtlasData); const std::string sourceName = (std::holds_alternative(source) ? std::get<0>(source) : "Binary array"); const auto& [lib, face] = FontAtlasGeneratorBase::InitFreetype(source); FT_FaceRec* pFace = face.get(); // TODO: add flexibility to set your own size const Math::Vector2ui cellSize = { 24, 24 }; // set pixel width/height lower than glyph size above, otherwise some glyphs will be cropped or some overlapping will be present FT_Set_Pixel_Sizes(pFace, 0, cellSize.y - cellSize.y / 3); const double sq = std::sqrt(chset.size()); const size_t glyphsPerRow = static_cast(sq) + (sq - static_cast(sq) != 0); const size_t rows = (chset.size() / glyphsPerRow) + (chset.size() % glyphsPerRow != 0); const Math::Vector2ui atlasResolution = { glyphsPerRow * cellSize.x, rows * cellSize.y }; // 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. / pFace->units_per_EM); SetupAtlasData(atlasResolution, pFace->height * scaleFactor, FontAtlasType::BITMAP); size_t loadedGlyphs = 0; FT_Error error = 0; int currentPosX = 0; int currentPosY = 0; Math::Vector2ui gridPos = { 0, 0 }; for (uint32_t codepoint : chset) { error = FT_Load_Char(pFace, codepoint, FT_LOAD_RENDER); if (error) { Logger::APP->error("FT_Load_Char for codepoint {} failed while reading from source {}", codepoint, sourceName); continue; } FT_GlyphSlot slot = face->glyph; if (slot->bitmap.width > cellSize.x || slot->bitmap.rows > cellSize.y) { Logger::APP->warn("Glyph size exceeds grid cell size: {}x{} exceeds {}x{}", slot->bitmap.width, slot->bitmap.rows, cellSize.x, cellSize.y); } const size_t firstGlyphByte = (gridPos.y * cellSize.x + gridPos.x * atlasResolution.x * cellSize.y); for (int row = 0; row < slot->bitmap.rows; row++) { for (int col = 0; col < slot->bitmap.width; col++) { m_atlasData->img->data[firstGlyphByte + row * atlasResolution.x + col] = slot->bitmap.buffer[(slot->bitmap.rows - 1 - row) * slot->bitmap.pitch + col]; } } GlyphInfo& glyphInfo = m_atlasData->glyphs[codepoint]; const Math::Vector2d glyphMetrics = { slot->metrics.width * scaleFactor, slot->metrics.height * scaleFactor }; const Math::Vector2d glyphBearing = { slot->metrics.horiBearingX * scaleFactor, slot->metrics.horiBearingY * scaleFactor }; // metrics are 1/64 of a pixel constexpr double toPixelScaler = 1. / 64; const Math::Vector2d whPixel = { static_cast(slot->metrics.width * toPixelScaler), static_cast(slot->metrics.height * toPixelScaler) }; Math::AABB glyphAtlasAABB(Math::Vector3f(currentPosX, currentPosY, 0), Math::Vector3f(currentPosX + whPixel.x, currentPosY + whPixel.y, 0)); SetGlyphData(glyphInfo, glyphBearing, glyphMetrics, glyphAtlasAABB, slot->advance.x * scaleFactor); currentPosX += cellSize.x; loadedGlyphs++; if (currentPosX + cellSize.x > atlasResolution.x) { currentPosX = 0; currentPosY += cellSize.y; gridPos.y = 0; gridPos.x++; } else { gridPos.y++; } } if (pngOutput) { SavePng(*pngOutput); } Logger::APP->debug("Created atlas with {} glyphs, {} glyphs could not be loaded", loadedGlyphs, chset.size() - loadedGlyphs); } }