LCOV - code coverage report
Current view: top level - muse/framework/draw/internal - fontfacext.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 82.0 % 334 274
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2026 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : #include "fontfacext.h"
      23              : 
      24              : #include <algorithm>
      25              : #include <array>
      26              : #include <cctype>
      27              : #include <string_view>
      28              : 
      29              : #include <lodepng.h>
      30              : 
      31              : #include "global/serialization/zipreader.h"
      32              : #include "global/serialization/msgpack.h"
      33              : #include "global/stringutils.h"
      34              : #include "global/io/buffer.h"
      35              : #include "global/io/fileinfo.h"
      36              : #include "global/containers.h"
      37              : #include "log.h"
      38              : 
      39              : using namespace muse::draw;
      40              : 
      41         2052 : static bool isNumber(const std::string& s)
      42              : {
      43         2052 :     return !s.empty()
      44         8460 :            && (std::all_of(s.begin(), s.end(), [](unsigned char c) { return std::isdigit(c); })
      45            0 :                || (s[0] == '-' && s.size() > 1 && std::all_of(s.begin() + 1, s.end(), [](unsigned char c) {
      46            0 :         return std::isdigit(c);
      47         2052 :     })));
      48              : }
      49              : 
      50          208 : bool FontFaceXT::isFtx(const ByteArray& data)
      51              : {
      52              :     static constexpr std::array<uint8_t, 3> MARKER = { 'f', 't', 'x' };
      53          208 :     return ZipReader::startsWithEntry(data, MARKER);
      54              : }
      55              : 
      56            6 : FontFaceXT::FontFaceXT()
      57              : {
      58            6 : }
      59              : 
      60            6 : FontFaceXT::~FontFaceXT()
      61              : {
      62            6 : }
      63              : 
      64            6 : bool FontFaceXT::load(const FaceKey& key, const FontData& fontData, bool isSymbolMode)
      65              : {
      66            6 :     m_key = key;
      67            6 :     m_isSymbolMode = isSymbolMode;
      68              : 
      69            6 :     if (!fontData.valid()) {
      70            0 :         LOGE() << "empty font data: " << key.dataKey.family().id();
      71            0 :         return false;
      72              :     }
      73              : 
      74            6 :     m_fileBuffer = std::make_unique<muse::io::Buffer>(muse::ByteArray(fontData.data));
      75            6 :     m_fileBuffer->open(muse::io::IODevice::ReadOnly);
      76              : 
      77            6 :     m_zip = std::make_unique<muse::ZipReader>(m_fileBuffer.get());
      78              : 
      79              :     // meta
      80              :     {
      81           12 :         muse::ByteArray metaData = m_zip->fileData("meta.txt");
      82            6 :         if (metaData.empty()) {
      83            0 :             LOGE() << "meta is empty";
      84            0 :             return false;
      85              :         }
      86              : 
      87            6 :         std::string meta(metaData.constChar(), metaData.size());
      88           12 :         std::vector<std::string> metas;
      89           18 :         muse::strings::split(meta, metas, "\n");
      90              : 
      91            6 :         std::string ver;
      92            6 :         std::string glyphs;
      93           60 :         for (const std::string& p : metas) {
      94           54 :             size_t i = p.find(':');
      95           54 :             if (i == std::string::npos) {
      96            0 :                 LOGE() << "failed parse param: " << p;
      97            0 :                 continue;
      98              :             }
      99              : 
     100           54 :             std::string name = p.substr(0, i);
     101           54 :             std::string valStr = p.substr(i + 1);
     102              : 
     103           54 :             if (name == "version") {
     104            6 :                 ver = valStr;
     105           48 :             } else if (name == "glyphs") {
     106            6 :                 glyphs = valStr;
     107           42 :             } else if (name == "leading") {
     108            6 :                 m_leading = std::stol(valStr);
     109           36 :             } else if (name == "ascent") {
     110            6 :                 m_ascent = std::stol(valStr);
     111           30 :             } else if (name == "descent") {
     112            6 :                 m_descent = std::stol(valStr);
     113           24 :             } else if (name == "xHeight") {
     114            6 :                 m_xHeight = std::stol(valStr);
     115           18 :             } else if (name == "capHeight") {
     116            6 :                 m_capHeight = std::stol(valStr);
     117           12 :             } else if (name == "underlinePos") {
     118            6 :                 m_underlinePos = std::stol(valStr);
     119            6 :             } else if (name == "lineWidth") {
     120            6 :                 m_lineWidth = std::stol(valStr);
     121              :             } else {
     122            0 :                 LOGW() << "unknown param: " << name;
     123              :             }
     124           54 :         }
     125              : 
     126            6 :         LOGI() << "fxt version: " << ver << ", glyphs: " << glyphs << ", family: " << key.dataKey.family().id();
     127            6 :     }
     128              : 
     129              :     // ligatures
     130           12 :     muse::ByteArray ligaturesData = m_zip->fileData("ligatures.txt");
     131            6 :     if (!ligaturesData.empty()) {
     132            6 :         std::string ligaturesStr(ligaturesData.constChar(), ligaturesData.size());
     133            6 :         std::vector<std::string> ligatureStrs;
     134           18 :         muse::strings::split(ligaturesStr, ligatureStrs, "\n");
     135              : 
     136           42 :         for (const std::string& str : ligatureStrs) {
     137           36 :             if (str.empty()) {
     138            6 :                 continue;
     139              :             }
     140              : 
     141           30 :             size_t sepIdx = str.find('=');
     142           30 :             if (sepIdx == std::string::npos) {
     143            0 :                 continue;
     144              :             }
     145              : 
     146           30 :             std::string valStr = str.substr(0, sepIdx);
     147           30 :             std::string keyStr = str.substr(sepIdx + 1);
     148           30 :             std::vector<std::string> keyStrs;
     149           90 :             muse::strings::split(keyStr, keyStrs, " ");
     150              : 
     151           30 :             Ligature l;
     152           30 :             l.first = std::stoi(valStr);
     153          132 :             for (const std::string& k : keyStrs) {
     154          102 :                 if (k.empty()) {
     155           30 :                     continue;
     156              :                 }
     157           72 :                 l.second.push_back(std::stoi(k));
     158              :             }
     159              : 
     160           30 :             m_ligatures.push_back(l);
     161           30 :         }
     162              : 
     163            6 :         std::sort(m_ligatures.begin(), m_ligatures.end(), [](const Ligature& l1, const Ligature& l2) {
     164           60 :             return l1.second.size() > l2.second.size();
     165              :         });
     166            6 :     }
     167              : 
     168              :     // kernings
     169           12 :     muse::ByteArray kerningsData = m_zip->fileData("kernings.txt");
     170            6 :     if (!kerningsData.empty()) {
     171            6 :         std::string kerningsStr(kerningsData.constChar(), kerningsData.size());
     172            6 :         std::vector<std::string> kerningStrs;
     173           18 :         muse::strings::split(kerningsStr, kerningStrs, "\n");
     174              : 
     175        76158 :         for (const std::string& str : kerningStrs) {
     176        76152 :             if (str.empty()) {
     177            6 :                 continue;
     178              :             }
     179              : 
     180        76146 :             size_t sepIdx = str.find('=');
     181        76146 :             if (sepIdx == std::string::npos) {
     182            0 :                 continue;
     183              :             }
     184              : 
     185        76146 :             std::string keyStr = str.substr(0, sepIdx);
     186        76146 :             std::string valStr = str.substr(sepIdx + 1);
     187        76146 :             std::vector<std::string> keyStrs;
     188       228438 :             muse::strings::split(keyStr, keyStrs, " ");
     189        76146 :             if (keyStrs.size() != 2) {
     190              :                 //error in file
     191            0 :                 continue;
     192              :             }
     193              : 
     194        76146 :             f26dot6_t advance = std::stoi(valStr);
     195        76146 :             std::pair<glyph_idx_t, glyph_idx_t> keyPair;
     196        76146 :             keyPair.first = std::stoi(keyStrs[0]);
     197        76146 :             keyPair.second = std::stoi(keyStrs[1]);
     198              : 
     199        76146 :             m_kernings.insert({ keyPair, advance });
     200        76146 :         }
     201            6 :     }
     202              : 
     203              :     {
     204              :         // glyphIndecies
     205           12 :         muse::ByteArray glyphIndeciesData = m_zip->fileData("glyphIndecies.txt");
     206            6 :         if (!glyphIndeciesData.empty()) {
     207            6 :             std::string glyphIndeciesStr(glyphIndeciesData.constChar(), glyphIndeciesData.size());
     208            6 :             std::vector<std::string> glyphIndexStrs;
     209           18 :             muse::strings::split(glyphIndeciesStr, glyphIndexStrs, "\n");
     210              : 
     211         2064 :             for (const std::string& str : glyphIndexStrs) {
     212         2058 :                 if (str.empty()) {
     213            6 :                     continue;
     214              :                 }
     215              : 
     216         2052 :                 std::vector<std::string> glyphIndexPair;
     217         6156 :                 muse::strings::split(str, glyphIndexPair, " ");
     218              : 
     219         2052 :                 if (glyphIndexPair.size() != 2) {
     220            0 :                     continue;
     221              :                 }
     222              : 
     223         2052 :                 glyph_idx_t glyphIdx = static_cast<glyph_idx_t>(std::stoi(glyphIndexPair[1]));
     224              : 
     225         2052 :                 if (isNumber(glyphIndexPair[0])) {
     226         2052 :                     m_unicodeGlyphs.insert({ static_cast<char32_t>(std::stoi(glyphIndexPair[0])), glyphIdx });
     227              :                 } else {
     228            0 :                     m_nonUnicodeGlyphs.insert({ glyphIndexPair[0], glyphIdx });
     229              :                 }
     230         2052 :             }
     231            6 :         }
     232            6 :     }
     233              : 
     234              :     {
     235              :         // atlas texture
     236           12 :         muse::ByteArray metaData = m_zip->fileData("atlatInfo.txt");
     237            6 :         if (metaData.empty()) {
     238            0 :             return true;
     239              :         }
     240            6 :         m_atlastexture.loadAtlasMetaInfo(metaData);
     241              : 
     242           12 :         muse::ByteArray atlasPng = m_zip->fileData("atlas.png");
     243            6 :         if (atlasPng.empty()) {
     244            0 :             LOGE() << "atlas.png is empty";
     245            0 :             return false;
     246              :         }
     247            6 :         if (!m_atlastexture.loadAtlas(atlasPng)) {
     248            0 :             return false;
     249              :         }
     250            6 :     }
     251              : 
     252            6 :     return true;
     253            6 : }
     254              : 
     255            1 : const FaceKey& FontFaceXT::key() const
     256              : {
     257            1 :     return m_key;
     258              : }
     259              : 
     260            1 : bool FontFaceXT::isSymbolMode() const
     261              : {
     262            1 :     return m_isSymbolMode;
     263              : }
     264              : 
     265            1 : f26dot6_t FontFaceXT::leading() const
     266              : {
     267            1 :     return m_leading;
     268              : }
     269              : 
     270            1 : f26dot6_t FontFaceXT::ascent() const
     271              : {
     272            1 :     return m_ascent;
     273              : }
     274              : 
     275            1 : f26dot6_t FontFaceXT::descent() const
     276              : {
     277            1 :     return m_descent;
     278              : }
     279              : 
     280            1 : f26dot6_t FontFaceXT::xHeight() const
     281              : {
     282            1 :     return m_xHeight;
     283              : }
     284              : 
     285            2 : f26dot6_t FontFaceXT::capHeight() const
     286              : {
     287            2 :     return m_capHeight;
     288              : }
     289              : 
     290            1 : f26dot6_t FontFaceXT::underlinePos() const
     291              : {
     292            1 :     return m_underlinePos;
     293              : }
     294              : 
     295            1 : f26dot6_t FontFaceXT::lineWidth() const
     296              : {
     297            1 :     return m_lineWidth;
     298              : }
     299              : 
     300           18 : void FontFaceXT::applyLigatures(std::vector<ShapedGlyph>& glyphs, const Ligatures& ls)
     301              : {
     302          108 :     for (const Ligature& l : ls) {
     303           90 :         if (l.second.empty() || l.second.size() > glyphs.size() || (l.second.size() == 1 && l.second.front() == l.first)) {
     304            6 :             continue;
     305              :         }
     306              : 
     307           84 :         auto from = glyphs.begin();
     308              :         while (1) {
     309           84 :             auto it = std::search(from, glyphs.end(), l.second.begin(), l.second.end(),
     310          663 :                                   [](const ShapedGlyph& glyph, glyph_idx_t idx) { return glyph.idx == idx; });
     311           84 :             if (it != glyphs.end()) {
     312            0 :                 it->idx = l.first;
     313            0 :                 for (size_t i = 1; i < l.second.size(); ++i) {
     314            0 :                     ++it;
     315            0 :                     it->idx = 0;
     316              :                 }
     317            0 :                 from = std::next(it);
     318              :             } else {
     319           84 :                 break;
     320              :             }
     321            0 :         }
     322              :     }
     323           18 : }
     324              : 
     325           18 : std::vector<ShapedGlyph> FontFaceXT::glyphs(const char32_t* text, int text_length) const
     326              : {
     327           18 :     if (text_length < 1) {
     328            0 :         return std::vector<ShapedGlyph>();
     329              :     }
     330              : 
     331           18 :     std::vector<ShapedGlyph> result;
     332              : 
     333           18 :     std::vector<char32_t> data(text, text + text_length);
     334              : 
     335           18 :     result.reserve(text_length);
     336              : 
     337          153 :     for (size_t i = 0; i < data.size(); ++i) {
     338          135 :         const char32_t ch = data[i];
     339          135 :         if (ch == 0) {
     340            0 :             continue;
     341              :         }
     342              : 
     343          135 :         glyph_idx_t idx = glyphIndex(ch);
     344          135 :         if (idx == 0) {
     345            0 :             LOGE() << "font: " << m_key.dataKey.family().id() << " not found glyph: " << (int)ch;
     346            0 :             continue;
     347              :         }
     348              : 
     349          135 :         result.push_back({ idx, 0, static_cast<uint32_t>(i) });
     350              :     }
     351              : 
     352           18 :     if (!m_isSymbolMode) {
     353           18 :         applyLigatures(result, m_ligatures);
     354              :     }
     355              : 
     356          153 :     muse::remove_if(result, [](const ShapedGlyph& glyph) { return glyph.idx == 0; });
     357              : 
     358          153 :     for (auto it = result.begin(); it != result.end(); ++it) {
     359          135 :         if (auto nextIt = std::next(it); nextIt != result.end()) {
     360          117 :             it->x_advance = glyphAdvance(it->idx, nextIt->idx);
     361              :         } else {
     362           18 :             it->x_advance = glyphAdvance(it->idx);
     363              :         }
     364              :     }
     365              : 
     366           18 :     return result;
     367           18 : }
     368              : 
     369          172 : glyph_idx_t FontFaceXT::glyphIndex(char32_t ucs4) const
     370              : {
     371          172 :     return muse::value(m_unicodeGlyphs, ucs4);
     372              : }
     373              : 
     374            0 : glyph_idx_t FontFaceXT::glyphIndex(const std::string& glyphName) const
     375              : {
     376            0 :     return muse::value(m_nonUnicodeGlyphs, glyphName);
     377              : }
     378              : 
     379           19 : char32_t FontFaceXT::findCharCode(glyph_idx_t idx) const
     380              : {
     381           19 :     return muse::key(m_unicodeGlyphs, idx);
     382              : }
     383              : 
     384           64 : FBBox FontFaceXT::glyphBbox(glyph_idx_t idx) const
     385              : {
     386           64 :     return m_isSymbolMode ? glyphData(idx).symBbox : glyphData(idx).textBbox;
     387              : }
     388              : 
     389          109 : f26dot6_t FontFaceXT::glyphAdvance(glyph_idx_t idx) const
     390              : {
     391          109 :     return m_isSymbolMode ? glyphData(idx).symAdvance : glyphData(idx).textAdvance;
     392              : }
     393              : 
     394           18 : const GlyphImage& FontFaceXT::glyphImage(glyph_idx_t idx) const
     395              : {
     396           18 :     return glyphData(idx).glyphImage;
     397              : }
     398              : 
     399          191 : const FontFaceXT::GlyphData& FontFaceXT::glyphData(glyph_idx_t idx) const
     400              : {
     401          191 :     auto it = m_cache.find(idx);
     402          191 :     if (it != m_cache.end()) {
     403          102 :         return it->second;
     404              :     }
     405              : 
     406           89 :     muse::ByteArray data = m_zip->fileData(std::to_string(idx));
     407           89 :     muse::io::Buffer buf(&data);
     408           89 :     buf.open(muse::io::IODevice::ReadOnly);
     409              : 
     410           89 :     std::pair<glyph_idx_t, GlyphData> v;
     411           89 :     v.first = idx;
     412           89 :     v.second.read(&buf);
     413           89 :     v.second.glyphImage.sdf = m_atlastexture.getGlyphImage(idx);
     414              : 
     415           89 :     return m_cache.insert(std::move(v)).first->second;
     416           89 : }
     417              : 
     418          117 : f26dot6_t FontFaceXT::glyphAdvance(glyph_idx_t idx, glyph_idx_t nextIdx) const
     419              : {
     420          117 :     auto kerningPair = std::make_pair(idx, nextIdx);
     421              : 
     422          117 :     if (auto it = m_kernings.find(kerningPair); it != m_kernings.end()) {
     423           45 :         return it->second;
     424              :     }
     425              : 
     426           72 :     return glyphAdvance(idx);
     427              : }
     428              : 
     429            0 : void FontFaceXT::GlyphData::write(muse::io::IODevice* d) const
     430              : {
     431            0 :     muse::ByteArray data = muse::msgpack::pack(static_cast<int64_t>(textBbox.x()),
     432            0 :                                                static_cast<int64_t>(textBbox.y()),
     433            0 :                                                static_cast<int64_t>(textBbox.width()),
     434            0 :                                                static_cast<int64_t>(textBbox.height()),
     435            0 :                                                static_cast<int64_t>(textAdvance),
     436            0 :                                                static_cast<int64_t>(symBbox.x()),
     437            0 :                                                static_cast<int64_t>(symBbox.y()),
     438            0 :                                                static_cast<int64_t>(symBbox.width()),
     439            0 :                                                static_cast<int64_t>(symBbox.height()),
     440            0 :                                                static_cast<int64_t>(symAdvance),
     441            0 :                                                glyphImage.rect.x(), glyphImage.rect.y(),
     442            0 :                                                glyphImage.rect.width(), glyphImage.rect.height(),
     443            0 :                                                glyphImage.range);
     444            0 :     d->write(data);
     445            0 : }
     446              : 
     447           89 : void FontFaceXT::GlyphData::read(muse::io::IODevice* d)
     448              : {
     449           89 :     int64_t textBboxX = 0;
     450           89 :     int64_t textBboxY = 0;
     451           89 :     int64_t textBboxW = 0;
     452           89 :     int64_t textBboxH = 0;
     453           89 :     int64_t textAdvanceValue = 0;
     454           89 :     int64_t symBboxX = 0;
     455           89 :     int64_t symBboxY = 0;
     456           89 :     int64_t symBboxW = 0;
     457           89 :     int64_t symBboxH = 0;
     458           89 :     int64_t symAdvanceValue = 0;
     459           89 :     double rectX = 0.0;
     460           89 :     double rectY = 0.0;
     461           89 :     double rectW = 0.0;
     462           89 :     double rectH = 0.0;
     463           89 :     float range = 0.f;
     464              : 
     465           89 :     const bool ok = muse::msgpack::unpack(d->readAll(), textBboxX, textBboxY, textBboxW, textBboxH, textAdvanceValue,
     466              :                                           symBboxX, symBboxY, symBboxW, symBboxH, symAdvanceValue,
     467              :                                           rectX, rectY, rectW, rectH, range);
     468           89 :     if (!ok) {
     469            0 :         LOGE() << "failed read glyph data";
     470            0 :         return;
     471              :     }
     472              : 
     473           89 :     textBbox = FBBox(textBboxX, textBboxY, textBboxW, textBboxH);
     474           89 :     textAdvance = textAdvanceValue;
     475           89 :     symBbox = FBBox(symBboxX, symBboxY, symBboxW, symBboxH);
     476           89 :     symAdvance = symAdvanceValue;
     477           89 :     glyphImage.rect = muse::RectF(rectX, rectY, rectW, rectH);
     478           89 :     glyphImage.range = range;
     479              : }
     480              : 
     481            6 : bool AtlasTexture::loadAtlas(const muse::ByteArray& atlas)
     482              : {
     483            6 :     const unsigned error = lodepng::decode(m_atlas, m_atlasWidth, m_atlasHeight,
     484              :                                            atlas.constData(), atlas.size(), LCT_GREY);
     485            6 :     if (error != 0) {
     486            0 :         LOGE() << "failed to decode atlas: " << lodepng_error_text(error);
     487            0 :         m_atlas.clear();
     488            0 :         m_atlasWidth = 0;
     489            0 :         m_atlasHeight = 0;
     490            0 :         return false;
     491              :     }
     492              : 
     493            6 :     return true;
     494              : }
     495              : 
     496            6 : void AtlasTexture::loadAtlasMetaInfo(const muse::ByteArray& metaData)
     497              : {
     498            6 :     std::string meta(metaData.constChar(), metaData.size());
     499            6 :     std::vector<std::string> glyphsInfos;
     500           18 :     muse::strings::split(meta, glyphsInfos, "\n");
     501              : 
     502         2046 :     for (const std::string& info : glyphsInfos) {
     503         2040 :         if (info.empty()) {
     504            0 :             continue;
     505              :         }
     506         2040 :         GlyphInfo glyphInfo;
     507         2040 :         size_t i = info.find('=');
     508         2040 :         if (i == std::string::npos) {
     509            0 :             LOGE() << "failed parse param: " << info;
     510            0 :             continue;
     511              :         }
     512         2040 :         glyphInfo.gidx = std::stoi(info.substr(0, i));
     513              : 
     514         2040 :         std::string valStr = info.substr(i + 1);
     515         2040 :         std::vector<std::string> infos;
     516         6120 :         muse::strings::split(valStr, infos, ":");
     517         2040 :         if (infos.size() != 5) {
     518            0 :             continue;
     519              :         }
     520              : 
     521         2040 :         glyphInfo.rect.setX(std::stoi(infos[0]));
     522         2040 :         glyphInfo.rect.setY(std::stoi(infos[1]));
     523         2040 :         glyphInfo.rect.setWidth(std::stoi(infos[2]));
     524         2040 :         glyphInfo.rect.setHeight(std::stoi(infos[3]));
     525         2040 :         glyphInfo.rotated = std::stoi(infos[4]);
     526         2040 :         m_glyphInfos.insert({ glyphInfo.gidx, glyphInfo });
     527         2040 :     }
     528            6 : }
     529              : 
     530           89 : Sdf AtlasTexture::getGlyphImage(glyph_idx_t idx)
     531              : {
     532           89 :     Sdf result;
     533           89 :     auto it = m_glyphInfos.find(idx);
     534           89 :     if (it == m_glyphInfos.end()) {
     535            4 :         return result;
     536              :     }
     537              : 
     538           85 :     auto width = it->second.rect.width();
     539           85 :     auto height = it->second.rect.height();
     540           85 :     auto posX = it->second.rect.x();
     541           85 :     auto posY = it->second.rect.y();
     542              : 
     543           85 :     if (width <= 0 || height <= 0 || posX < 0 || posY < 0
     544           85 :         || posX + width > static_cast<int>(m_atlasWidth)
     545           85 :         || posY + height > static_cast<int>(m_atlasHeight)) {
     546            0 :         LOGE() << "invalid atlas glyph rect: " << idx;
     547            0 :         return result;
     548              :     }
     549              : 
     550           85 :     result.bitmap.reserve(width * height);
     551              : 
     552           85 :     if (it->second.rotated) {
     553         2174 :         for (int x = 0; x < width; ++x) {
     554        69529 :             for (int y = 0; y < height; ++y) {
     555        67409 :                 auto px = m_atlas.at((posY + y) * m_atlasWidth + posX + x);
     556        67409 :                 result.bitmap.push_back(px);
     557              :             }
     558              :         }
     559              :     } else {
     560          976 :         for (int y = 0; y < height; ++y) {
     561        42020 :             for (int x = 0; x < width; ++x) {
     562        41075 :                 result.bitmap.push_back(m_atlas.at((posY + y) * m_atlasWidth + posX + x));
     563              :             }
     564              :         }
     565              :     }
     566              : 
     567           85 :     result.width = it->second.rotated ? height : width;
     568           85 :     result.height = it->second.rotated ? width : height;
     569           85 :     result.hash = std::hash<std::string_view> {}({ reinterpret_cast<const char*>(result.bitmap.data()), result.bitmap.size() });
     570              : 
     571           85 :     return result;
     572            0 : }
        

Generated by: LCOV version 2.0-1