/* * 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 "FontAtlasGenerator.hpp" #include "Base/Logger.hpp" #include "Scene/AtlasMetadata.hpp" #include namespace OpenVulkano::Scene { using namespace msdfgen; using namespace msdf_atlas; void FontAtlasGenerator::GenerateAtlas(const std::string& fontFile, const Charset& charset, const std::optional& pngOutput) { if (charset.empty()) { Logger::RENDER->info("Provided charset is empty. Atlas will not be generated"); return; } // TODO: dynamic atlas and add only those symbols which are not present yet in current atlas FreetypeHandle* ft = initializeFreetype(); if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } FontHandle* font = loadFont(ft, fontFile.data()); if (!font) { deinitializeFreetype(ft); throw std::runtime_error(fmt::format("Failed to load font from file {0}", fontFile.data())); } Generate(ft, font, charset, pngOutput); } void FontAtlasGenerator::GenerateAtlas(const msdfgen::byte* fontData, int length, const Charset& charset, const std::optional& pngOutput) { FreetypeHandle* ft = initializeFreetype(); if (!ft) { throw std::runtime_error("Failed to initialize freetype"); } FontHandle* font = loadFontData(ft, fontData, length); if (!font) { deinitializeFreetype(ft); throw std::runtime_error("Failed to load font data from given buffer"); } Generate(ft, font, charset, pngOutput); } void FontAtlasGenerator::SaveAtlasMetadataInfo(const std::string& outputFile) const { if (m_symbols.empty()) { Logger::DATA->info("No glyphs loaded. Nothing to save."); return; } std::fstream fs(outputFile.c_str(), std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); fs.write(reinterpret_cast(&m_meta), sizeof(AtlasMetadata)); for (const auto& [key, val] : m_symbols) { fs.write(reinterpret_cast(&key), sizeof(uint32_t)); fs.write(reinterpret_cast(&val), sizeof(GlyphInfo)); } } void FontAtlasGenerator::Generate(FreetypeHandle* ft, FontHandle* font, const Charset& chset, const std::optional& pngOutput) { m_symbols.clear(); std::vector glyphsGeometry; // FontGeometry is a helper class that loads a set of glyphs from a single font. FontGeometry fontGeometry(&glyphsGeometry); fontGeometry.loadCharset(font, 1, chset); TightAtlasPacker packer; packer.setDimensionsConstraint(DimensionsConstraint::SQUARE); int width = 1024, height = 1024; packer.setDimensions(width, height); // more value - more sdf impact packer.setPixelRange(26.0); packer.setMiterLimit(1.0); packer.pack(glyphsGeometry.data(), glyphsGeometry.size()); m_generator.resize(width, height); GeneratorAttributes attributes; m_generator.setAttributes(attributes); m_generator.setThreadCount(4); m_generator.generate(glyphsGeometry.data(), glyphsGeometry.size()); int idx = 0; const BitmapConstRef& storage = m_generator.atlasStorage(); m_atlasTex.resolution = Math::Vector3ui(storage.width, storage.height, 1); m_atlasTex.textureBuffer = (msdfgen::byte*) storage.pixels; m_atlasTex.format = OpenVulkano::DataFormat::R8_UNORM; m_atlasTex.size = storage.width * storage.height * 1; // 1 channel m_meta.lineHeight = fontGeometry.getMetrics().lineHeight; struct Bbox { double l = 0, r = 0, t = 0, b = 0; }; for (const auto& glyph: glyphsGeometry) { GlyphInfo& info = m_symbols[glyph.getCodepoint()]; const GlyphBox& glyphBox = m_generator.getLayout()[idx++]; Bbox glyphBaselineBbox, glyphAtlasBbox; glyph.getQuadPlaneBounds(glyphBaselineBbox.l, glyphBaselineBbox.b, glyphBaselineBbox.r, glyphBaselineBbox.t); glyph.getQuadAtlasBounds(glyphAtlasBbox.l, glyphAtlasBbox.b, glyphAtlasBbox.r, glyphAtlasBbox.t); double bearingX = glyphBox.bounds.l; double bearingY = glyphBox.bounds.t; double w = glyphBaselineBbox.r - glyphBaselineBbox.l; double h = glyphBaselineBbox.t - glyphBaselineBbox.b; double l = glyphAtlasBbox.l; double r = glyphAtlasBbox.r; double t = glyphAtlasBbox.t; double b = glyphAtlasBbox.b; info.xyz[0].x = bearingX; info.xyz[0].y = h - bearingY; info.xyz[0].z = 1; info.uv[0].x = l / m_atlasTex.resolution.x; info.uv[0].y = b / m_atlasTex.resolution.y; info.xyz[1].x = bearingX + w; info.xyz[1].y = h - bearingY; info.xyz[1].z = 1; info.uv[1].x = r / m_atlasTex.resolution.x; info.uv[1].y = b / m_atlasTex.resolution.y; info.xyz[2].x = bearingX + w; info.xyz[2].y = bearingY; //h - bearingY + h; info.xyz[2].z = 1; info.uv[2].x = r / m_atlasTex.resolution.x; info.uv[2].y = t / m_atlasTex.resolution.y; info.xyz[3].x = bearingX; info.xyz[3].y = bearingY; info.xyz[3].z = 1; info.uv[3].x = l / m_atlasTex.resolution.x; info.uv[3].y = t / m_atlasTex.resolution.y; info.advance = glyphBox.advance; } if (pngOutput && !pngOutput->empty()) { savePng(m_generator.atlasStorage(), pngOutput->c_str()); } destroyFont(font); deinitializeFreetype(ft); } }