MythTV master
mythfontproperties.cpp
Go to the documentation of this file.
1
3
4#include <cmath>
5
6#include <QCoreApplication>
7#include <QDomDocument>
8#include <QFontInfo>
9#include <QFontDatabase>
10#include <QRect>
11#include <QRegularExpression>
12
14#include "libmythbase/mythdb.h"
16
17#include "mythuihelper.h"
18#include "mythmainwindow.h"
19#include "mythuitype.h"
20
21#define LOC QString("MythFontProperties: ")
22
23#define KEEP_OLD_NAMES 1 // NOLINT(cppcoreguidelines-macro-usage)
24
27
29{
30 Zoom();
31 CalcHash();
32}
33
34void MythFontProperties::SetFace(const QFont &face)
35{
36 m_face = face;
37 CalcHash();
38}
39
40QFont MythFontProperties::face(void) const
41{
42 QFont face = m_face;
43
44 face.setPixelSize(face.pixelSize() *
45 (static_cast<double>(s_zoomPercent) / 100.0));
46 return face;
47}
48
49void MythFontProperties::SetColor(const QColor &color)
50{
51 m_brush.setColor(color);
52 CalcHash();
53}
54
55void MythFontProperties::SetShadow(bool on, const QPoint offset,
56 const QColor &color, int alpha)
57{
58 m_hasShadow = on;
59 m_shadowOffset = offset;
61 m_shadowAlpha = alpha;
62 CalcHash();
63}
64
65void MythFontProperties::SetOutline(bool on, const QColor &color,
66 int size, int alpha)
67{
68 m_hasOutline = on;
70 m_outlineSize = size;
71 m_outlineAlpha = alpha;
72 CalcHash();
73}
74
75void MythFontProperties::GetShadow(QPoint &offset, QColor &color, int &alpha) const
76{
77 offset = m_shadowOffset;
79 alpha = m_shadowAlpha;
80}
81
82void MythFontProperties::GetOutline(QColor &color, int &size, int &alpha) const
83{
85 size = m_outlineSize;
86 alpha = m_outlineAlpha;
87}
88
90{
91 if (m_bFreeze)
92 return;
93
94 m_hash = QString("%1%2%3%4").arg(m_face.toString(),
95 m_brush.color().name())
96 .arg(static_cast<int>(m_hasShadow))
97 .arg(static_cast<int>(m_hasOutline));
98
99 if (m_hasShadow)
100 {
101 m_hash += QString("%1%2%3%4").arg(m_shadowOffset.x())
102 .arg(m_shadowOffset.y()).arg(m_shadowColor.name())
103 .arg(m_shadowAlpha);
104 }
105
106 if (m_hasOutline)
107 m_hash += QString("%1%2%3").arg(m_outlineColor.name())
108 .arg(m_outlineSize).arg(m_outlineAlpha);
109
110 m_hash += QString("Z%1").arg(s_zoomPercent);
111}
112
114{
115 QMutexLocker locker(&s_zoomLock);
116 if (s_zoomPercent == 0)
117 s_zoomPercent = gCoreContext->GetNumSetting("GUITEXTZOOM", 100);
118}
119
121{
122 QMutexLocker locker(&s_zoomLock);
123
124 gCoreContext->SaveSetting("GUITEXTZOOM", QString::number(percent));
125 s_zoomPercent = percent;
126}
127
129{
130 m_face.setPixelSize(m_relativeSize * height);
131}
132
134{
135 QRect rect = GetMythMainWindow()->GetUIScreenRect();
136 Rescale(rect.height());
137}
138
140{
141 int newStretch = lroundf((float)m_stretch * ((float)stretch / 100.0F));
142
143 if (newStretch <= 0)
144 newStretch = 1;
145
146 m_face.setStretch(newStretch);
147}
148
150{
151 QSize baseSize = GetMythUI()->GetBaseSize();
152 m_relativeSize = size / (float)(baseSize.height());
153 m_face.setPixelSize(GetMythMainWindow()->NormY(lroundf(size)));
154}
155
157{
158 float pixels = (float)points / 72.0F * 100.0F;
159 SetPixelSize(pixels);
160}
161
163{
164 m_bFreeze = true;
165}
166
168{
169 m_bFreeze = false;
170 CalcHash();
171}
172
174 const QString &filename,
175 const QDomElement &element,
176 MythUIType *parent,
177 bool addToGlobal,
178 bool showWarnings)
179{
180 // Crappy, but cached. Move to GlobalFontMap?
181
182 static bool s_showAvailable = true;
183 bool fromBase = false;
184 auto *newFont = new MythFontProperties();
185 newFont->Freeze();
186
187 if (element.tagName() == "font")
188 {
189 LOG(VB_GENERAL, LOG_WARNING, LOC +
190 QString("File %1: Use of 'font' is deprecated in favour of "
191 "'fontdef'") .arg(filename));
192 }
193
194 QString name = element.attribute("name", "");
195 if (name.isEmpty())
196 {
197 VERBOSE_XML(VB_GENERAL, LOG_ERR,
198 filename, element, "Font requires a name");
199 delete newFont;
200 return nullptr;
201 }
202
203 QString base = element.attribute("from", "");
204
205 if (!base.isEmpty())
206 {
207 MythFontProperties *tmp = nullptr;
208
209 if (parent)
210 tmp = parent->GetFont(base);
211
212 if (!tmp)
213 tmp = GetGlobalFontMap()->GetFont(base);
214
215 if (!tmp)
216 {
217 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
218 QString("Specified base font '%1' does not exist.").arg(base));
219
220 delete newFont;
221 return nullptr;
222 }
223
224 *newFont = *tmp;
225 fromBase = true;
226 }
227
228 int size = -1;
229 int pixelsize = -1;
230
231 QString face = element.attribute("face", "");
232 if (face.isEmpty())
233 {
234 if (!fromBase)
235 {
236 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
237 "Font needs a face");
238 delete newFont;
239 return nullptr;
240 }
241 }
242 else
243 {
244 newFont->m_face.setFamily(face);
245 }
246
247 if (addToGlobal && GetGlobalFontMap()->Contains(name))
248 {
250 if (showWarnings)
251 {
252 VERBOSE_XML(VB_GENERAL, LOG_WARNING, filename, element,
253 QString("Attempting to define '%1'\n\t\t\t"
254 "with face '%2', but it already "
255 "exists with face '%3'")
256 .arg(name, QFontInfo(newFont->m_face).family(),
257 (tmp) ? QFontInfo(tmp->m_face).family() : "ERROR"));
258 }
259 delete newFont;
260 return nullptr;
261 }
262
263 QString hint = element.attribute("stylehint", "");
264 if (!hint.isEmpty())
265 {
266 newFont->m_face.setStyleHint((QFont::StyleHint)hint.toInt());
267 }
268
269 for (QDomNode child = element.firstChild(); !child.isNull();
270 child = child.nextSibling())
271 {
272 QDomElement info = child.toElement();
273 if (!info.isNull())
274 {
275 if (info.tagName() == "size")
276 {
277 size = getFirstText(info).toInt();
278 }
279 else if (info.tagName() == "pixelsize")
280 {
281 pixelsize = getFirstText(info).toInt();
282 }
283 else if (info.tagName() == "color")
284 {
285 newFont->m_brush = QBrush(QColor(getFirstText(info)));
286 }
287 else if (info.tagName() == "gradient")
288 {
289 newFont->m_brush = parseGradient(info);
290 }
291 else if (info.tagName() == "shadowcolor")
292 {
293 newFont->m_shadowColor = QColor(getFirstText(info));
294 }
295 else if (info.tagName() == "shadowoffset")
296 {
297 newFont->m_hasShadow = true;
298 newFont->m_shadowOffset = parsePoint(info, false);
299 }
300 else if (info.tagName() == "shadowalpha")
301 {
302 newFont->m_shadowAlpha = getFirstText(info).toInt();
303 }
304 else if (info.tagName() == "outlinecolor")
305 {
306 newFont->m_outlineColor = QColor(getFirstText(info));
307 }
308 else if (info.tagName() == "outlinesize")
309 {
310 newFont->m_hasOutline = true;
311 newFont->m_outlineSize = getFirstText(info).toInt();
312 }
313 else if (info.tagName() == "outlinealpha")
314 {
315 newFont->m_outlineAlpha = getFirstText(info).toInt();
316 }
317 else if (info.tagName() == "italics")
318 {
319 newFont->m_face.setItalic(parseBool(info));
320 }
321 else if (info.tagName() == "letterspacing")
322 {
323 newFont->m_face.setLetterSpacing(QFont::AbsoluteSpacing,
324 getFirstText(info).toInt());
325 }
326 else if (info.tagName() == "wordspacing")
327 {
328 newFont->m_face.setWordSpacing(getFirstText(info).toInt());
329 }
330 else if (info.tagName() == "decoration")
331 {
332 QString dec = getFirstText(info).toLower();
333 QStringList values = dec.split(',');
334
335 QStringList::Iterator it;
336 for ( it = values.begin(); it != values.end(); ++it )
337 {
338 if (*it == "underline")
339 newFont->m_face.setUnderline(true);
340 else if (*it == "overline")
341 newFont->m_face.setOverline(true);
342 else if (*it == "strikeout")
343 newFont->m_face.setStrikeOut(true);
344 }
345 }
346 else if (info.tagName() == "weight")
347 {
348 QString weight = getFirstText(info).toLower();
349
350 if (weight == "thin" ||
351 weight == "1")
352 newFont->m_face.setWeight(QFont::Thin);
353 else if (weight == "extralight" ||
354#ifdef KEEP_OLD_NAMES
355 weight == "ultralight" ||
356#endif
357 weight == "2")
358 newFont->m_face.setWeight(QFont::ExtraLight);
359 else if (weight == "light" ||
360 weight == "3")
361 newFont->m_face.setWeight(QFont::Light);
362 else if (weight == "normal" ||
363 weight == "4")
364 newFont->m_face.setWeight(QFont::Normal); // NOLINT(bugprone-branch-clone)
365 else if (weight == "medium" ||
366 weight == "5")
367 newFont->m_face.setWeight(QFont::Medium);
368 else if (weight == "demibold" ||
369 weight == "6")
370 newFont->m_face.setWeight(QFont::DemiBold);
371 else if (weight == "bold" ||
372 weight == "7")
373 newFont->m_face.setWeight(QFont::Bold);
374 else if (weight == "extrabold" ||
375 weight == "8")
376 newFont->m_face.setWeight(QFont::ExtraBold);
377 else if (weight == "black" ||
378#ifdef KEEP_OLD_NAMES
379 weight == "ultrablack" ||
380#endif
381 weight == "9")
382 newFont->m_face.setWeight(QFont::Black);
383 else
384 newFont->m_face.setWeight(QFont::Normal);
385 }
386 else if (info.tagName() == "stretch")
387 {
388 QString stretch = getFirstText(info).toLower();
389
390 if (stretch == "ultracondensed" ||
391 stretch == "1")
392 newFont->m_stretch = QFont::UltraCondensed;
393 else if (stretch == "extracondensed" ||
394 stretch == "2")
395 newFont->m_stretch = QFont::ExtraCondensed;
396 else if (stretch == "condensed" ||
397 stretch == "3")
398 newFont->m_stretch = QFont::Condensed;
399 else if (stretch == "semicondensed" ||
400 stretch == "4")
401 newFont->m_stretch = QFont::SemiCondensed;
402 else if (stretch == "unstretched" ||
403 stretch == "5")
404 newFont->m_stretch = QFont::Unstretched; // NOLINT(bugprone-branch-clone)
405 else if (stretch == "semiexpanded" ||
406 stretch == "6")
407 newFont->m_stretch = QFont::SemiExpanded;
408 else if (stretch == "expanded" ||
409 stretch == "7")
410 newFont->m_stretch = QFont::Expanded;
411 else if (stretch == "extraexpanded" ||
412 stretch == "8")
413 newFont->m_stretch = QFont::ExtraExpanded;
414 else if (stretch == "ultraexpanded" ||
415 stretch == "9")
416 newFont->m_stretch = QFont::UltraExpanded;
417 else
418 newFont->m_stretch = QFont::Unstretched;
419
420 newFont->m_face.setStretch(newFont->m_stretch);
421 }
422 else
423 {
424 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, info,
425 QString("Unknown tag in font '%1'").arg(name));
426 delete newFont;
427 return nullptr;
428 }
429 }
430 }
431
432 if (size <= 0 && pixelsize <= 0 && !fromBase)
433 {
434 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
435 "Font size must be greater than 0.");
436 delete newFont;
437 return nullptr;
438 }
439 if (pixelsize > 0)
440 {
441 newFont->SetPixelSize(pixelsize);
442 }
443 else if (size > 0)
444 {
445 newFont->SetPointSize(size);
446 }
447
448 newFont->Unfreeze();
449
450 static const QRegularExpression re { "\\[.*]" };
451 QFontInfo fi(newFont->m_face);
452 QString fi_family =
453 fi.family().remove(re).trimmed();
454 if (newFont->m_face.family() != fi_family)
455 {
456 VERBOSE_XML(VB_GENERAL, LOG_ERR, filename, element,
457 QString("Failed to load '%1', got '%2' instead")
458 .arg(newFont->m_face.family(), fi.family()));
459
460 if (s_showAvailable)
461 {
462 LOG(VB_GUI, LOG_DEBUG, "Available fonts:");
463
464#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
465 QFontDatabase database;
466 QStringList families = database.families();
467#else
468 QStringList families = QFontDatabase::families();
469#endif
470 for (const QString & family : std::as_const(families))
471 {
472 QStringList family_styles;
473
474 family_styles << family + "::";
475#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
476 QStringList styles = database.styles(family);
477#else
478 QStringList styles = QFontDatabase::styles(family);
479#endif
480 for (const QString & style : std::as_const(styles))
481 {
482 family_styles << style + ":";
483
484 QString sizes;
485 bool tic = false;
486#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
487 QList<int> pointList = database.smoothSizes(family, style);
488#else
489 QList<int> pointList = QFontDatabase::smoothSizes(family, style);
490#endif
491 for (int points : std::as_const(pointList))
492 {
493 if (tic)
494 sizes += ",";
495 tic = true;
496 sizes += QString::number(points);
497 }
498 sizes += "; ";
499
500 family_styles << sizes.trimmed();
501 }
502 LOG(VB_GUI, LOG_DEBUG, family_styles.join(" "));
503 }
504 s_showAvailable = false;
505 }
506 }
507 else
508 {
509 VERBOSE_XML(VB_GUI, LOG_DEBUG, filename, element,
510 QString("loaded '%1'").arg(fi.family()));
511 }
512
513 if (addToGlobal)
514 GetGlobalFontMap()->AddFont(name, newFont);
515
516 return newFont;
517}
518
519static FontMap *gFontMap = nullptr;
520
521// FIXME: remove
522QMap<QString, fontProp> globalFontMap;
523
525{
526 if (text.isEmpty())
527 return nullptr;
528
529 if (m_fontMap.contains(text))
530 return &(m_fontMap[text]);
531 return nullptr;
532}
533
534bool FontMap::AddFont(const QString &text, MythFontProperties *font)
535{
536 if (!font || text.isEmpty())
537 return false;
538
539 if (m_fontMap.contains(text))
540 {
541 LOG(VB_GENERAL, LOG_ERR, LOC +
542 QString("Already have a font: %1").arg(text));
543 return false;
544 }
545
546 m_fontMap[text] = *font;
547
548 {
549 /* FIXME backwards compat, remove */
550 fontProp oldf;
551
552 oldf.face = font->m_face;
553 oldf.color = font->m_brush.color();
554 if (font->m_hasShadow)
555 {
556 oldf.dropColor = font->m_shadowColor;
557 oldf.shadowOffset = font->m_shadowOffset;
558 }
559
560 globalFontMap[text] = oldf;
561 }
562
563 return true;
564}
565
566bool FontMap::Contains(const QString &text)
567{
568 return m_fontMap.contains(text);
569}
570
572{
573 m_fontMap.clear();
574
575 //FIXME: remove
576 globalFontMap.clear();
577}
578
579void FontMap::Rescale(int height)
580{
581 if (height <= 0)
582 {
583 QRect rect = GetMythMainWindow()->GetUIScreenRect();
584 height = rect.height();
585 }
586
587 QMap<QString, MythFontProperties>::iterator it;
588 for (it = m_fontMap.begin(); it != m_fontMap.end(); ++it)
589 {
590 (*it).Rescale(height);
591 }
592}
593
595{
596 if (!gFontMap)
597 gFontMap = new FontMap();
598 return gFontMap;
599}
600
602{
604}
bool AddFont(const QString &text, MythFontProperties *fontProp)
static FontMap * GetGlobalFontMap(void)
QMap< QString, MythFontProperties > m_fontMap
void Clear(void)
void Rescale(int height=0)
FontMap()=default
MythFontProperties * GetFont(const QString &text)
bool Contains(const QString &text)
void SaveSetting(const QString &key, int newValue)
int GetNumSetting(const QString &key, int defaultval=0)
static void SetZoom(uint zoom_percent)
void AdjustStretch(int stretch)
void SetOutline(bool on, const QColor &color, int size, int alpha)
void SetFace(const QFont &face)
QColor color(void) const
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
static MythFontProperties * ParseFromXml(const QString &filename, const QDomElement &element, MythUIType *parent=nullptr, bool addToGlobal=false, bool showWarnings=true)
static QMutex s_zoomLock
void SetShadow(bool on, QPoint offset, const QColor &color, int alpha)
static void Zoom(void)
void GetOutline(QColor &color, int &size, int &alpha) const
QFont face(void) const
void SetColor(const QColor &color)
void SetPixelSize(float size)
void SetPointSize(uint points)
int NormY(int Y) const
QSize GetBaseSize() const
The base class on which all widgets and screens are based.
Definition: mythuitype.h:86
MythFontProperties * GetFont(const QString &text) const
static MythPoint parsePoint(const QString &text, bool normalize=true)
static QBrush parseGradient(const QDomElement &element)
static QString getFirstText(QDomElement &element)
static bool parseBool(const QString &text)
unsigned int uint
Definition: compat.h:68
static guint32 * tmp
Definition: goom_core.cpp:26
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOC
QMap< QString, fontProp > globalFontMap
static FontMap * gFontMap
#define KEEP_OLD_NAMES
FontMap * GetGlobalFontMap(void)
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
MythMainWindow * GetMythMainWindow(void)
MythUIHelper * GetMythUI()
dictionary info
Definition: azlyrics.py:7
QPoint shadowOffset
#define VERBOSE_XML(type, level, filename, element, msg)
Definition: xmlparsebase.h:15