/* * SPDX-License-Identifier: GPL-3.0-only * MuseScore-CLA-applies * * MuseScore Studio * Music Composition & Notation * * Copyright (C) 2021 MuseScore Limited and others * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 3 as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "qfontprovider.h" #include #include "global/io/file.h" #include #include #include #include #include #include using namespace muse; using namespace muse::draw; class FontPaintDevice : public QPaintDevice { public: QPaintEngine* paintEngine() const override { return nullptr; } protected: static constexpr double MU_ENGRAVING_DPI = 1200; // Same as mu::engraving::DPI. TODO: pass as parameter int metric(PaintDeviceMetric m) const override { switch (m) { case QPaintDevice::PdmDpiX: case QPaintDevice::PdmDpiY: return MU_ENGRAVING_DPI; default: return 1; } } }; static FontPaintDevice device; int QFontProvider::addSymbolFont(const String& family, const io::path_t& path) { return addSymbolFont(family, std::make_shared(path)); } int QFontProvider::addSymbolFont(const String& family, std::shared_ptr device) { const std::string filePath = device->meta("file_path"); if (!filePath.empty()) { return QFontDatabase::addApplicationFont(QString::fromStdString(filePath)); } if (!device->open(io::IODevice::ReadOnly)) { return -1; } return QFontDatabase::addApplicationFontFromData(device->readAll().toQByteArray()); } void QFontProvider::removeSymbolFont(int id) { QFontDatabase::removeApplicationFont(id); } double QFontProvider::lineSpacing(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).lineSpacing(); } double QFontProvider::xHeight(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).xHeight(); } double QFontProvider::height(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).height(); } double QFontProvider::capHeight(const Font& f) const { return QFontMetrics(f.toQFont(), &device).capHeight(); } double QFontProvider::ascent(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).ascent(); } double QFontProvider::descent(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).descent(); } double QFontProvider::underlinePos(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).underlinePos(); } double QFontProvider::lineWidth(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).lineWidth(); } double QFontProvider::strikeOutPos(const Font& f) const { return QFontMetricsF(f.toQFont(), &device).strikeOutPos(); } bool QFontProvider::inFont(const Font& f, char32_t ucs4) const { // NOTE: QFontMetricsF::inFontUcs4 is unreliable for our use case because it uses Qt's fallback // system even if the flag noFontMerging is set, and returns true if the character // is found in any of the fallbacks. We need to use instead QRawFont, which represents the // *actual* font, not Qt's interpretation of the query. From QRawFont we can query the glyph index // of a character (zero if not in font) and the bounding rectangle of the actual glyph at that index. QRawFont qRawFont = QRawFont::fromFont(f.toQFont()); int glyphIndex = qRawFont.glyphIndexesForString(QString(QChar::fromUcs4(ucs4)))[0]; if (glyphIndex == 0) { return false; } //! @NOTE some symbols in fonts dont have glyph. For example U+ee80 //! exists in Bravura.otf but doesn't have glyph //! so QFontMetricsF returns true in that case return qRawFont.boundingRect(glyphIndex).isValid(); } double QFontProvider::horizontalAdvance(const Font& f, const String& string) const { return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(string); } double QFontProvider::horizontalAdvance(const Font& f, char32_t ucs4) const { if (Char::requiresSurrogates(ucs4)) { return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(String::fromUcs4(ucs4)); } return QFontMetricsF(f.toQFont(), &device).horizontalAdvance(static_cast(ucs4)); } RectF QFontProvider::boundingRect(const Font& f, const String& string) const { return RectF::fromQRectF(QFontMetricsF(f.toQFont(), &device).boundingRect(string)); } RectF QFontProvider::boundingRect(const Font& f, char32_t ucs4) const { if (Char::requiresSurrogates(ucs4)) { return RectF::fromQRectF(QFontMetrics(f.toQFont(), &device).boundingRect(String::fromUcs4(ucs4))); } if (f.type() == Font::Type::MusicSymbol || f.type() == Font::Type::MusicSymbolText) { // QFontMetrics::boundingRect returns pixel values obtained by rasterization of the font. // QPainterPath::boundingRect works from the actual vector shape so it's more accurate. // There is still some discretization going on so we can make it even more accurate by upscaling. // CAUTION: More expensive! Ok for music glyphs because they are cached. QFont qf = f.toQFont(); qf = QFont(qf, &device); static constexpr double UPSCALING = 4.0; qf.setPointSizeF(qf.pointSizeF() * UPSCALING); QPainterPath path; path.addText(QPointF(), qf, QString(static_cast(ucs4))); return RectF::fromQRectF(path.boundingRect()).scaled(SizeF(1.0 / UPSCALING, 1.0 / UPSCALING)); } return RectF::fromQRectF(QFontMetricsF(f.toQFont(), &device).boundingRect(static_cast(ucs4))); } RectF QFontProvider::tightBoundingRect(const Font& f, const String& string) const { auto boundingRect = QFontMetricsF(f.toQFont(), &device).tightBoundingRect(string); if (!boundingRect.isValid()) { // fix for https://github.com/musescore/MuseScore/issues/19503 - Qt can return garbage bounding rectangles that corrupt layout return RectF(); } return RectF::fromQRectF(boundingRect); }