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 : }
|