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