1 /*
2     SPDX-FileCopyrightText: 2020-2020 Gustavo Carneiro <gcarneiroa@hotmail.com>
3     SPDX-FileCopyrightText: 2007-2008 Robert Knight <robertknight@gmail.com>
4     SPDX-FileCopyrightText: 1997, 1998 Lars Doelle <lars.doelle@on-line.de>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 // Own
10 #include "TerminalFonts.h"
11 
12 // Konsole
13 #include "konsoledebug.h"
14 #include "session/Session.h"
15 #include "session/SessionController.h"
16 #include "session/SessionManager.h"
17 #include "terminalDisplay/TerminalDisplay.h"
18 
19 // Qt
20 #include <QFont>
21 #include <QFontMetrics>
22 
23 namespace Konsole
24 {
TerminalFont(QWidget * parent)25 TerminalFont::TerminalFont(QWidget *parent)
26     : m_parent(parent)
27     , m_lineSpacing(0)
28     , m_fontHeight(1)
29     , m_fontWidth(1)
30     , m_fontAscent(1)
31     , m_boldIntense(false)
32     , m_antialiasText(true)
33     , m_useFontLineCharacters(false)
34     , m_profile(nullptr)
35 {
36 }
37 
applyProfile(const Profile::Ptr & profile)38 void TerminalFont::applyProfile(const Profile::Ptr &profile)
39 {
40     m_profile = profile;
41     m_antialiasText = profile->antiAliasFonts();
42     m_boldIntense = profile->boldIntense();
43     m_useFontLineCharacters = profile->useFontLineCharacters();
44     m_lineSpacing = uint(profile->lineSpacing());
45     setVTFont(profile->font());
46 }
47 
setVTFont(const QFont & f)48 void TerminalFont::setVTFont(const QFont &f)
49 {
50     QFont newFont(f);
51     int strategy = 0;
52 
53     // hint that text should be drawn with- or without anti-aliasing.
54     // depending on the user's font configuration, this may not be respected
55     strategy |= m_antialiasText ? QFont::PreferAntialias : QFont::NoAntialias;
56 
57     // Konsole cannot handle non-integer font metrics
58     // TODO: Qt6 will remove ForceIntegerMetrics
59     // "Use QFontMetrics to retrieve rounded font metrics."
60     strategy |= QFont::ForceIntegerMetrics;
61 
62     // In case the provided font doesn't have some specific characters it should
63     // fall back to a Monospace fonts.
64     newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
65 
66     // Try to check that a good font has been loaded.
67     // For some fonts, ForceIntegerMetrics causes height() == 0 which
68     // will cause Konsole to crash later.
69     QFontMetrics fontMetrics2(newFont);
70     if (fontMetrics2.height() < 1) {
71         qCDebug(KonsoleDebug) << "The font " << newFont.toString() << " has an invalid height()";
72         // Ask for a generic font so at least it is usable.
73         // Font listed in profile's dialog will not be updated.
74         newFont = QFont(QStringLiteral("Monospace"));
75         // Set style strategy without ForceIntegerMetrics for the font
76         // TODO: Qt6 will remove ForceIntegerMetrics
77         // "Use QFontMetrics to retrieve rounded font metrics."
78         strategy &= ~QFont::ForceIntegerMetrics;
79         newFont.setStyleHint(QFont::TypeWriter, QFont::StyleStrategy(strategy));
80         qCDebug(KonsoleDebug) << "Font changed to " << newFont.toString();
81     }
82 
83     // experimental optimization.  Konsole assumes that the terminal is using a
84     // mono-spaced font, in which case kerning information should have an effect.
85     // Disabling kerning saves some computation when rendering text.
86     newFont.setKerning(false);
87 
88     // "Draw intense colors in bold font" feature needs to use different font weights. StyleName
89     // property, when set, doesn't allow weight changes. Since all properties (weight, stretch,
90     // italic, etc) are stored in QFont independently, in almost all cases styleName is not needed.
91     newFont.setStyleName(QString());
92 
93     if (newFont == qobject_cast<QWidget *>(m_parent)->font()) {
94         // Do not process the same font again
95         return;
96     }
97 
98     QFontInfo fontInfo(newFont);
99 
100     // QFontInfo::fixedPitch() appears to not match QFont::fixedPitch() - do not test it.
101     // related?  https://bugreports.qt.io/browse/QTBUG-34082
102     if (fontInfo.family() != newFont.family() || !qFuzzyCompare(fontInfo.pointSizeF(), newFont.pointSizeF()) || fontInfo.styleHint() != newFont.styleHint()
103         || fontInfo.weight() != newFont.weight() || fontInfo.style() != newFont.style() || fontInfo.underline() != newFont.underline()
104         || fontInfo.strikeOut() != newFont.strikeOut() || fontInfo.rawMode() != newFont.rawMode()) {
105         const QString nonMatching = QString::asprintf("%s,%g,%d,%d,%d,%d,%d,%d,%d,%d",
106                                                       qPrintable(fontInfo.family()),
107                                                       fontInfo.pointSizeF(),
108                                                       -1, // pixelSize is not used
109                                                       static_cast<int>(fontInfo.styleHint()),
110                                                       fontInfo.weight(),
111                                                       static_cast<int>(fontInfo.style()),
112                                                       static_cast<int>(fontInfo.underline()),
113                                                       static_cast<int>(fontInfo.strikeOut()),
114                                                       // Intentional newFont use - fixedPitch is bugged, see comment above
115                                                       static_cast<int>(newFont.fixedPitch()),
116                                                       static_cast<int>(fontInfo.rawMode()));
117         qCDebug(KonsoleDebug) << "The font to use in the terminal can not be matched exactly on your system.";
118         qCDebug(KonsoleDebug) << " Selected: " << newFont.toString();
119         qCDebug(KonsoleDebug) << " System  : " << nonMatching;
120     }
121 
122     qobject_cast<QWidget *>(m_parent)->setFont(newFont);
123     fontChange(newFont);
124 }
125 
getVTFont() const126 QFont TerminalFont::getVTFont() const
127 {
128     return qobject_cast<QWidget *>(m_parent)->font();
129 }
130 
increaseFontSize()131 void TerminalFont::increaseFontSize()
132 {
133     QFont font = qobject_cast<QWidget *>(m_parent)->font();
134     font.setPointSizeF(font.pointSizeF() + 1);
135     setVTFont(font);
136 }
137 
decreaseFontSize()138 void TerminalFont::decreaseFontSize()
139 {
140     const qreal MinimumFontSize = 6;
141 
142     QFont font = qobject_cast<QWidget *>(m_parent)->font();
143     font.setPointSizeF(qMax(font.pointSizeF() - 1, MinimumFontSize));
144     setVTFont(font);
145 }
146 
resetFontSize()147 void TerminalFont::resetFontSize()
148 {
149     const qreal MinimumFontSize = 6;
150 
151     TerminalDisplay *display = qobject_cast<TerminalDisplay *>(m_parent);
152     QFont font = display->font();
153     Profile::Ptr currentProfile = SessionManager::instance()->sessionProfile(display->sessionController()->session());
154     const qreal defaultFontSize = currentProfile->font().pointSizeF();
155     font.setPointSizeF(qMax(defaultFontSize, MinimumFontSize));
156     setVTFont(font);
157 }
158 
setLineSpacing(uint i)159 void TerminalFont::setLineSpacing(uint i)
160 {
161     m_lineSpacing = i;
162     fontChange(qobject_cast<QWidget *>(m_parent)->font());
163 }
164 
lineSpacing() const165 uint TerminalFont::lineSpacing() const
166 {
167     return m_lineSpacing;
168 }
169 
fontHeight() const170 int TerminalFont::fontHeight() const
171 {
172     return m_fontHeight;
173 }
174 
fontWidth() const175 int TerminalFont::fontWidth() const
176 {
177     return m_fontWidth;
178 }
179 
fontAscent() const180 int TerminalFont::fontAscent() const
181 {
182     return m_fontAscent;
183 }
184 
boldIntense() const185 bool TerminalFont::boldIntense() const
186 {
187     return m_boldIntense;
188 }
189 
antialiasText() const190 bool TerminalFont::antialiasText() const
191 {
192     return m_antialiasText;
193 }
194 
useFontLineCharacters() const195 bool TerminalFont::useFontLineCharacters() const
196 {
197     return m_useFontLineCharacters;
198 }
199 
fontChange(const QFont &)200 void TerminalFont::fontChange(const QFont &)
201 {
202     QFontMetrics fm(qobject_cast<QWidget *>(m_parent)->font());
203     m_fontHeight = fm.height() + m_lineSpacing;
204 
205     Q_ASSERT(m_fontHeight > 0);
206 
207     m_fontWidth = fm.horizontalAdvance(QLatin1Char('M'));
208 
209     if (m_fontWidth < 1) {
210         m_fontWidth = 1;
211     }
212 
213     m_fontAscent = fm.ascent();
214 
215     qobject_cast<TerminalDisplay *>(m_parent)->propagateSize();
216 }
217 
218 }
219