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