File indexing completed on 2024-04-28 05:50:51

0001 /*
0002     SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
0003     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
0004     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 // Own
0010 #include "TerminalFonts.h"
0011 
0012 // Konsole
0013 #include "konsoledebug.h"
0014 #include "session/Session.h"
0015 #include "session/SessionController.h"
0016 #include "session/SessionManager.h"
0017 #include "terminalDisplay/TerminalDisplay.h"
0018 
0019 // Qt
0020 #include <QFont>
0021 #include <QFontMetrics>
0022 
0023 namespace Konsole
0024 {
0025 TerminalFont::TerminalFont(QWidget *parent)
0026     : m_parent(parent)
0027 {
0028 }
0029 
0030 void TerminalFont::applyProfile(const Profile::Ptr &profile)
0031 {
0032     m_profile = profile;
0033     m_antialiasText = profile->antiAliasFonts();
0034     m_boldIntense = profile->boldIntense();
0035     m_useFontLineCharacters = profile->useFontLineCharacters();
0036     m_lineSpacing = uint(profile->lineSpacing());
0037     setVTFont(profile->font());
0038     extraFonts[0] = profile->emojiFont();
0039     if (extraFonts[0] == QFont()) {
0040         extraFonts[0] = QFont(QStringLiteral("Noto Color Emoji"));
0041         // extraFonts[0] = QFont(QStringLiteral("Apple Color Emoji"));
0042         // extraFonts[0] = QFont(QStringLiteral("Emoji One"));
0043         if (extraFonts[0] == QFont()) {
0044             extraFonts.remove(0);
0045         }
0046     }
0047 }
0048 
0049 void TerminalFont::setVTFont(const QFont &f)
0050 {
0051     QFont newFont(f);
0052     int strategy = 0;
0053 
0054     // hint that text should be drawn with- or without anti-aliasing.
0055     // depending on the user's font configuration, this may not be respected
0056     strategy |= m_antialiasText ? QFont::PreferAntialias : QFont::NoAntialias;
0057 
0058     // In case the provided font doesn't have some specific characters it should
0059     // fall back to a Monospace fonts.
0060     newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
0061 
0062     // Try to check that a good font has been loaded.
0063     // For some fonts, ForceIntegerMetrics causes height() == 0 which
0064     // will cause Konsole to crash later.
0065     QFontMetrics fontMetrics2(newFont);
0066     if (fontMetrics2.height() < 1) {
0067         qCDebug(KonsoleDebug) << "The font " << newFont.toString() << " has an invalid height()";
0068         // Ask for a generic font so at least it is usable.
0069         // Font listed in profile's dialog will not be updated.
0070         newFont = QFont(QStringLiteral("Monospace"));
0071 
0072         newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
0073         qCDebug(KonsoleDebug) << "Font changed to " << newFont.toString();
0074     }
0075 
0076     // experimental optimization.  Konsole assumes that the terminal is using a
0077     // mono-spaced font, in which case kerning information should have an effect.
0078     // Disabling kerning saves some computation when rendering text.
0079     newFont.setKerning(false);
0080 
0081     // QFont::ForceIntegerMetrics has been removed.
0082     // Set full hinting instead to ensure the letters are aligned properly.
0083     newFont.setHintingPreference(QFont::PreferFullHinting);
0084 
0085     // "Draw intense colors in bold font" feature needs to use different font weights. StyleName
0086     // property, when set, doesn't allow weight changes. Since all properties (weight, stretch,
0087     // italic, etc) are stored in QFont independently, in almost all cases styleName is not needed.
0088     newFont.setStyleName(QString());
0089 
0090     if (newFont == qobject_cast<QWidget *>(m_parent)->font()) {
0091         // Do not process the same font again
0092         return;
0093     }
0094 
0095     QFontInfo fontInfo(newFont);
0096 
0097     // clang-format off
0098     // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch() - do not test it.
0099     // related?  https://bugreports.qt.io/browse/QTBUG-34082
0100     if (fontInfo.family() != newFont.family()
0101             || !qFuzzyCompare(fontInfo.pointSizeF(), newFont.pointSizeF())
0102             || fontInfo.styleHint() != newFont.styleHint()
0103             || fontInfo.weight() != newFont.weight()
0104             || fontInfo.style() != newFont.style()
0105             || fontInfo.underline() != newFont.underline()
0106             || fontInfo.strikeOut() != newFont.strikeOut()
0107     ) { // clang-format on
0108         static const char format[] = "%s,%g,%d,%d,%d,%d,%d,%d,%d";
0109         const QString nonMatching = QString::asprintf(format,
0110                                                       qPrintable(fontInfo.family()),
0111                                                       fontInfo.pointSizeF(),
0112                                                       -1, // pixelSize is not used
0113                                                       static_cast<int>(fontInfo.styleHint()),
0114                                                       fontInfo.weight(),
0115                                                       static_cast<int>(fontInfo.style()),
0116                                                       static_cast<int>(fontInfo.underline()),
0117                                                       static_cast<int>(fontInfo.strikeOut()),
0118                                                       // Intentional newFont use - fixedPitch is bugged, see comment above
0119                                                       static_cast<int>(newFont.fixedPitch()));
0120         qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
0121         qCDebug(KonsoleDebug) << " Selected: " << newFont.toString();
0122         qCDebug(KonsoleDebug) << " System  : " << nonMatching;
0123     }
0124 
0125     qobject_cast<QWidget *>(m_parent)->setFont(newFont);
0126     fontChange(newFont);
0127 }
0128 
0129 QFont TerminalFont::getVTFont() const
0130 {
0131     return qobject_cast<QWidget *>(m_parent)->font();
0132 }
0133 
0134 void TerminalFont::increaseFontSize()
0135 {
0136     QFont font = qobject_cast<QWidget *>(m_parent)->font();
0137     font.setPointSizeF(font.pointSizeF() + 1);
0138     setVTFont(font);
0139 }
0140 
0141 void TerminalFont::decreaseFontSize()
0142 {
0143     const qreal MinimumFontSize = 6;
0144 
0145     QFont font = qobject_cast<QWidget *>(m_parent)->font();
0146     font.setPointSizeF(qMax(font.pointSizeF() - 1, MinimumFontSize));
0147     setVTFont(font);
0148 }
0149 
0150 void TerminalFont::resetFontSize()
0151 {
0152     const qreal MinimumFontSize = 6;
0153 
0154     TerminalDisplay *display = qobject_cast<TerminalDisplay *>(m_parent);
0155     QFont font = display->font();
0156     Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(display->sessionController()->session());
0157     const qreal defaultFontSize = currentProfile->font().pointSizeF();
0158     font.setPointSizeF(qMax(defaultFontSize, MinimumFontSize));
0159     setVTFont(font);
0160 }
0161 
0162 void TerminalFont::setLineSpacing(uint i)
0163 {
0164     m_lineSpacing = i;
0165     fontChange(qobject_cast<QWidget *>(m_parent)->font());
0166 }
0167 
0168 uint TerminalFont::lineSpacing() const
0169 {
0170     return m_lineSpacing;
0171 }
0172 
0173 int TerminalFont::fontHeight() const
0174 {
0175     return m_fontHeight;
0176 }
0177 
0178 int TerminalFont::fontWidth() const
0179 {
0180     return m_fontWidth;
0181 }
0182 
0183 int TerminalFont::fontAscent() const
0184 {
0185     return m_fontAscent;
0186 }
0187 
0188 int TerminalFont::lineWidth() const
0189 {
0190     return m_lineWidth;
0191 }
0192 
0193 qreal TerminalFont::underlinePos() const
0194 {
0195     return m_underlinePos;
0196 }
0197 
0198 int TerminalFont::strikeOutPos() const
0199 {
0200     return m_strikeOutPos;
0201 }
0202 
0203 qreal TerminalFont::overlinePos() const
0204 {
0205     return m_overlinePos;
0206 }
0207 
0208 bool TerminalFont::boldIntense() const
0209 {
0210     return m_boldIntense;
0211 }
0212 
0213 bool TerminalFont::antialiasText() const
0214 {
0215     return m_antialiasText;
0216 }
0217 
0218 bool TerminalFont::useFontLineCharacters() const
0219 {
0220     return m_useFontLineCharacters;
0221 }
0222 
0223 void TerminalFont::fontChange(const QFont &)
0224 {
0225     QFontMetrics fm(qobject_cast<QWidget *>(m_parent)->font());
0226     m_fontHeight = fm.height() + m_lineSpacing;
0227 
0228     Q_ASSERT(m_fontHeight > 0);
0229 
0230     m_fontWidth = fm.horizontalAdvance(QLatin1Char('M'));
0231 
0232     if (m_fontWidth < 1) {
0233         m_fontWidth = 1;
0234     }
0235 
0236     m_fontAscent = fm.ascent();
0237     m_lineWidth = fm.lineWidth();
0238     m_underlinePos = qMin(static_cast<qreal>(fm.underlinePos()), fm.descent() - m_lineWidth / static_cast<qreal>(2));
0239     m_strikeOutPos = fm.strikeOutPos();
0240     m_overlinePos = qMin(static_cast<qreal>(fm.overlinePos()), fm.ascent() - m_lineWidth / static_cast<qreal>(2));
0241 
0242     qobject_cast<TerminalDisplay *>(m_parent)->propagateSize();
0243 }
0244 
0245 bool TerminalFont::hasExtraFont(int i) const
0246 {
0247     return extraFonts.contains(i);
0248 }
0249 
0250 QFont TerminalFont::getExtraFont(int i) const
0251 {
0252     return extraFonts[i];
0253 }
0254 }