LCOV - code coverage report
Current view: top level - muse/framework/draw/internal - qfontprovider.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 89.7 % 68 61
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) 2021 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 "qfontprovider.h"
      23              : 
      24              : #include <memory>
      25              : 
      26              : #include "global/io/file.h"
      27              : 
      28              : #include <QFont>
      29              : #include <QPaintDevice>
      30              : #include <QFontDatabase>
      31              : #include <QFontMetricsF>
      32              : #include <QRawFont>
      33              : #include <QPainterPath>
      34              : 
      35              : using namespace muse;
      36              : using namespace muse::draw;
      37              : 
      38              : class FontPaintDevice : public QPaintDevice
      39              : {
      40              : public:
      41            0 :     QPaintEngine* paintEngine() const override
      42              :     {
      43            0 :         return nullptr;
      44              :     }
      45              : 
      46              : protected:
      47              :     static constexpr double MU_ENGRAVING_DPI = 1200; // Same as mu::engraving::DPI. TODO: pass as parameter
      48      1005719 :     int metric(PaintDeviceMetric m) const override
      49              :     {
      50      1005719 :         switch (m) {
      51      1005719 :         case QPaintDevice::PdmDpiX:
      52              :         case QPaintDevice::PdmDpiY:
      53      1005719 :             return MU_ENGRAVING_DPI;
      54            0 :         default:
      55            0 :             return 1;
      56              :         }
      57              :     }
      58              : };
      59              : 
      60              : static FontPaintDevice device;
      61              : 
      62           46 : int QFontProvider::addSymbolFont(const String& family, const io::path_t& path)
      63              : {
      64           46 :     return addSymbolFont(family, std::make_shared<io::File>(path));
      65              : }
      66              : 
      67          174 : int QFontProvider::addSymbolFont(const String& family, std::shared_ptr<io::IODevice> device)
      68              : {
      69          348 :     const std::string filePath = device->meta("file_path");
      70          174 :     if (!filePath.empty()) {
      71          174 :         return QFontDatabase::addApplicationFont(QString::fromStdString(filePath));
      72              :     }
      73              : 
      74            0 :     if (!device->open(io::IODevice::ReadOnly)) {
      75            0 :         return -1;
      76              :     }
      77              : 
      78            0 :     return QFontDatabase::addApplicationFontFromData(device->readAll().toQByteArray());
      79          174 : }
      80              : 
      81        68578 : double QFontProvider::lineSpacing(const Font& f) const
      82              : {
      83        68578 :     return QFontMetricsF(f.toQFont(), &device).lineSpacing();
      84              : }
      85              : 
      86         2163 : double QFontProvider::xHeight(const Font& f) const
      87              : {
      88         2163 :     return QFontMetricsF(f.toQFont(), &device).xHeight();
      89              : }
      90              : 
      91            6 : double QFontProvider::height(const Font& f) const
      92              : {
      93            6 :     return QFontMetricsF(f.toQFont(), &device).height();
      94              : }
      95              : 
      96        17733 : double QFontProvider::capHeight(const Font& f) const
      97              : {
      98        17733 :     return QFontMetrics(f.toQFont(), &device).capHeight();
      99              : }
     100              : 
     101         4160 : double QFontProvider::ascent(const Font& f) const
     102              : {
     103         4160 :     return QFontMetricsF(f.toQFont(), &device).ascent();
     104              : }
     105              : 
     106            6 : double QFontProvider::descent(const Font& f) const
     107              : {
     108            6 :     return QFontMetricsF(f.toQFont(), &device).descent();
     109              : }
     110              : 
     111            6 : double QFontProvider::underlinePos(const Font& f) const
     112              : {
     113            6 :     return QFontMetricsF(f.toQFont(), &device).underlinePos();
     114              : }
     115              : 
     116            6 : double QFontProvider::lineWidth(const Font& f) const
     117              : {
     118            6 :     return QFontMetricsF(f.toQFont(), &device).lineWidth();
     119              : }
     120              : 
     121            6 : double QFontProvider::strikeOutPos(const Font& f) const
     122              : {
     123            6 :     return QFontMetricsF(f.toQFont(), &device).strikeOutPos();
     124              : }
     125              : 
     126       630944 : bool QFontProvider::inFont(const Font& f, char32_t ucs4) const
     127              : {
     128              :     // NOTE: QFontMetricsF::inFontUcs4 is unreliable for our use case because it uses Qt's fallback
     129              :     // system even if the flag noFontMerging is set, and returns true if the character
     130              :     // is found in any of the fallbacks. We need to use instead QRawFont, which represents the
     131              :     // *actual* font, not Qt's interpretation of the query. From QRawFont we can query the glyph index
     132              :     // of a character (zero if not in font) and the bounding rectangle of the actual glyph at that index.
     133       630944 :     QRawFont qRawFont = QRawFont::fromFont(f.toQFont());
     134       630944 :     int glyphIndex = qRawFont.glyphIndexesForString(QString(QChar::fromUcs4(ucs4)))[0];
     135       630944 :     if (glyphIndex == 0) {
     136       393941 :         return false;
     137              :     }
     138              : 
     139              :     //! @NOTE some symbols in fonts dont have glyph. For example U+ee80
     140              :     //! exists in Bravura.otf but doesn't have glyph
     141              :     //! so QFontMetricsF returns true in that case
     142       237003 :     return qRawFont.boundingRect(glyphIndex).isValid();
     143       630944 : }
     144              : 
     145       235851 : double QFontProvider::horizontalAdvance(const Font& f, const String& string) const
     146              : {
     147       235851 :     return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(string);
     148              : }
     149              : 
     150       139532 : double QFontProvider::horizontalAdvance(const Font& f, char32_t ucs4) const
     151              : {
     152       139532 :     if (Char::requiresSurrogates(ucs4)) {
     153           48 :         return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(String::fromUcs4(ucs4));
     154              :     }
     155              : 
     156       139484 :     return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(static_cast<char16_t>(ucs4));
     157              : }
     158              : 
     159           99 : RectF QFontProvider::boundingRect(const Font& f, const String& string) const
     160              : {
     161           99 :     return RectF::fromQRectF(QFontMetricsF(f.toQFont(), &device).boundingRect(string));
     162              : }
     163              : 
     164       139302 : RectF QFontProvider::boundingRect(const Font& f, char32_t ucs4) const
     165              : {
     166       139302 :     if (Char::requiresSurrogates(ucs4)) {
     167           48 :         return RectF::fromQRectF(QFontMetrics(f.toQFont(), &device).boundingRect(String::fromUcs4(ucs4)));
     168              :     }
     169              : 
     170       139254 :     if (f.type() == Font::Type::MusicSymbol || f.type() == Font::Type::MusicSymbolText) {
     171              :         // QFontMetrics::boundingRect returns pixel values obtained by rasterization of the font.
     172              :         // QPainterPath::boundingRect works from the actual vector shape so it's more accurate.
     173              :         // There is still some discretization going on so we can make it even more accurate by upscaling.
     174              :         // CAUTION: More expensive! Ok for music glyphs because they are cached.
     175       139194 :         QFont qf = f.toQFont();
     176       139194 :         qf = QFont(qf, &device);
     177              :         static constexpr double UPSCALING = 4.0;
     178       139194 :         qf.setPointSizeF(qf.pointSizeF() * UPSCALING);
     179       139194 :         QPainterPath path;
     180       139194 :         path.addText(QPointF(), qf, QString(static_cast<char16_t>(ucs4)));
     181       139194 :         return RectF::fromQRectF(path.boundingRect()).scaled(SizeF(1.0 / UPSCALING, 1.0 / UPSCALING));
     182       139194 :     }
     183              : 
     184           60 :     return RectF::fromQRectF(QFontMetricsF(f.toQFont(), &device).boundingRect(static_cast<char16_t>(ucs4)));
     185              : }
     186              : 
     187       398271 : RectF QFontProvider::tightBoundingRect(const Font& f, const String& string) const
     188              : {
     189       398271 :     auto boundingRect = QFontMetricsF(f.toQFont(), &device).tightBoundingRect(string);
     190       398271 :     if (!boundingRect.isValid()) {
     191              :         // fix for https://github.com/musescore/MuseScore/issues/19503 - Qt can return garbage bounding rectangles that corrupt layout
     192        15147 :         return RectF();
     193              :     }
     194       383124 :     return RectF::fromQRectF(boundingRect);
     195              : }
        

Generated by: LCOV version 2.0-1