/
ArtemNech
/
simpleOpenGL
Обзор
Документация
Войти
/
ArtemNech
/
simpleOpenGL
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
backend/ogl_sys/drawing-sets/COglTextManager.cpp
471 строка
14 KB
Artem Nechitaylenko
All objects are works and draggable
20 май 2026, 09:40
20 май 2026, 09:40
bf16523
Код
Авторство
О чём код?
#include "COglTextManager.h" #include "glad.h" #include <fstream> #include <sstream> #include <nlohmann/json.hpp> #include <unicode/utf16.h> #include <QDebug> #include <QFile> #include "../CImageFactory.h" COglTextManager::COglTextManager(/*const std::string &atlas_path*/) { //m_atlas_path = atlas_path; } COglTextManager::~COglTextManager() = default; GLuint COglTextManager::createTextureFromImageData(const std::vector<uint8_t> &imageData, int width, int height) { GLuint textureId; glGenTextures(1, &textureId); glBindTexture(GL_TEXTURE_2D, textureId); while (glGetError() != GL_NO_ERROR) ; // it should be callback, but not in demo // ПРОВЕРКА ДАННЫХ if (imageData.empty()) { m_error = true; return 0; } else if (imageData.size() < width * height * 4) { m_error = true; return 0; } else { glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData.data()); } // Настройки фильтрации glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); // Проверка ошибок GLenum error = glGetError(); if (error != GL_NO_ERROR) { glDeleteTextures(1, &textureId); m_error = true; return 0; } return textureId; } void COglTextManager::loadTexture(const std::string &textureId, const ngraph::IImageElement &image) { if (!image.isValid()) { m_error = true; return; } if (image.bytes.empty()) { m_error = true; return; } int width = static_cast<int>(image.rect.width()); int height = static_cast<int>(image.rect.height()); // Проверяем валидность размеров if (width <= 0 || height <= 0) { m_error = true; return; } size_t expectedBytes = width * height * 4; // RGBA if (image.bytes.size() != expectedBytes) { m_error = true; return; } m_atlas_texture = createTextureFromImageData(image.bytes, width, height); } bool COglTextManager::init_font() { ngraph::IImageElement sdfAtlasImage = ngraph::CImageFactory::createFromPNG(":/atlas/images/atlas.png"); loadTexture("sdf_atlas", sdfAtlasImage); // 2. Загружаем JSON из файла //std::ifstream jsonFile("atlas.json"); QFile jsonFile(":/atlas/images/atlas.json"); if (!jsonFile.open(QIODevice::ReadOnly)) { qDebug() << "Failed to open atlas.json"; m_error = true; return false; } QByteArray jsonData = jsonFile.readAll(); jsonFile.close(); nlohmann::json atlasJson; try { //atlasJson = nlohmann::json::parse(jsonData); atlasJson = nlohmann::json::parse(jsonData.toStdString()); } catch (const nlohmann::json::parse_error& e) { m_error = true; return false; } // 3. атлас float atlasWidth = atlasJson["atlas"]["width"].get<float>(); float atlasHeight = atlasJson["atlas"]["height"].get<float>(); m_atlasTexSize[0] = atlasWidth; m_atlasTexSize[1] = atlasHeight; m_atlasSize = atlasJson["atlas"]["size"].get<float>(); m_atlasDistanceRange = atlasJson["atlas"]["distanceRange"].get<float>(); m_lineHeightEm = atlasJson["metrics"]["lineHeight"].get<float>(); // 4. Загружаем глифы for (const auto& glyphJson : atlasJson["glyphs"]) { if (!glyphJson.contains("planeBounds")) continue; char32_t unicode = glyphJson["unicode"].get<char32_t>(); Glyph glyph{}; glyph.advance = glyphJson["advance"].get<float>(); if (unicode == 32) { m_space = glyph.advance; } /// atlasBounds — Координаты в текстуре атласа (пиксели->texCoord): const auto& atlasBounds = glyphJson["atlasBounds"]; float left = atlasBounds["left"].get<float>(); float right = atlasBounds["right"].get<float>(); float bottom = atlasBounds["bottom"].get<float>(); float top = atlasBounds["top"].get<float>(); glyph.uvPosPX = glm::vec2( left, bottom ); /// 2. uvSize.y должен быть ПОЛОЖИТЕЛЬНЫМ: glyph.uvSizePX = glm::vec2( std::fabs(right - left), std::fabs(top - bottom) ); /// planeBounds — Геометрия глифа в типографских единицах const auto& planeBounds = glyphJson["planeBounds"]; float pLeft = planeBounds["left"].get<float>(); float pRight = planeBounds["right"].get<float>(); float pBottom = planeBounds["bottom"].get<float>(); float pTop = planeBounds["top"].get<float>(); glyph.planeBoundsEM = glm::vec4(pLeft, pBottom, pRight, pTop); // Размер в em glyph.sizeEM = glm::vec2( (pRight - pLeft), (pTop - pBottom) ); // Bearing (смещение от начала символа) glyph.bearingEM = glm::vec2( pLeft, pBottom ); m_glyphs[unicode] = glyph; } if (m_space < 0.1 && !m_glyphs.empty()) { m_space = m_glyphs.begin()->second.advance; } if (atlasJson.contains("kerning")) { for (const auto& kerningPair : atlasJson["kerning"]) { char32_t first = kerningPair["unicode1"].get<char32_t>(); char32_t second = kerningPair["unicode2"].get<char32_t>(); float offset = kerningPair["advance"].get<float>(); auto pair = std::pair<char32_t,char32_t>(first, second); m_kerning[pair] = offset; } } return m_glyphs.size() > 10; } void COglTextManager::explodeText(COglLayer *set, elem::STextElement &textElement) { std::string textUtf8 = textElement.text; ngraph::NColor color = textElement.color; float fontSizePx = textElement.size; ngraph::NPointF bottomLeft = textElement.position; if (textUtf8.empty() || m_atlas_texture == 0) return; auto utf8_to_utf32 = [](const std::string& utf8) { std::vector<char32_t> result; const char* source = utf8.c_str(); size_t length = utf8.length(); int32_t offset = 0; while (offset < length) { UChar32 c; U8_NEXT(source, offset, length, c); if (c < 0) break; result.push_back(c); } return result; }; auto m_textColor = glm::vec4(color.redF(), color.greenF(), color.blueF(), color.alphaF()); auto m_outlineColor = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f); // Чёрный контур ngraph::NLineF fontLine(ngraph::NPointF(0,0), ngraph::NPointF(fontSizePx, 0)); //fontSizePx = static_cast<float>(fontLine.width()); set->startBatch(GL_TRIANGLES, fontSizePx, true, m_atlas_texture, true, false); std::vector<char32_t> unicodeChars = utf8_to_utf32(textUtf8); float scale = fontSizePx / m_atlasSize; // Масштаб от размера атласа auto cursorX = static_cast<float>(bottomLeft.x()); auto cursorY = static_cast<float>(bottomLeft.y()); for (size_t i = 0; i < unicodeChars.size(); ++i) { char32_t ch = unicodeChars[i]; if (ch == 32) { cursorX += (m_space * m_atlasSize * scale) / 2.0f; continue; } if (ch == 10) { cursorY += m_lineHeightEm * fontSizePx; cursorX = static_cast<float>(bottomLeft.x()); continue; } auto it = m_glyphs.find(ch); if (it == m_glyphs.end()) { cursorX += fontSizePx * 0.5f; continue; } const Glyph& glyph = it->second; float letterOffsetX = glyph.planeBoundsEM.x * fontSizePx; float letterOffsetY = glyph.planeBoundsEM.y * fontSizePx; float xpos = cursorX - letterOffsetX;// - bearingX; float ybottom = cursorY - letterOffsetY;// - bearingY; float w = (glyph.sizeEM.x) * fontSizePx; float h = (glyph.sizeEM.y) * fontSizePx; float ytop = ybottom - h; /// UV координаты float uvLeft = glyph.uvPosPX.x / m_atlasTexSize[0]; float uvBottom = 1.0f - glyph.uvPosPX.y / m_atlasTexSize[1]; float uvTop = 1.0f - (glyph.uvPosPX.y + glyph.uvSizePX.y) / m_atlasTexSize[1]; float uvRight = (glyph.uvPosPX.x + glyph.uvSizePX.x) / m_atlasTexSize[0]; auto & m_vertices = set->vertices(); float Z = set->get_layer_Z(); m_vertices.insert(m_vertices.end(), { // Треугольник 1 (левая нижняя половина) vertex(xpos, ybottom, Z, color, uvLeft, uvBottom), vertex(xpos + w, ybottom, Z, color, uvRight, uvBottom), vertex(xpos, ytop, Z, color, uvLeft, uvTop), // Треугольник 2 (правая верхняя половина) vertex(xpos + w, ybottom, Z, color, uvRight, uvBottom), vertex(xpos + w, ytop, Z, color, uvRight, uvTop), vertex(xpos, ytop, Z, color, uvLeft, uvTop) }); if (i + 1 < unicodeChars.size()) { char32_t nextChar = unicodeChars[i + 1]; auto kerningIt = m_kerning.find({ch, nextChar}); if (kerningIt != m_kerning.end()) { cursorX += kerningIt->second * m_atlasSize * scale; } } //cursorX += glyph.advance * fontSizePx; cursorX += glyph.advance * m_atlasSize * scale; } set->endBatch(); } void COglTextManager::setSDFUniforms(const DrawBatch &batch, GLuint programId) { //GLint locTextColor = glGetUniformLocation(programId, "u_TextColor"); //GLint locOutlineColor = glGetUniformLocation(programId, "u_OutlineColor"); //GLint locOutlineSize = glGetUniformLocation(programId, "u_OutlineSize"); GLint locFontScale = glGetUniformLocation(programId, "u_FontScale"); GLint locTexSize = glGetUniformLocation(programId, "u_TextureSize"); GLint locSmoozy = glGetUniformLocation(programId, "u_Smoothing"); GLint locDistanceRange = glGetUniformLocation(programId, "u_distRange"); GLint locSampler = glGetUniformLocation(programId, "textureSampler"); /* if (locTextColor != -1) glUniform4fv(locTextColor, 1, glm::value_ptr(m_textColor)); if (locOutlineColor != -1) glUniform4fv(locOutlineColor, 1, glm::value_ptr(m_outlineColor)); if (locOutlineSize != -1) glUniform1f(locOutlineSize, m_outlineSize); */ float fontScale = batch.fontSize / m_atlasSize; if (locFontScale != -1) glUniform1f(locFontScale, fontScale); if (locSampler != -1) glUniform1f(locSampler, 0); if (locTexSize != -1) glUniform2f(locTexSize, m_atlasTexSize[0], m_atlasTexSize[1]); if (locSmoozy != -1) glUniform1f(locSmoozy, 16.0f); // это из параметра генерации атласа if (locDistanceRange != -1) glUniform1f(locDistanceRange, m_atlasDistanceRange); } bool COglTextManager::is_error() const { return m_error; } ngraph::NRectF COglTextManager::measureText(const std::string& text, float fontSizePx, const ngraph::NPointF& startPosition) { if (text.empty()) { return ngraph::NRectF(startPosition.x(), startPosition.y(), 0.0f, 0.0f); } auto utf8_to_utf32 = [](const std::string& utf8) { std::vector<char32_t> result; const char* source = utf8.c_str(); size_t length = utf8.length(); int32_t offset = 0; while (offset < length) { UChar32 c; U8_NEXT(source, offset, length, c); if (c < 0) break; result.push_back(c); } return result; }; std::vector<char32_t> unicodeChars = utf8_to_utf32(text); float scale = fontSizePx / m_atlasSize; auto cursorX = static_cast<float>(startPosition.x()); auto cursorY = static_cast<float>(startPosition.y()); float minX = cursorX; float maxX = cursorX; float minY = cursorY; float maxY = cursorY; float lineStartX = cursorX; for (size_t i = 0; i < unicodeChars.size(); ++i) { char32_t ch = unicodeChars[i]; if (ch == 32) { cursorX += (m_space * m_atlasSize * scale) / 2.0f; maxX = std::max(maxX, cursorX); continue; } if (ch == 10) { cursorY += m_lineHeightEm * fontSizePx; cursorX = lineStartX; continue; } auto it = m_glyphs.find(ch); if (it == m_glyphs.end()) { cursorX += fontSizePx * 0.5f; maxX = std::max(maxX, cursorX); continue; } const Glyph& glyph = it->second; float letterOffsetX = glyph.planeBoundsEM.x * fontSizePx; float letterOffsetY = glyph.planeBoundsEM.y * fontSizePx; float xpos = cursorX - letterOffsetX; float ybottom = cursorY - letterOffsetY; float w = glyph.sizeEM.x * fontSizePx; float h = glyph.sizeEM.y * fontSizePx; float ytop = ybottom - h; minX = std::min(minX, xpos); maxX = std::max(maxX, xpos + w); minY = std::min(minY, ytop); maxY = std::max(maxY, ybottom); // Кернинг if (i + 1 < unicodeChars.size()) { char32_t nextChar = unicodeChars[i + 1]; auto kerningIt = m_kerning.find({ch, nextChar}); if (kerningIt != m_kerning.end()) { cursorX += kerningIt->second * m_atlasSize * scale; } } cursorX += glyph.advance * m_atlasSize * scale; maxX = std::max(maxX, cursorX); } float width = maxX - minX; float height = maxY - minY; return ngraph::NRectF(minX, minY, minX + width, minY + height); }