MythTV  master
subtitlescreen.cpp
Go to the documentation of this file.
1 #include <QFontMetrics>
2 
3 #include "mythlogging.h"
4 #include "mythfontproperties.h"
5 #include "mythuisimpletext.h"
6 #include "mythuishape.h"
7 #include "mythuiimage.h"
8 #include "mythpainter.h"
9 #include "subtitlescreen.h"
10 
11 #define LOC QString("Subtitles: ")
12 #define LOC_WARN QString("Subtitles Warning: ")
13 
14 // Class SubWrapper is used to attach caching structures to MythUIType
15 // objects in the SubtitleScreen's m_ChildrenList. If these objects
16 // inherit from SubWrapper, they can carry relevant information and an
17 // external caching object is not required.
18 //
19 // The following caching information is represented here:
20 // 1. Original m_Area, used for OptimiseDisplayedArea().
21 // 2. Which cc708 window it belongs to, used for Clear708Cache().
22 // 3. Subtitle expire time, primarily for AV subtitles.
24 {
25 protected:
26  SubWrapper(const MythRect &rect,
27  long long expireTime,
28  int whichImageCache = -1) :
29  m_swOrigArea(rect),
30  m_swWhichImageCache(whichImageCache),
31  m_swExpireTime(expireTime)
32  {
33  }
34 public:
35  long long GetExpireTime(void) const { return m_swExpireTime; }
36  MythRect GetOrigArea(void) const { return m_swOrigArea; }
37  int GetWhichImageCache(void) const { return m_swWhichImageCache; }
38 protected:
39  // Returns true if the object was deleted.
41  const int m_swWhichImageCache; // cc708 only; -1 = none
42  const long long m_swExpireTime; // avsubs only; -1 = none
43 };
44 
46 {
47 public:
48  SubSimpleText(const QString &text, const MythFontProperties &font,
49  const QRect &rect, Qt::Alignment align,
50  MythUIType *parent, const QString &name,
51  int whichImageCache, long long expireTime) :
52  MythUISimpleText(text, font, rect, align, parent, name),
53  SubWrapper(rect, expireTime, whichImageCache) {}
54 };
55 
56 class SubShape : public MythUIShape, public SubWrapper
57 {
58 public:
59  SubShape(MythUIType *parent, const QString &name, const MythRect &area,
60  int whichImageCache, long long expireTime) :
61  MythUIShape(parent, name),
62  SubWrapper(area, expireTime, whichImageCache) {}
63 };
64 
65 class SubImage : public MythUIImage, public SubWrapper
66 {
67 public:
68  SubImage(MythUIType *parent, const QString &name, const MythRect &area,
69  long long expireTime) :
70  MythUIImage(parent, name),
71  SubWrapper(area, expireTime) {}
72  MythImage *GetImage(void) { return m_Images[0]; }
73 };
74 
76 
77 // Class SubtitleFormat manages fonts and backgrounds for subtitles.
78 //
79 // Formatting is specified by the theme in the new file
80 // osd_subtitle.xml, in the osd_subtitle window. Subtitle types are
81 // text, teletext, 608, 708_0, 708_1, ..., 708_7. Each subtitle type
82 // has a fontdef component for the text and a shape component for the
83 // background. By default, the attributes of a subtitle type come
84 // from either the provider (e.g. font color, italics) or the system
85 // default (e.g. font family). These attributes can be overridden by
86 // the xml file.
87 //
88 // The fontdef name and the background shape name are both simply the
89 // name of the subtitle type. The fontdef and shape should ultimately
90 // inherit from the special value "provider", otherwise all provider
91 // values will be ignored.
92 //
93 // The following example forces .srt subtitles to be rendered in
94 // yellow text, FreeSans font, black shadow, with a translucent black
95 // background. The fontdef and shape names are "text" since the
96 // subtitles come from a .srt file. Note that in this example, color
97 // formatting controls in the .srt file will be ignored due to the
98 // explicit setting of yellow text.
99 //
100 // <?xml version="1.0" encoding="utf-8"?>
101 // <!DOCTYPE mythuitheme SYSTEM "http://www.mythtv.org/schema/mythuitheme.dtd">
102 // <mythuitheme>
103 // <window name="osd_subtitle">
104 // <fontdef name="text" face="FreeSans" from="provider">
105 // <color>#FFFF00</color>
106 // <shadowoffset>2,2</shadowoffset>
107 // <shadowcolor>#000000</shadowcolor>
108 // </fontdef>
109 // <shape name="text" from="provider">
110 // <fill color="#000000" alpha="100" />
111 // </shape>
112 // </window>
113 // </mythuitheme>
114 //
115 // All elements/attributes of fontdef and shape can be used. Note
116 // however that area and position are explicitly ignored, as these are
117 // dictated by the subtitle layout.
118 //
119 // This is implemented with almost no MythUI changes. Two copies of a
120 // "provider" object are created, with default/representative provider
121 // attributes. One copy is then "complemented" to have a different
122 // value for each attribute that a provider might change. The
123 // osd_subtitle.xml file is loaded twice, once with respect to each
124 // provider object, and the desired fontdef or shape is looked up.
125 // The two fontdefs or shapes are compared attribute by attribute, and
126 // each attribute that is different is an attribute that the provider
127 // may modify, whereas each identical attribute represents one that is
128 // fixed by the theme.
129 
131 {
132 public:
133  SubtitleFormat(void) = default;
134  ~SubtitleFormat(void);
135  MythFontProperties *GetFont(const QString &family,
136  const CC708CharacterAttribute &attr,
137  int pixelSize, int zoom, int stretch);
138  SubShape *GetBackground(MythUIType *parent, const QString &name,
139  const QString &family,
140  const CC708CharacterAttribute &attr,
141  const MythRect &area,
142  int whichImageCache,
143  int start, int duration);
144  int GetBackgroundAlpha(const QString &family);
145  static QString MakePrefix(const QString &family,
146  const CC708CharacterAttribute &attr);
147 private:
148  void Load(const QString &family,
149  const CC708CharacterAttribute &attr);
150  bool IsUnlocked(const QString &prefix, const QString &property) const
151  {
152  return m_changeMap[prefix].contains(property);
153  }
154  static void CreateProviderDefault(const QString &family,
155  const CC708CharacterAttribute &attr,
156  MythUIType *parent,
157  bool isComplement,
158  MythFontProperties **font,
159  MythUIShape **bg);
160  static void Complement(MythFontProperties *font, MythUIShape *bg);
161  static QSet<QString> Diff(const QString &family,
162  const CC708CharacterAttribute &attr,
163  MythFontProperties *font1,
164  MythFontProperties *font2,
165  MythUIShape *bg1,
166  MythUIShape *bg2);
167 
168  QHash<QString, MythFontProperties *> m_fontMap;
169  QHash<QString, MythUIShape *> m_shapeMap;
170  QHash<QString, QSet<QString> > m_changeMap;
171  // The following m_*Map fields are the original values from the
172  // m_fontMap, which are preserved because the text font zoom
173  // factor affects the actual values stored in the font.
174  QHash<QString, int> m_pixelSizeMap;
175  QHash<QString, int> m_outlineSizeMap;
176  QHash<QString, QPoint> m_shadowOffsetMap;
177  QVector<MythUIType *> m_cleanup;
178 };
179 
180 static const QString kSubProvider("provider");
181 static const QString kSubFileName ("osd_subtitle.xml");
182 static const QString kSubWindowName("osd_subtitle");
183 static const QString kSubFamily608 ("608");
184 static const QString kSubFamily708 ("708");
185 static const QString kSubFamilyText ("text");
186 static const QString kSubFamilyAV ("AV");
187 static const QString kSubFamilyTeletext("teletext");
188 
189 static const QString kSubAttrItalics ("italics");
190 static const QString kSubAttrBold ("bold");
191 static const QString kSubAttrUnderline("underline");
192 static const QString kSubAttrPixelsize("pixelsize");
193 static const QString kSubAttrColor ("color");
194 static const QString kSubAttrBGfill ("bgfill");
195 static const QString kSubAttrShadow ("shadow"); // unused
196 static const QString kSubAttrShadowoffset("shadowoffset");
197 static const QString kSubAttrShadowcolor ("shadowcolor");
198 static const QString kSubAttrShadowalpha ("shadowalpha");
199 static const QString kSubAttrOutline ("outline"); // unused
200 static const QString kSubAttrOutlinecolor("outlinecolor");
201 static const QString kSubAttrOutlinesize ("outlinesize");
202 static const QString kSubAttrOutlinealpha("outlinealpha");
203 
204 static QString srtColorString(const QColor& color)
205 {
206  return QString("#%1%2%3")
207  .arg(color.red(), 2, 16, QLatin1Char('0'))
208  .arg(color.green(), 2, 16, QLatin1Char('0'))
209  .arg(color.blue(), 2, 16, QLatin1Char('0'));
210 }
211 
213 {
214  QString result;
215  result = QString("face=%1 pixelsize=%2 color=%3 "
216  "italics=%4 weight=%5 underline=%6")
217  .arg(f->GetFace()->family())
218  .arg(f->GetFace()->pixelSize())
219  .arg(srtColorString(f->color()))
220  .arg(f->GetFace()->italic())
221  .arg(f->GetFace()->weight())
222  .arg(f->GetFace()->underline());
223  QPoint offset;
224  QColor color;
225  int alpha = 0;
226  int size = 0;
227  f->GetShadow(offset, color, alpha);
228  result += QString(" shadow=%1 shadowoffset=%2 "
229  "shadowcolor=%3 shadowalpha=%4")
230  .arg(f->hasShadow())
231  .arg(QString("(%1,%2)").arg(offset.x()).arg(offset.y()))
232  .arg(srtColorString(color))
233  .arg(alpha);
234  f->GetOutline(color, size, alpha);
235  result += QString(" outline=%1 outlinecolor=%2 "
236  "outlinesize=%3 outlinealpha=%4")
237  .arg(f->hasOutline())
238  .arg(srtColorString(color))
239  .arg(size)
240  .arg(alpha);
241  return result;
242 }
243 
245 SubtitleFormat::GetFont(const QString &family,
246  const CC708CharacterAttribute &attr,
247  int pixelSize, int zoom, int stretch)
248 {
249  int origPixelSize = pixelSize;
250  float scale = zoom / 100.0;
251  if ((attr.m_penSize & 0x3) == k708AttrSizeSmall)
252  scale = scale * 32 / 42;
253  else if ((attr.m_penSize & 0x3) == k708AttrSizeLarge)
254  scale = scale * 42 / 32;
255 
256  QString prefix = MakePrefix(family, attr);
257  if (!m_fontMap.contains(prefix))
258  Load(family, attr);
260 
261  // Apply the scaling factor to pixelSize even if the theme
262  // explicitly sets pixelSize.
264  pixelSize = m_pixelSizeMap[prefix];
265  pixelSize *= scale;
266  result->GetFace()->setPixelSize(pixelSize);
267 
268  result->GetFace()->setStretch(stretch);
270  result->GetFace()->setItalic(attr.m_italics);
272  result->GetFace()->setUnderline(attr.m_underline);
274  result->GetFace()->setBold(attr.m_boldface);
276  result->SetColor(attr.GetFGColor());
277 
278  MythPoint offset;
279  QColor color;
280  int alpha = 0;
281  bool shadow = result->hasShadow();
282  result->GetShadow(offset, color, alpha);
284  color = attr.GetEdgeColor();
286  alpha = attr.GetFGAlpha();
288  {
289  int off = lroundf(scale * pixelSize / 20);
290  offset = QPoint(off, off);
292  {
293  shadow = true;
294  offset.setX(-off);
295  }
296  else if (attr.m_edgeType == k708AttrEdgeRightDropShadow)
297  shadow = true;
298  else
299  shadow = false;
300  }
301  else
302  {
303  offset = m_shadowOffsetMap[prefix];
304  offset.NormPoint();
305  offset.setX(lroundf(offset.x() * scale));
306  offset.setY(lroundf(offset.y() * scale));
307  }
308  result->SetShadow(shadow, offset, color, alpha);
309 
310  int off = 0;
311  bool outline = result->hasOutline();
312  result->GetOutline(color, off, alpha);
314  color = attr.GetEdgeColor();
316  alpha = attr.GetFGAlpha();
318  {
319  if (attr.m_edgeType == k708AttrEdgeUniform ||
320  attr.m_edgeType == k708AttrEdgeRaised ||
322  {
323  outline = true;
324  off = lroundf(scale * pixelSize / 20);
325  }
326  else
327  outline = false;
328  }
329  else
330  {
331  off = m_outlineSizeMap[prefix];
332  MythPoint point(off, off);
333  point.NormPoint();
334  off = lroundf(point.x() * scale);
335  }
336  result->SetOutline(outline, color, off, alpha);
337 
338  LOG(VB_VBI, LOG_DEBUG,
339  QString("GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
340  "new pixelSize=%4 zoom=%5) = %6")
341  .arg(family).arg(prefix).arg(origPixelSize).arg(pixelSize)
342  .arg(zoom).arg(fontToString(result)));
343  return result;
344 }
345 
347 {
348  // NOLINTNEXTLINE(modernize-loop-convert)
349  for (int i = 0; i < m_cleanup.size(); ++i)
350  {
351  m_cleanup[i]->DeleteAllChildren();
352  m_cleanup[i]->deleteLater();
353  m_cleanup[i] = nullptr; // just to be safe
354  }
355 }
356 
357 QString SubtitleFormat::MakePrefix(const QString &family,
358  const CC708CharacterAttribute &attr)
359 {
360  if (family == kSubFamily708)
361  return family + "_" + QString::number(attr.m_fontTag & 0x7);
362  return family;
363 }
364 
365 void SubtitleFormat::CreateProviderDefault(const QString &family,
366  const CC708CharacterAttribute &attr,
367  MythUIType *parent,
368  bool isComplement,
369  MythFontProperties **returnFont,
370  MythUIShape **returnBg)
371 {
372  auto *font = new MythFontProperties();
373  auto *bg = new MythUIShape(parent, kSubProvider);
374  if (family == kSubFamily608)
375  {
376  font->GetFace()->setFamily("FreeMono");
377  QBrush brush(Qt::black);
378  bg->SetFillBrush(brush);
379  bg->SetLinePen(QPen(brush, 0));
380  }
381  else if (family == kSubFamily708)
382  {
383  static const char *s_cc708Fonts[] = {
384  "FreeMono", // default
385  "FreeMono", // mono serif
386  "Droid Serif", // prop serif
387  "Droid Sans Mono", // mono sans
388  "Droid Sans", // prop sans
389  "Purisa", // casual
390  "TeX Gyre Chorus", // cursive
391  "Droid Serif" // small caps, QFont::SmallCaps will be applied
392  };
393  font->GetFace()->setFamily(s_cc708Fonts[attr.m_fontTag & 0x7]);
394  }
395  else if (family == kSubFamilyText)
396  {
397  font->GetFace()->setFamily("Droid Sans");
398  QBrush brush(Qt::black);
399  bg->SetFillBrush(brush);
400  bg->SetLinePen(QPen(brush, 0));
401  }
402  else if (family == kSubFamilyTeletext)
403  {
404  font->GetFace()->setFamily("FreeMono");
405  // Set up a background with default alpha=100% so that the theme can
406  // override the background alpha.
407  QBrush brush(Qt::black);
408  bg->SetFillBrush(brush);
409  bg->SetLinePen(QPen(brush, 0));
410  }
411  font->GetFace()->setPixelSize(10);
412 
413  if (isComplement)
414  Complement(font, bg);
415  parent->AddFont(kSubProvider, font);
416 
417  *returnFont = font;
418  *returnBg = bg;
419 }
420 
421 static QColor differentColor(const QColor &color)
422 {
423  return color == Qt::white ? Qt::black : Qt::white;
424 }
425 
426 // Change everything (with respect to the == operator) that the
427 // provider might define or override.
429 {
430  QPoint offset;
431  QColor color;
432  int alpha = 0;
433  int size = 0;
434  QFont *face = font->GetFace();
435  face->setItalic(!face->italic());
436  face->setPixelSize(face->pixelSize() + 1);
437  face->setUnderline(!face->underline());
438  face->setWeight((face->weight() + 1) % 32);
439  font->SetColor(differentColor(font->color()));
440 
441  font->GetShadow(offset, color, alpha);
442  offset.setX(offset.x() + 1);
443  font->SetShadow(!font->hasShadow(), offset, differentColor(color),
444  255 - alpha);
445 
446  font->GetOutline(color, size, alpha);
447  font->SetOutline(!font->hasOutline(), differentColor(color),
448  size + 1, 255 - alpha);
449 
450  bg->SetFillBrush(bg->m_fillBrush == Qt::NoBrush ?
451  Qt::SolidPattern : Qt::NoBrush);
452 }
453 
454 void SubtitleFormat::Load(const QString &family,
455  const CC708CharacterAttribute &attr)
456 {
457  // Widgets for the actual values
458  auto *baseParent = new MythUIType(nullptr, "base");
459  m_cleanup += baseParent;
460  MythFontProperties *providerBaseFont = nullptr;
461  MythUIShape *providerBaseShape = nullptr;
462  CreateProviderDefault(family, attr, baseParent, false,
463  &providerBaseFont, &providerBaseShape);
464 
465  // Widgets for the "negative" values
466  auto *negParent = new MythUIType(nullptr, "base");
467  m_cleanup += negParent;
468  MythFontProperties *negFont = nullptr;
469  MythUIShape *negBG = nullptr;
470  CreateProviderDefault(family, attr, negParent, true, &negFont, &negBG);
471 
472  bool posResult =
474  baseParent);
475  bool negResult =
477  negParent);
478  if (!posResult || !negResult)
479  LOG(VB_VBI, LOG_INFO,
480  QString("Couldn't load theme file %1").arg(kSubFileName));
481  QString prefix = MakePrefix(family, attr);
482  MythFontProperties *resultFont = baseParent->GetFont(prefix);
483  if (!resultFont)
484  resultFont = providerBaseFont;
485  auto *resultBG = dynamic_cast<MythUIShape *>(baseParent->GetChild(prefix));
486 
487  // The providerBaseShape object is not leaked here. It is added
488  // to a list of children in its base class constructor.
489  if (!resultBG)
490  resultBG = providerBaseShape;
491  MythFontProperties *testFont = negParent->GetFont(prefix);
492  if (!testFont)
493  testFont = negFont;
494  auto *testBG = dynamic_cast<MythUIShape *>(negParent->GetChild(prefix));
495  if (!testBG)
496  testBG = negBG;
497  if (family == kSubFamily708 &&
498  (attr.m_fontTag & 0x7) == k708AttrFontSmallCaps)
499  resultFont->GetFace()->setCapitalization(QFont::SmallCaps);
500  m_fontMap[prefix] = resultFont;
501  m_shapeMap[prefix] = resultBG;
502  LOG(VB_VBI, LOG_DEBUG,
503  QString("providerBaseFont = %1").arg(fontToString(providerBaseFont)));
504  LOG(VB_VBI, LOG_DEBUG,
505  QString("negFont = %1").arg(fontToString(negFont)));
506  LOG(VB_VBI, LOG_DEBUG,
507  QString("resultFont = %1").arg(fontToString(resultFont)));
508  LOG(VB_VBI, LOG_DEBUG,
509  QString("testFont = %1").arg(fontToString(testFont)));
510  m_changeMap[prefix] = Diff(family, attr, resultFont, testFont,
511  resultBG, testBG);
512  QPoint offset;
513  QColor color;
514  int alpha = 0;
515  int size = 0;
516  resultFont->GetShadow(offset, color, alpha);
517  resultFont->GetOutline(color, size, alpha);
518  m_outlineSizeMap[prefix] = size;
519  m_shadowOffsetMap[prefix] = offset;
520  m_pixelSizeMap[prefix] = resultFont->GetFace()->pixelSize();
521 
522  delete negFont;
523 }
524 
525 QSet<QString> SubtitleFormat::Diff(const QString &family,
526  const CC708CharacterAttribute &attr,
527  MythFontProperties *font1,
528  MythFontProperties *font2,
529  MythUIShape *bg1,
530  MythUIShape *bg2)
531 {
532  bool is708 = (family == kSubFamily708);
533  QSet<QString> result;
534  QFont *face1 = font1->GetFace();
535  QFont *face2 = font2->GetFace();
536  if (face1->italic() != face2->italic())
537  result << kSubAttrItalics;
538  if (face1->weight() != face2->weight())
539  result << kSubAttrBold;
540  if (face1->underline() != face2->underline())
541  result << kSubAttrUnderline;
542  if (face1->pixelSize() != face2->pixelSize())
543  result << kSubAttrPixelsize;
544  if (font1->color() != font2->color())
545  result << kSubAttrColor;
546  if (is708 && font1->hasShadow() != font2->hasShadow())
547  {
548  result << kSubAttrShadow;
549  QPoint offset1;
550  QPoint offset2;
551  QColor color1;
552  QColor color2;
553  int alpha1 = 0;
554  int alpha2 = 0;
555  font1->GetShadow(offset1, color1, alpha1);
556  font2->GetShadow(offset2, color2, alpha2);
557  if (offset1 != offset2)
558  result << kSubAttrShadowoffset;
559  if (color1 != color2)
560  result << kSubAttrShadowcolor;
561  if (alpha1 != alpha2)
562  result << kSubAttrShadowalpha;
563  }
564  if (is708 && font1->hasOutline() != font2->hasOutline())
565  {
566  result << kSubAttrOutline;
567  QColor color1;
568  QColor color2;
569  int size1 = 0;
570  int size2 = 0;
571  int alpha1 = 0;
572  int alpha2 = 0;
573  font1->GetOutline(color1, size1, alpha1);
574  font2->GetOutline(color2, size2, alpha2);
575  if (color1 != color2)
576  result << kSubAttrOutlinecolor;
577  if (size1 != size2)
578  result << kSubAttrOutlinesize;
579  if (alpha1 != alpha2)
580  result << kSubAttrOutlinealpha;
581  }
582  if (bg1->m_fillBrush != bg2->m_fillBrush)
583  result << kSubAttrBGfill;
584 
585  QString values = "";
586  for (auto i = result.constBegin(); i != result.constEnd(); ++i)
587  values += " " + (*i);
588  LOG(VB_VBI, LOG_INFO,
589  QString("Subtitle family %1 allows provider to change:%2")
590  .arg(MakePrefix(family, attr)).arg(values));
591 
592  return result;
593 }
594 
595 SubShape *
596 SubtitleFormat::GetBackground(MythUIType *parent, const QString &name,
597  const QString &family,
598  const CC708CharacterAttribute &attr,
599  const MythRect &area,
600  int whichImageCache,
601  int start,
602  int duration)
603 {
604  QString prefix = MakePrefix(family, attr);
605  if (!m_shapeMap.contains(prefix))
606  Load(family, attr);
607  if (m_shapeMap[prefix]->GetAlpha() == 0)
608  return nullptr;
609  auto *result = new SubShape(parent, name, area, whichImageCache,
610  start + duration);
611  result->CopyFrom(m_shapeMap[prefix]);
612  if (family == kSubFamily708)
613  {
615  {
616  QBrush brush(attr.GetBGColor());
617  result->SetFillBrush(brush);
618  result->SetLinePen(QPen(brush, 0));
619  }
620  }
621  else if (family == kSubFamilyTeletext)
622  {
623  // add code here when teletextscreen.cpp is refactored
624  }
625  LOG(VB_VBI, LOG_DEBUG,
626  QString("GetBackground(prefix=%1) = "
627  "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
628  .arg(prefix).arg(result->m_type).arg(result->GetAlpha())
629  .arg(result->m_fillBrush.style())
630  .arg(srtColorString(result->m_fillBrush.color())));
631  return result;
632 }
633 
634 int SubtitleFormat::GetBackgroundAlpha(const QString &family)
635 {
636  // This is a "temporary" hack for allowing teletextscreen.cpp to get the
637  // background alpha value from osd_subtitle.xml.
638  CC708CharacterAttribute attr(false, false, false, Qt::white);
639  SubShape *bgShape = GetBackground(nullptr, "dummyName", family, attr,
640  MythRect(), -1, 0, -1);
641  return bgShape->m_fillBrush.color().alpha();
642 }
643 
645 
646 QSize FormattedTextChunk::CalcSize(float layoutSpacing) const
647 {
648  return m_parent->CalcTextSize(m_text, m_format, layoutSpacing);
649 }
650 
651 void FormattedTextChunk::CalcPadding(bool isFirst, bool isLast,
652  int &left, int &right) const
653 {
654  m_parent->CalcPadding(m_format, isFirst, isLast, left, right);
655 }
656 
658 {
659  LOG(VB_VBI, LOG_INFO,
660  QString("Attempting to split chunk '%1'").arg(m_text));
661  int lastSpace = m_text.lastIndexOf(' ', -2); // -2 to ignore trailing space
662  if (lastSpace < 0)
663  {
664  LOG(VB_VBI, LOG_INFO,
665  QString("Failed to split chunk '%1'").arg(m_text));
666  return false;
667  }
668  newChunk.m_parent = m_parent;
669  newChunk.m_format = m_format;
670  newChunk.m_text = m_text.mid(lastSpace + 1).trimmed() + ' ';
671  m_text = m_text.left(lastSpace).trimmed();
672  LOG(VB_VBI, LOG_INFO,
673  QString("Split chunk into '%1' + '%2'").arg(m_text).arg(newChunk.m_text));
674  return true;
675 }
676 
678 {
679  QString str("");
680  str += QString("fg=%1.%2 ")
682  .arg(m_format.GetFGAlpha());
683  str += QString("bg=%1.%2 ")
685  .arg(m_format.GetBGAlpha());
686  str += QString("edge=%1.%2 ")
688  .arg(m_format.m_edgeType);
689  str += QString("off=%1 pensize=%2 ")
690  .arg(m_format.m_offset)
691  .arg(m_format.m_penSize);
692  str += QString("it=%1 ul=%2 bf=%3 ")
693  .arg(m_format.m_italics)
694  .arg(m_format.m_underline)
695  .arg(m_format.m_boldface);
696  str += QString("font=%1 ").arg(m_format.m_fontTag);
697  str += QString(" text='%1'").arg(m_text);
698  return str;
699 }
700 
701 bool FormattedTextChunk::PreRender(bool isFirst, bool isLast,
702  int &x, int y, int height)
703 {
705  if (!m_textFont)
706  return false;
707  QFontMetrics font(*(m_textFont->GetFace()));
708  // If the chunk starts with whitespace, the leading whitespace
709  // ultimately gets lost due to the text.trimmed() operation in the
710  // MythUISimpleText constructor. To compensate, we manually
711  // indent the chunk accordingly.
712  int count = 0;
713  while (count < m_text.length() && m_text.at(count) == ' ')
714  {
715  ++count;
716  }
717 #if QT_VERSION < QT_VERSION_CHECK(5,11,0)
718  int x_adjust = count * font.width(" ");
719 #else
720  int x_adjust = count * font.horizontalAdvance(" ");
721 #endif
722  int leftPadding = 0;
723  int rightPadding = 0;
724  CalcPadding(isFirst, isLast, leftPadding, rightPadding);
725  // Account for extra padding before the first chunk.
726  if (isFirst)
727  x += leftPadding;
728  QSize chunk_sz = CalcSize();
729  QRect bgrect(x - leftPadding, y,
730  chunk_sz.width() + leftPadding + rightPadding,
731  height);
732  // Don't draw a background behind leading spaces.
733  if (isFirst)
734  bgrect.setLeft(bgrect.left() + x_adjust);
735  m_bgShapeName = QString("subbg%1x%2@%3,%4")
736  .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
737  m_bgShapeRect = bgrect;
738  // Shift to the right to account for leading spaces that
739  // are removed by the MythUISimpleText constructor. Also
740  // add in padding at the end to avoid clipping.
741  m_textName = QString("subtxt%1x%2@%3,%4")
742  .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
743  m_textRect = QRect(x + x_adjust, y,
744  chunk_sz.width() - x_adjust + rightPadding, height);
745 
746  x += chunk_sz.width();
747  return true;
748 }
749 
750 QSize FormattedTextLine::CalcSize(float layoutSpacing) const
751 {
752  int height = 0;
753  int width = 0;
754  int leftPadding = 0;
755  int rightPadding = 0;
756  QList<FormattedTextChunk>::const_iterator it;
757  bool isFirst = true;
758  for (it = chunks.constBegin(); it != chunks.constEnd(); ++it)
759  {
760  bool isLast = (it + 1 == chunks.constEnd());
761  QSize tmp = (*it).CalcSize(layoutSpacing);
762  height = max(height, tmp.height());
763  width += tmp.width();
764  (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
765  if (it == chunks.constBegin())
766  width += leftPadding;
767  isFirst = false;
768  }
769  return {width + rightPadding, height};
770 }
771 
772 // Normal font height is designed to be 1/20 of safe area height, with
773 // extra blank space between lines to make 17 lines within the safe
774 // area.
775 static const float LINE_SPACING = (20.0 / 17.0);
776 static const float PAD_WIDTH = 0.5;
777 static const float PAD_HEIGHT = 0.04;
778 
779 // Resolves any TBD x_indent and y_indent values in FormattedTextLine
780 // objects. Calculates m_bounds. Prunes most leading and all
781 // trailing whitespace from each line so that displaying with a black
782 // background doesn't look clumsy.
784 {
785  // Calculate dimensions of bounding rectangle
786  int anchor_width = 0;
787  int anchor_height = 0;
788  foreach (auto & line, m_lines)
789  {
790  QSize sz = line.CalcSize(LINE_SPACING);
791  anchor_width = max(anchor_width, sz.width());
792  anchor_height += sz.height();
793  }
794 
795  // Adjust the anchor point according to actual width and height
796  int anchor_x = m_xAnchor;
797  int anchor_y = m_yAnchor;
798  if (m_xAnchorPoint == 1)
799  anchor_x -= anchor_width / 2;
800  else if (m_xAnchorPoint == 2)
801  anchor_x -= anchor_width;
802  if (m_yAnchorPoint == 1)
803  anchor_y -= anchor_height / 2;
804  else if (m_yAnchorPoint == 2)
805  anchor_y -= anchor_height;
806 
807  // Shift the anchor point back into the safe area if necessary/possible.
808  anchor_y = max(0, min(anchor_y, m_safeArea.height() - anchor_height));
809  anchor_x = max(0, min(anchor_x, m_safeArea.width() - anchor_width));
810 
811  m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
812 
813  // Fill in missing coordinates
814  int y = anchor_y;
815  // NOLINTNEXTLINE(modernize-loop-convert)
816  for (int i = 0; i < m_lines.size(); i++)
817  {
818  if (m_lines[i].m_xIndent < 0)
819  m_lines[i].m_xIndent = anchor_x;
820  if (m_lines[i].m_yIndent < 0)
821  m_lines[i].m_yIndent = y;
822  y += m_lines[i].CalcSize(LINE_SPACING).height();
823  // Prune leading all-whitespace chunks.
824  while (!m_lines[i].chunks.isEmpty() &&
825  m_lines[i].chunks.first().m_text.trimmed().isEmpty())
826  {
827  m_lines[i].m_xIndent +=
828  m_lines[i].chunks.first().CalcSize().width();
829  m_lines[i].chunks.removeFirst();
830  }
831  // Prune trailing all-whitespace chunks.
832  while (!m_lines[i].chunks.isEmpty() &&
833  m_lines[i].chunks.last().m_text.trimmed().isEmpty())
834  {
835  m_lines[i].chunks.removeLast();
836  }
837  // Trim trailing whitespace from last chunk. (Trimming
838  // leading whitespace from all chunks is handled in the Draw()
839  // routine.)
840  if (!m_lines[i].chunks.isEmpty())
841  {
842  QString *str = &m_lines[i].chunks.last().m_text;
843  int idx = str->length() - 1;
844  while (idx >= 0 && str->at(idx) == ' ')
845  --idx;
846  str->truncate(idx + 1);
847  }
848  }
849 }
850 
852 {
853  // NOLINTNEXTLINE(modernize-loop-convert)
854  for (int i = 0; i < m_lines.size(); i++)
855  {
856  int x = m_lines[i].m_xIndent;
857  int y = m_lines[i].m_yIndent;
858  int height = m_lines[i].CalcSize().height();
859  QList<FormattedTextChunk>::iterator chunk;
860  bool isFirst = true;
861  for (chunk = m_lines[i].chunks.begin();
862  chunk != m_lines[i].chunks.end();
863  ++chunk)
864  {
865  bool isLast = (chunk + 1 == m_lines[i].chunks.end());
866  (*chunk).PreRender(isFirst, isLast, x, y, height);
867  isFirst = false;
868  }
869  }
870 }
871 
872 // Returns true if anything new was drawn, false if not. The caller
873 // should call SubtitleScreen::OptimiseDisplayedArea() if true is
874 // returned.
876 {
877  QList<MythUIType *> textList;
878  QList<MythUIType *> shapeList;
879  for (auto & line : m_lines)
880  {
881  QList<FormattedTextChunk>::const_iterator chunk;
882  for (chunk = line.chunks.constBegin();
883  chunk != line.chunks.constEnd();
884  ++chunk)
885  {
886  MythFontProperties *mythfont =
887  m_subScreen->GetFont((*chunk).m_format);
888  if (!mythfont)
889  continue;
890  // Note: nullptr is passed as the parent argument to the
891  // MythUI constructors so that we can control the drawing
892  // order of the children. In particular, background
893  // shapes should be added/drawn first, and text drawn on
894  // top.
895  if ((*chunk).m_textRect.width() > 0) {
896  auto *text = new SubSimpleText((*chunk).m_text, *mythfont,
897  (*chunk).m_textRect,
898  Qt::AlignLeft|Qt::AlignTop,
899  /*m_subScreen*/nullptr,
900  (*chunk).m_textName, CacheNum(),
901  m_start + m_duration);
902  textList += text;
903  }
904  if ((*chunk).m_bgShapeRect.width() > 0) {
906  GetBackground(/*m_subScreen*/nullptr,
907  (*chunk).m_bgShapeName,
908  m_base, (*chunk).m_format,
909  MythRect((*chunk).m_bgShapeRect), CacheNum(),
911  if (bgshape)
912  {
913  bgshape->SetArea(MythRect((*chunk).m_bgShapeRect));
914  shapeList += bgshape;
915  }
916  }
917  }
918  }
919  while (!shapeList.isEmpty())
920  m_subScreen->AddChild(shapeList.takeFirst());
921  while (!textList.isEmpty())
922  m_subScreen->AddChild(textList.takeFirst());
923 }
924 
925 QStringList FormattedTextSubtitle::ToSRT(void) const
926 {
927  QStringList result;
928  foreach (const auto & ftl, m_lines)
929  {
930  QString line;
931  if (ftl.m_origX > 0)
932  line.fill(' ', ftl.m_origX);
933  QList<FormattedTextChunk>::const_iterator chunk;
934  for (chunk = ftl.chunks.constBegin();
935  chunk != ftl.chunks.constEnd();
936  ++chunk)
937  {
938  const QString &text = (*chunk).m_text;
939  const CC708CharacterAttribute &attr = (*chunk).m_format;
940  bool isBlank = !attr.m_underline && text.trimmed().isEmpty();
941  if (!isBlank)
942  {
943  if (attr.m_boldface)
944  line += "<b>";
945  if (attr.m_italics)
946  line += "<i>";
947  if (attr.m_underline)
948  line += "<u>";
949  if (attr.GetFGColor() != Qt::white)
950  line += QString("<font color=\"%1\">")
951  .arg(srtColorString(attr.GetFGColor()));
952  }
953  line += text;
954  if (!isBlank)
955  {
956  if (attr.GetFGColor() != Qt::white)
957  line += QString("</font>");
958  if (attr.m_underline)
959  line += "</u>";
960  if (attr.m_italics)
961  line += "</i>";
962  if (attr.m_boldface)
963  line += "</b>";
964  }
965  }
966  if (!line.trimmed().isEmpty())
967  result += line;
968  }
969  return result;
970 }
971 
972 void FormattedTextSubtitleSRT::Init(const QStringList &subs)
973 {
974  // Does a simplistic parsing of HTML tags from the strings.
975  // Nesting is not implemented. Stray whitespace may cause
976  // legitimate tags to be ignored. All other HTML tags are
977  // stripped and ignored.
978  //
979  // <i> - enable italics
980  // </i> - disable italics
981  // <b> - enable boldface
982  // </b> - disable boldface
983  // <u> - enable underline
984  // </u> - disable underline
985  // <font color="#xxyyzz"> - change font color
986  // </font> - reset font color to white
987 
988  int pixelSize = m_safeArea.height() / 20;
989  if (m_subScreen)
990  m_subScreen->SetFontSize(pixelSize);
991  m_xAnchorPoint = 1; // center
992  m_yAnchorPoint = 2; // bottom
993  m_xAnchor = m_safeArea.width() / 2;
994  m_yAnchor = m_safeArea.height();
995 
996  bool isItalic = false;
997  bool isBold = false;
998  bool isUnderline = false;
999  QColor color(Qt::white);
1000  QRegExp htmlTag("</?.+>");
1001  QString htmlPrefix("<font color=\"");
1002  QString htmlSuffix("\">");
1003  htmlTag.setMinimal(true);
1004  foreach (QString subtitle, subs)
1005  {
1006  FormattedTextLine line;
1007  QString text(subtitle);
1008  while (!text.isEmpty())
1009  {
1010  int pos = text.indexOf(htmlTag);
1011  if (pos != 0) // don't add a zero-length string
1012  {
1013  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1014  color);
1015  FormattedTextChunk chunk(text.left(pos), attr, m_subScreen);
1016  line.chunks += chunk;
1017  text = (pos < 0 ? "" : text.mid(pos));
1018  LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
1019  .arg(chunk.ToLogString()));
1020  }
1021  if (pos >= 0)
1022  {
1023  int htmlLen = htmlTag.matchedLength();
1024  QString html = text.left(htmlLen).toLower();
1025  text = text.mid(htmlLen);
1026  if (html == "<i>")
1027  isItalic = true;
1028  else if (html == "</i>")
1029  isItalic = false;
1030  else if (html.startsWith(htmlPrefix) &&
1031  html.endsWith(htmlSuffix))
1032  {
1033  int colorLen = html.length() -
1034  (htmlPrefix.length() + htmlSuffix.length());
1035  QString colorString(
1036  html.mid(htmlPrefix.length(), colorLen));
1037  QColor newColor(colorString);
1038  if (newColor.isValid())
1039  {
1040  color = newColor;
1041  }
1042  else
1043  {
1044  LOG(VB_VBI, LOG_INFO,
1045  QString("Ignoring invalid SRT color specification: "
1046  "'%1'").arg(colorString));
1047  }
1048  }
1049  else if (html == "</font>")
1050  color = Qt::white;
1051  else if (html == "<b>")
1052  isBold = true;
1053  else if (html == "</b>")
1054  isBold = false;
1055  else if (html == "<u>")
1056  isUnderline = true;
1057  else if (html == "</u>")
1058  isUnderline = false;
1059  else
1060  {
1061  LOG(VB_VBI, LOG_INFO,
1062  QString("Ignoring unknown SRT formatting: '%1'")
1063  .arg(html));
1064  }
1065 
1066  LOG(VB_VBI, LOG_INFO,
1067  QString("SRT formatting change '%1', "
1068  "new ital=%2 bold=%3 uline=%4 color=%5)")
1069  .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1070  .arg(srtColorString(color)));
1071  }
1072  }
1073  m_lines += line;
1074  }
1075 }
1076 
1078 {
1079  int maxWidth = m_safeArea.width();
1080  for (int i = 0; i < m_lines.size(); i++)
1081  {
1082  int width = m_lines[i].CalcSize().width();
1083  // Move entire chunks to the next line as necessary. Leave at
1084  // least one chunk on the current line.
1085  while (width > maxWidth && m_lines[i].chunks.size() > 1)
1086  {
1087  width -= m_lines[i].chunks.back().CalcSize().width();
1088  // Make sure there's a next line to wrap into.
1089  if (m_lines.size() == i + 1)
1090  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1091  m_lines[i].m_yIndent);
1092  m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
1093  LOG(VB_VBI, LOG_INFO,
1094  QString("Wrapping chunk to next line: '%1'")
1095  .arg(m_lines[i+1].chunks[0].m_text));
1096  }
1097  // Split the last chunk until width is small enough or until
1098  // no more splits are possible.
1099  bool isSplitPossible = true;
1100  while (width > maxWidth && isSplitPossible)
1101  {
1102  FormattedTextChunk newChunk;
1103  isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
1104  if (isSplitPossible)
1105  {
1106  // Make sure there's a next line to split into.
1107  if (m_lines.size() == i + 1)
1108  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1109  m_lines[i].m_yIndent);
1110  m_lines[i+1].chunks.prepend(newChunk);
1111  width = m_lines[i].CalcSize().width();
1112  }
1113  }
1114  }
1115 }
1116 
1122 static QString extract_cc608(QString &text, int &color,
1123  bool &isItalic, bool &isUnderline)
1124 {
1125  QString result;
1126  QString orig(text);
1127 
1128  // Handle an initial control sequence.
1129  if (text.length() >= 1 && text[0] >= 0x7000)
1130  {
1131  int op = text[0].unicode() - 0x7000;
1132  isUnderline = ((op & 0x1) != 0);
1133  switch (op & ~1)
1134  {
1135  case 0x0e:
1136  // color unchanged
1137  isItalic = true;
1138  break;
1139  case 0x1e:
1140  color = op >> 1;
1141  isItalic = true;
1142  break;
1143  case 0x20:
1144  // color unchanged
1145  // italics unchanged
1146  break;
1147  default:
1148  color = (op & 0xf) >> 1;
1149  isItalic = false;
1150  break;
1151  }
1152  text = text.mid(1);
1153  }
1154 
1155  // Copy the string into the result, up to the next control
1156  // character.
1157  int nextControl = text.indexOf(QRegExp("[\\x7000-\\x7fff]"));
1158  if (nextControl < 0)
1159  {
1160  result = text;
1161  text.clear();
1162  }
1163  else
1164  {
1165  result = text.left(nextControl);
1166  text = text.mid(nextControl);
1167  }
1168 
1169  return result;
1170 }
1171 
1172 // Adjusts the Y coordinates to avoid overlap, which could happen as a
1173 // result of a large text zoom factor. Then, if the total height
1174 // exceeds the safe area, compresses each piece of vertical blank
1175 // space proportionally to make it fit.
1177 {
1178  int totalHeight = 0;
1179  int totalSpace = 0;
1180  int firstY = 0;
1181  int prevY = 0;
1182  QVector<int> heights(m_lines.size());
1183  QVector<int> spaceBefore(m_lines.size());
1184  // Calculate totalHeight and totalSpace
1185  for (int i = 0; i < m_lines.size(); i++)
1186  {
1187  m_lines[i].m_yIndent = max(m_lines[i].m_yIndent, prevY); // avoid overlap
1188  int y = m_lines[i].m_yIndent;
1189  if (i == 0)
1190  firstY = prevY = y;
1191  int height = m_lines[i].CalcSize().height();
1192  heights[i] = height;
1193  spaceBefore[i] = y - prevY;
1194  totalSpace += (y - prevY);
1195  prevY = y + height;
1196  totalHeight += height;
1197  }
1198  int safeHeight = m_safeArea.height();
1199  int overage = min(totalHeight - safeHeight, totalSpace);
1200 
1201  // Recalculate Y coordinates, applying the shrink factor to space
1202  // between each line.
1203  if (overage > 0 && totalSpace > 0)
1204  {
1205  float shrink = (totalSpace - overage) / (float)totalSpace;
1206  prevY = firstY;
1207  for (int i = 0; i < m_lines.size(); i++)
1208  {
1209  m_lines[i].m_yIndent = prevY + spaceBefore[i] * shrink;
1210  prevY = m_lines[i].m_yIndent + heights[i];
1211  }
1212  }
1213 
1214  // Shift Y coordinates back up into the safe area.
1215  int shift = min(firstY, max(0, prevY - safeHeight));
1216  for (int i = 0; i < m_lines.size(); i++)
1217  m_lines[i].m_yIndent -= shift;
1218 
1220 }
1221 
1222 void FormattedTextSubtitle608::Init(const vector<CC608Text*> &buffers)
1223 {
1224  static const QColor kClr[8] =
1225  {
1226  Qt::white, Qt::green, Qt::blue, Qt::cyan,
1227  Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1228  };
1229 
1230  if (buffers.empty())
1231  return;
1232  auto i = buffers.cbegin();
1233  int xscale = 36;
1234  int yscale = 17;
1235  int pixelSize = m_safeArea.height() / (yscale * LINE_SPACING);
1236  int fontwidth = 0;
1237  int xmid = 0;
1238  int zoom = 100;
1239  if (m_subScreen)
1240  {
1241  zoom = m_subScreen->GetZoom();
1242  m_subScreen->SetFontSize(pixelSize);
1243  CC708CharacterAttribute def_attr(false, false, false, kClr[0]);
1244  QFont *font = m_subScreen->GetFont(def_attr)->GetFace();
1245  QFontMetrics fm(*font);
1246  fontwidth = fm.averageCharWidth();
1247  xmid = m_safeArea.width() / 2;
1248  // Disable centering for zoom factor >= 100%
1249  if (zoom >= 100)
1250  xscale = m_safeArea.width() / fontwidth;
1251  }
1252 
1253  for (; i != buffers.cend(); ++i)
1254  {
1255  CC608Text *cc = (*i);
1256  int color = 0;
1257  bool isItalic = false;
1258  bool isUnderline = false;
1259  const bool isBold = false;
1260  QString text(cc->m_text);
1261 
1262  int orig_x = cc->m_x;
1263  // position as if we use a fixed size font
1264  // - font size already has zoom factor applied
1265 
1266  int x = 0;
1267  if (xmid)
1268  {
1269  // center horizontally
1270  x = xmid + (orig_x - xscale / 2) * fontwidth;
1271  }
1272  else
1273  {
1274  // fallback
1275  x = (orig_x + 3) * m_safeArea.width() / xscale;
1276  }
1277 
1278  int orig_y = cc->m_y;
1279  int y = 0;
1280  if (orig_y < yscale / 2)
1281  {
1282  // top half -- anchor up
1283  y = (orig_y * m_safeArea.height() * zoom / (yscale * 100));
1284  }
1285  else
1286  {
1287  // bottom half -- anchor down
1288  y = m_safeArea.height() -
1289  ((yscale - orig_y - 0.5) * m_safeArea.height() * zoom /
1290  (yscale * 100));
1291  }
1292 
1293  FormattedTextLine line(x, y, orig_x, orig_y);
1294  while (!text.isNull())
1295  {
1296  QString captionText =
1297  extract_cc608(text, color, isItalic, isUnderline);
1298  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1299  kClr[min(max(0, color), 7)]);
1300  FormattedTextChunk chunk(captionText, attr, m_subScreen);
1301  line.chunks += chunk;
1302  LOG(VB_VBI, LOG_INFO,
1303  QString("Adding cc608 chunk (%1,%2): %3")
1304  .arg(cc->m_x).arg(cc->m_y).arg(chunk.ToLogString()));
1305  }
1306  m_lines += line;
1307  }
1308 }
1309 
1311 {
1312  // Draw the window background after calculating bounding rectangle
1313  // in Layout()
1314  if (m_bgFillAlpha) // TODO border?
1315  {
1316  QBrush fill(m_bgFillColor, Qt::SolidPattern);
1317  auto *shape = new SubShape(m_subScreen, QString("cc708bg%1").arg(m_num),
1319  shape->SetFillBrush(fill);
1320  shape->SetArea(MythRect(m_bounds));
1321  }
1322  // The background is drawn first so that it appears behind
1323  // everything else.
1325 }
1326 
1328  const vector<CC708String*> &list,
1329  float aspect)
1330 {
1331  LOG(VB_VBI, LOG_DEBUG,LOC +
1332  QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1333  "relative %5")
1334  .arg(m_num).arg(win.m_anchor_point).arg(win.m_anchor_horizontal)
1335  .arg(win.m_anchor_vertical).arg(win.m_relative_pos));
1336  int pixelSize = m_safeArea.height() / 20;
1337  if (m_subScreen)
1338  m_subScreen->SetFontSize(pixelSize);
1339 
1340  float xrange = win.m_relative_pos ? 100.0F :
1341  (aspect > 1.4F) ? 210.0F : 160.0F;
1342  float yrange = win.m_relative_pos ? 100.0F : 75.0F;
1343  float xmult = (float)m_safeArea.width() / xrange;
1344  float ymult = (float)m_safeArea.height() / yrange;
1345  uint anchor_x = (uint)(xmult * (float)win.m_anchor_horizontal);
1346  uint anchor_y = (uint)(ymult * (float)win.m_anchor_vertical);
1347  m_xAnchorPoint = win.m_anchor_point % 3;
1348  m_yAnchorPoint = win.m_anchor_point / 3;
1349  m_xAnchor = anchor_x;
1350  m_yAnchor = anchor_y;
1351 
1352  for (auto str708 : list)
1353  {
1354  if (str708->m_y >= (uint)m_lines.size())
1355  m_lines.resize(str708->m_y + 1);
1356  FormattedTextChunk chunk(str708->m_str, str708->m_attr, m_subScreen);
1357  m_lines[str708->m_y].chunks += chunk;
1358  LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
1359  .arg(m_num).arg(str708->m_y).arg(chunk.ToLogString()));
1360  }
1361 }
1362 
1364 
1365 SubtitleScreen::SubtitleScreen(MythPlayer *player, const char * name,
1366  int fontStretch) :
1367  MythScreenType((MythScreenType*)nullptr, name),
1368  m_player(player),
1369  m_fontStretch(fontStretch),
1370  m_format(new SubtitleFormat)
1371 {
1372  m_removeHTML.setMinimal(true);
1373 }
1374 
1376 {
1378  delete m_format;
1379 #ifdef USING_LIBASS
1381 #endif
1382 }
1383 
1384 void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
1385 {
1386  m_subtitleType = type;
1387 
1388  if (forced_only)
1389  {
1391  SetVisible(true);
1392  SetArea(MythRect());
1393  return;
1394  }
1395 
1396  if (m_subreader)
1397  {
1401  }
1402  if (m_cc608reader)
1404  if (m_cc708reader)
1408  SetArea(MythRect());
1409  switch (m_subtitleType)
1410  {
1411  case kDisplayTextSubtitle:
1414  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1415  break;
1416  case kDisplayCC608:
1418  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1419  break;
1420  case kDisplayCC708:
1422  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1423  break;
1424  case kDisplayAVSubtitle:
1426  m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
1427  break;
1428  }
1431 }
1432 
1434 {
1436  return;
1438  SetVisible(false);
1439  SetArea(MythRect());
1440 }
1441 
1443 {
1446 #ifdef USING_LIBASS
1447  if (m_assTrack)
1448  ass_flush_events(m_assTrack);
1449 #endif
1450 }
1451 
1453 {
1459  m_cc608reader->ClearBuffers(true, true);
1462 }
1463 
1465 {
1468 }
1469 
1470 void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
1471 {
1472  if (!dvdButton || !m_player)
1473  return;
1474 
1476  if (!vo)
1477  return;
1478 
1481 
1482  float tmp = 0.0;
1483  QRect dummy;
1484  vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1485 
1486  AVSubtitleRect *hl_button = dvdButton->rects[0];
1487  uint h = hl_button->h;
1488  uint w = hl_button->w;
1489  QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1490  QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1491  auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1492 
1493  QVector<uint32_t> bg_palette(4);
1494  for (int i = 0; i < 4; i++)
1495  bg_palette[i] = bgpalette[i];
1496  bg_image.setColorTable(bg_palette);
1497 
1498  // copy button region of background image
1499  const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1500  QImage fg_image = bg_image.copy(fg_rect);
1501  QVector<uint32_t> fg_palette(4);
1502  auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1503  if (fgpalette)
1504  {
1505  for (int i = 0; i < 4; i++)
1506  fg_palette[i] = fgpalette[i];
1507  fg_image.setColorTable(fg_palette);
1508  }
1509 
1510  bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1511  fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1512 
1513  // set pixel of highlight area to highlight color
1514  for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1515  {
1516  if ((x < 0) || (x > hl_button->w))
1517  continue;
1518  for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1519  {
1520  if ((y < 0) || (y > hl_button->h))
1521  continue;
1522  bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1523  }
1524  }
1525 
1526  AddScaledImage(bg_image, rect);
1527 }
1528 
1529 void SubtitleScreen::SetZoom(int percent)
1530 {
1531  m_textFontZoom = percent;
1532  if (m_family == kSubFamilyAV)
1533  gCoreContext->SaveSetting("OSDAVSubZoom", percent);
1534  else
1535  gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
1536 }
1537 
1539 {
1540  return m_textFontZoom;
1541 }
1542 
1544 {
1545  m_textFontDelayMs = ms;
1546 }
1547 
1549 {
1550  return m_textFontDelayMs;
1551 }
1552 
1554 {
1555  QList<MythUIType *> list = m_ChildrenList;
1556  QList<MythUIType *>::iterator it;
1557  for (it = list.begin(); it != list.end(); ++it)
1558  {
1559  MythUIType *child = *it;
1560  auto *wrapper = dynamic_cast<SubWrapper *>(child);
1561  if (wrapper)
1562  {
1563  int whichImageCache = wrapper->GetWhichImageCache();
1564  if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1565  {
1567  DeleteChild(child);
1568  }
1569  }
1570  }
1571 }
1572 
1573 // SetElementAdded() should be called after a new element is added to
1574 // the subtitle screen.
1576 {
1577  m_refreshModified = true;
1578 }
1579 
1580 // SetElementResized() should be called after a subtitle screen
1581 // element's size is changed.
1583 {
1584  SetElementAdded();
1585 }
1586 
1587 // SetElementAdded() should be called *before* an element is deleted
1588 // from the subtitle screen.
1590 {
1591  if (!m_refreshDeleted)
1592  SetRedraw();
1593  m_refreshDeleted = true;
1594 }
1595 
1596 // The QFontMetrics class does not account for the MythFontProperties
1597 // shadow and offset properties. This method calculates the
1598 // additional padding to the right and below that is needed for proper
1599 // bounding box computation.
1601 {
1602  QColor color;
1603  int alpha = 0;
1604  int outlineSize = 0;
1605  int shadowWidth = 0;
1606  int shadowHeight = 0;
1607  if (mythfont->hasOutline())
1608  {
1609  mythfont->GetOutline(color, outlineSize, alpha);
1610  MythPoint outline(outlineSize, outlineSize);
1611  outline.NormPoint();
1612  outlineSize = outline.x();
1613  }
1614  if (mythfont->hasShadow())
1615  {
1616  MythPoint shadowOffset;
1617  mythfont->GetShadow(shadowOffset, color, alpha);
1618  shadowOffset.NormPoint();
1619  shadowWidth = abs(shadowOffset.x());
1620  shadowHeight = abs(shadowOffset.y());
1621  // Shadow and outline overlap, so don't just add them.
1622  shadowWidth = max(shadowWidth, outlineSize);
1623  shadowHeight = max(shadowHeight, outlineSize);
1624  }
1625  return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1626 }
1627 
1628 QSize SubtitleScreen::CalcTextSize(const QString &text,
1629  const CC708CharacterAttribute &format,
1630  float layoutSpacing) const
1631 {
1632  MythFontProperties *mythfont = GetFont(format);
1633  QFont *font = mythfont->GetFace();
1634  QFontMetrics fm(*font);
1635 #if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1636  int width = fm.width(text);
1637 #else
1638  int width = fm.horizontalAdvance(text);
1639 #endif
1640  int height = fm.height() * (1 + PAD_HEIGHT);
1641  if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1642  height = max(height, (int)(font->pixelSize() * layoutSpacing));
1643  height += CalcShadowOffsetPadding(mythfont).height();
1644  return {width, height};
1645 }
1646 
1647 // Padding calculation is different depending on whether the padding
1648 // is on the left side or the right side of the text. Padding on the
1649 // right needs to add the shadow and offset padding.
1651  bool isFirst, bool isLast,
1652  int &left, int &right) const
1653 {
1654  MythFontProperties *mythfont = GetFont(format);
1655  QFont *font = mythfont->GetFace();
1656  QFontMetrics fm(*font);
1657  int basicPadding = fm.maxWidth() * PAD_WIDTH;
1658  left = isFirst ? basicPadding : 0;
1659  right = CalcShadowOffsetPadding(mythfont).width() +
1660  (isLast ? basicPadding : 0);
1661 }
1662 
1665 {
1666  return m_format->GetFont(m_family, attr, m_fontSize,
1668 }
1669 
1671 {
1672  SubtitleFormat format;
1673  CC708CharacterAttribute attr(false, false, false, Qt::white);
1674  MythFontProperties *mythfont =
1675  format.GetFont(kSubFamilyTeletext, attr,
1676  /*pixelsize*/20, /*zoom*/100, /*stretch*/100);
1677  return mythfont->face().family();
1678 }
1679 
1681 {
1682  SubtitleFormat format;
1683  return format.GetBackgroundAlpha(kSubFamilyTeletext);
1684 }
1685 
1687 {
1688  if (!m_player)
1689  return false;
1690 
1694  if (!m_subreader)
1695  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
1696  if (!m_cc608reader)
1697  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
1698  if (!m_cc708reader)
1699  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
1700 
1701  return true;
1702 }
1703 
1705 {
1706  QList<MythUIType *>::iterator it;
1707  QList<MythUIType *>::iterator itNext;
1708 
1709  MythVideoOutput *videoOut = m_player->GetVideoOutput();
1710  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1711  long long now = currentFrame ? currentFrame->timecode : LLONG_MAX;
1712  bool needRescale = (m_textFontZoom != m_textFontZoomPrev);
1713 
1714  for (it = m_ChildrenList.begin(); it != m_ChildrenList.end(); it = itNext)
1715  {
1716  itNext = it + 1;
1717  MythUIType *child = *it;
1718  auto *wrapper = dynamic_cast<SubWrapper *>(child);
1719  if (!wrapper)
1720  continue;
1721 
1722  // Expire the subtitle object if needed.
1723  long long expireTime = wrapper->GetExpireTime();
1724  if (expireTime > 0 && expireTime < now)
1725  {
1726  DeleteChild(child);
1728  continue;
1729  }
1730 
1731  // Rescale the AV subtitle image if the zoom changed.
1732  if (expireTime > 0 && needRescale)
1733  {
1734  auto *image = dynamic_cast<SubImage *>(child);
1735  if (image)
1736  {
1737  double factor = m_textFontZoom / (double)m_textFontZoomPrev;
1738  QSize size = image->GetImage()->size();
1739  size *= factor;
1740  image->GetImage()->Resize(size);
1742  }
1743  }
1744  }
1745 
1746  DisplayAVSubtitles(); // allow forced subtitles to work
1747 
1750  else if (kDisplayCC608 == m_subtitleType)
1752  else if (kDisplayCC708 == m_subtitleType)
1756  while (!m_qInited.isEmpty())
1757  {
1758  FormattedTextSubtitle *fsub = m_qInited.takeFirst();
1759  fsub->WrapLongLines();
1760  fsub->Layout();
1761  fsub->PreRender();
1762  fsub->Draw();
1763  delete fsub;
1764  SetElementAdded();
1765  }
1766 
1772 }
1773 
1775 {
1776  m_refreshModified = false;
1777  m_refreshDeleted = false;
1778 }
1779 
1781 {
1782  if (!m_refreshModified)
1783  return;
1784 
1785  QRegion visible;
1786  QListIterator<MythUIType *> i(m_ChildrenList);
1787  while (i.hasNext())
1788  {
1789  MythUIType *img = i.next();
1790  auto *wrapper = dynamic_cast<SubWrapper *>(img);
1791  if (wrapper && img->IsVisible())
1792  visible = visible.united(wrapper->GetOrigArea());
1793  }
1794 
1795  if (visible.isEmpty())
1796  return;
1797 
1798  QRect bounding = visible.boundingRect();
1799  bounding = bounding.translated(m_safeArea.topLeft());
1800  bounding = m_safeArea.intersected(bounding);
1801  int left = m_safeArea.left() - bounding.left();
1802  int top = m_safeArea.top() - bounding.top();
1803  SetArea(MythRect(bounding));
1804 
1805  i.toFront();
1806  while (i.hasNext())
1807  {
1808  MythUIType *img = i.next();
1809  auto *wrapper = dynamic_cast<SubWrapper *>(img);
1810  if (wrapper && img->IsVisible())
1811  img->SetArea(wrapper->GetOrigArea().translated(left, top));
1812  }
1813 }
1814 
1816 {
1817  if (!m_player || !m_subreader)
1818  return;
1819 
1821  QMutexLocker lock(&(subs->m_lock));
1822  if (subs->m_buffers.empty() && (kDisplayAVSubtitle != m_subtitleType))
1823  return;
1824 
1825  MythVideoOutput *videoOut = m_player->GetVideoOutput();
1826  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1827 
1828  if (!currentFrame || !videoOut)
1829  return;
1830 
1831  float tmp = 0.0;
1832  QRect dummy;
1833  videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1834 
1835  while (!subs->m_buffers.empty())
1836  {
1837  AVSubtitle subtitle = subs->m_buffers.front();
1838  if (subtitle.start_display_time > currentFrame->timecode)
1839  break;
1840 
1841  long long displayfor = subtitle.end_display_time -
1842  subtitle.start_display_time;
1843  if (displayfor == 0)
1844  displayfor = 60000;
1845  displayfor = (displayfor < 50) ? 50 : displayfor;
1846  long long late = currentFrame->timecode -
1847  subtitle.start_display_time;
1848 
1850  subs->m_buffers.pop_front();
1851  for (std::size_t i = 0; i < subtitle.num_rects; ++i)
1852  {
1853  AVSubtitleRect* rect = subtitle.rects[i];
1854 
1855  bool displaysub = true;
1856  if (!subs->m_buffers.empty() &&
1857  subs->m_buffers.front().end_display_time <
1858  currentFrame->timecode)
1859  {
1860  displaysub = false;
1861  }
1862 
1863  if (displaysub && rect->type == SUBTITLE_BITMAP)
1864  {
1865  QRect display(rect->display_x, rect->display_y,
1866  rect->display_w, rect->display_h);
1867 
1868  // XSUB and some DVD/DVB subs are based on the original video
1869  // size before the video was converted. We need to guess the
1870  // original size and allow for the difference
1871 
1872  int right = rect->x + rect->w;
1873  int bottom = rect->y + rect->h;
1874  if (subs->m_fixPosition || (currentFrame->height < bottom) ||
1875  (currentFrame->width < right) ||
1876  !display.width() || !display.height())
1877  {
1878  int sd_height = 576;
1879  if ((m_player->GetFrameRate() > 26.0F ||
1880  m_player->GetFrameRate() < 24.0F) && bottom <= 480)
1881  sd_height = 480;
1882  int height = ((currentFrame->height <= sd_height) &&
1883  (bottom <= sd_height)) ? sd_height :
1884  ((currentFrame->height <= 720) && bottom <= 720)
1885  ? 720 : 1080;
1886  int width = ((currentFrame->width <= 720) &&
1887  (right <= 720)) ? 720 :
1888  ((currentFrame->width <= 1280) &&
1889  (right <= 1280)) ? 1280 : 1920;
1890  display = QRect(0, 0, width, height);
1891  }
1892 
1893  // split into upper/lower to allow zooming
1894  QRect bbox;
1895  int uh = display.height() / 2 - rect->y;
1896  long long displayuntil = currentFrame->timecode + displayfor;
1897  if (uh > 0)
1898  {
1899  bbox = QRect(0, 0, rect->w, uh);
1900  uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
1901  subtitle.forced,
1902  QString("avsub%1t").arg(i),
1903  displayuntil, late);
1904  }
1905  else
1906  uh = 0;
1907  int lh = rect->h - uh;
1908  if (lh > 0)
1909  {
1910  bbox = QRect(0, uh, rect->w, lh);
1911  DisplayScaledAVSubtitles(rect, bbox, false, display,
1912  subtitle.forced,
1913  QString("avsub%1b").arg(i),
1914  displayuntil, late);
1915  }
1916  }
1917 #ifdef USING_LIBASS
1918  else if (displaysub && rect->type == SUBTITLE_ASS)
1919  {
1921  AddAssEvent(rect->ass);
1922  }
1923 #endif
1924  }
1926  }
1927 #ifdef USING_LIBASS
1928  RenderAssTrack(currentFrame->timecode);
1929 #endif
1930 }
1931 
1932 int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
1933  QRect &bbox, bool top,
1934  QRect &display, int forced,
1935  const QString& imagename,
1936  long long displayuntil,
1937  long long late)
1938 {
1939  // split image vertically if it spans middle of display
1940  // - split point is empty line nearest the middle
1941  // crop image to reduce scaling time
1942  bool prev_empty = false;
1943 
1944  // initialize to opposite edges
1945  int xmin = bbox.right();
1946  int xmax = bbox.left();
1947  int ymin = bbox.bottom();
1948  int ymax = bbox.top();
1949  int ylast = bbox.top();
1950  int ysplit = bbox.bottom();
1951 
1952  // find bounds of active image
1953  for (int y = bbox.top(); y <= bbox.bottom(); ++y)
1954  {
1955  if (y >= rect->h)
1956  {
1957  // end of image
1958  if (!prev_empty)
1959  ylast = y;
1960  break;
1961  }
1962 
1963  bool empty = true;
1964  for (int x = bbox.left(); x <= bbox.right(); ++x)
1965  {
1966  const uint8_t color =
1967  rect->data[0][y * rect->linesize[0] + x];
1968  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
1969  if (pixel & 0xff000000)
1970  {
1971  empty = false;
1972  if (x < xmin)
1973  xmin = x;
1974  if (x > xmax)
1975  xmax = x;
1976  }
1977  }
1978 
1979  if (!empty)
1980  {
1981  if (y < ymin)
1982  ymin = y;
1983  if (y > ymax)
1984  ymax = y;
1985  }
1986  else if (!prev_empty)
1987  {
1988  // remember uppermost empty line
1989  ylast = y;
1990  }
1991  prev_empty = empty;
1992  }
1993 
1994  if (ymax <= ymin)
1995  return 0;
1996 
1997  if (top)
1998  {
1999  if (ylast < ymin)
2000  // no empty lines
2001  return 0;
2002 
2003  if (ymax == bbox.bottom())
2004  {
2005  ymax = ylast;
2006  ysplit = ylast;
2007  }
2008  }
2009 
2010  // set new bounds
2011  bbox.setLeft(xmin);
2012  bbox.setRight(xmax);
2013  bbox.setTop(ymin);
2014  bbox.setBottom(ymax);
2015 
2016  // copy active region
2017  // AVSubtitleRect's image data's not guaranteed to be 4 byte
2018  // aligned.
2019 
2020  QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2021 
2022  QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2023  for (int y = 0; y < bbox.height(); ++y)
2024  {
2025  int ysrc = y + bbox.top();
2026  for (int x = 0; x < bbox.width(); ++x)
2027  {
2028  int xsrc = x + bbox.left();
2029  const uint8_t color =
2030  rect->data[0][ysrc * rect->linesize[0] + xsrc];
2031  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
2032  qImage.setPixel(x, y, pixel);
2033  }
2034  }
2035 
2036  // translate to absolute coordinates
2037  bbox.translate(rect->x, rect->y);
2038 
2039  // scale and move according to zoom factor
2040  bbox.setWidth(bbox.width() * m_textFontZoom / 100);
2041  bbox.setHeight(bbox.height() * m_textFontZoom / 100);
2042 
2043  MythVideoOutput *videoOut = m_player->GetVideoOutput();
2044  QRect scaled = videoOut->GetImageRect(bbox, &display);
2045 
2046  if (scaled.size() != orig_rect.size())
2047  qImage = qImage.scaled(scaled.width(), scaled.height(),
2048  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2049 
2050  int hsize = m_safeArea.width();
2051  int vsize = m_safeArea.height();
2052 
2053  scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 +
2054  m_textFontZoom * scaled.left()) /
2055  100);
2056  if (top)
2057  {
2058  // anchor up
2059  scaled.moveTop(scaled.top() * m_textFontZoom / 100);
2060  }
2061  else
2062  {
2063  // anchor down
2064  scaled.moveTop(((100 - m_textFontZoom) * vsize +
2065  m_textFontZoom * scaled.top()) / 100);
2066  }
2067 
2068 
2069  MythPainter *osd_painter = videoOut->GetOSDPainter();
2070  MythImage *image = nullptr;
2071  if (osd_painter)
2072  image = osd_painter->GetFormatImage();
2073 
2074  SubImage *uiimage = nullptr;
2075  if (image)
2076  {
2077  image->Assign(qImage);
2078  uiimage = new SubImage(this, imagename,
2079  MythRect(scaled), displayuntil);
2080  if (uiimage)
2081  {
2082  uiimage->SetImage(image);
2083  uiimage->SetArea(MythRect(scaled));
2084  SetElementAdded();
2085  }
2086  image->DecrRef();
2087  image = nullptr;
2088  }
2089  if (uiimage)
2090  {
2091  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2092  QString("Display %1AV sub until %2ms")
2093  .arg(forced ? "FORCED " : "")
2094  .arg(displayuntil));
2095  if (late > 50)
2096  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2097  QString("AV Sub was %1ms late").arg(late));
2098  }
2099 
2100  return (ysplit + 1);
2101 }
2102 
2104 {
2105  if (!m_player || !m_subreader)
2106  return;
2107 
2108  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2109  changed |= (m_textFontDelayMs != m_textFontDelayMsPrev);
2111  if (!vo)
2112  return;
2113  m_safeArea = vo->GetSafeRect();
2114 
2115  VideoFrame *currentFrame = vo->GetLastShownFrame();
2116  if (!currentFrame)
2117  return;
2118 
2120  subs->Lock();
2121  uint64_t playPos = 0;
2122  int playPosAdj = m_textFontDelayMs;
2123  if (subs->IsFrameBasedTiming())
2124  {
2125  // frame based subtitles get out of synch after running mythcommflag
2126  // for the file, i.e., the following number is wrong and does not
2127  // match the subtitle frame numbers:
2128  playPos = currentFrame->frameNumber;
2129  playPosAdj /= m_player->GetFrameRate();
2130  }
2131  else
2132  {
2133  // Use timecodes for time based SRT subtitles. Feeding this into
2134  // NormalizeVideoTimecode() should adjust for non-zero start times
2135  // and wraps. For MPEG, wraps will occur just once every 26.5 hours
2136  // and other formats less frequently so this should be sufficient.
2137  // Note: timecodes should now always be valid even in the case
2138  // when a frame doesn't have a valid timestamp. If an exception is
2139  // found where this is not true then we need to use the frameNumber
2140  // when timecode is not defined by uncommenting the following lines.
2141  //if (currentFrame->timecode == 0)
2142  // playPos = (uint64_t)
2143  // ((currentFrame->frameNumber / video_frame_rate) * 1000);
2144  //else
2145  playPos = m_player->GetDecoder()->NormalizeVideoTimecode(currentFrame->timecode);
2146  }
2147  playPos -= playPosAdj;
2148  if (playPos != 0)
2149  changed |= subs->HasSubtitleChanged(playPos);
2150  if (!changed)
2151  {
2152  subs->Unlock();
2153  return;
2154  }
2155 
2158 
2159  if (playPos == 0)
2160  {
2161  subs->Unlock();
2162  return;
2163  }
2164 
2165  QStringList rawsubs = subs->GetSubtitles(playPos);
2166  if (rawsubs.empty())
2167  {
2168  subs->Unlock();
2169  return;
2170  }
2171 
2172  subs->Unlock();
2173  DrawTextSubtitles(rawsubs, 0, 0);
2174 }
2175 
2177 {
2178  if (!m_player || !m_subreader)
2179  return;
2180 
2181  uint64_t duration = 0;
2182  QStringList subs = m_subreader->GetRawTextSubtitles(duration);
2183  if (subs.empty())
2184  return;
2185 
2187  if (!vo)
2188  return;
2189 
2190  VideoFrame *currentFrame = vo->GetLastShownFrame();
2191  if (!currentFrame)
2192  return;
2193 
2194  m_safeArea = vo->GetSafeRect();
2195 
2196  // delete old subs that may still be on screen
2199  DrawTextSubtitles(subs, currentFrame->timecode, duration);
2200 }
2201 
2202 void SubtitleScreen::DrawTextSubtitles(const QStringList &subs,
2203  uint64_t start, uint64_t duration)
2204 {
2205  auto *fsub = new FormattedTextSubtitleSRT(m_family, m_safeArea, start,
2206  duration, this, subs);
2207  m_qInited.append(fsub);
2208 }
2209 
2211 {
2212  if (!m_cc608reader)
2213  return;
2214 
2215  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2216 
2217  if (!m_player || !m_player->GetVideoOutput())
2218  return;
2220 
2221  CC608Buffer* textlist = m_cc608reader->GetOutputText(changed);
2222  if (!changed)
2223  return;
2224 
2227 
2228  if (!textlist)
2229  return;
2230 
2231  QMutexLocker locker(&textlist->m_lock);
2232 
2233  if (textlist->m_buffers.empty())
2234  return;
2235 
2236  auto *fsub = new FormattedTextSubtitle608(textlist->m_buffers, m_family,
2237  m_safeArea, this/*, m_textFontZoom*/);
2238  m_qInited.append(fsub);
2239 }
2240 
2242 {
2244  return;
2245 
2246  CC708Service *cc708service = m_cc708reader->GetCurrentService();
2247  float video_aspect = m_player->GetVideoAspect();
2248  QRect oldsafe = m_safeArea;
2250  bool changed = (oldsafe != m_safeArea || m_textFontZoom != m_textFontZoomPrev);
2251  if (changed)
2252  {
2253  for (auto & window : cc708service->m_windows)
2254  window.SetChanged();
2255  }
2256 
2257  uint64_t clearMask = 0;
2258  QList<FormattedTextSubtitle *> addList;
2259  for (int i = 0; i < k708MaxWindows; i++)
2260  {
2261  CC708Window &win = cc708service->m_windows[i];
2262  if (win.GetExists() && win.GetVisible() && !win.GetChanged())
2263  continue;
2264  if (!win.GetChanged())
2265  continue;
2266 
2267  clearMask |= (1UL << i);
2268  win.ResetChanged();
2269  if (!win.GetExists() || !win.GetVisible())
2270  continue;
2271 
2272  QMutexLocker locker(&win.m_lock);
2273  vector<CC708String*> list = win.GetStrings();
2274  if (!list.empty())
2275  {
2276  auto *fsub = new FormattedTextSubtitle708(win, i, list, m_family,
2277  m_safeArea, this, video_aspect);
2278  addList.append(fsub);
2279  }
2281  }
2282  if (clearMask)
2283  {
2284  Clear708Cache(clearMask);
2285  }
2286  if (!addList.empty())
2287  m_qInited.append(addList);
2288 }
2289 
2290 void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
2291 {
2293  if (!vo)
2294  return;
2295 
2296  QRect scaled = vo->GetImageRect(pos);
2297  if (scaled.size() != pos.size())
2298  {
2299  img = img.scaled(scaled.width(), scaled.height(),
2300  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2301  }
2302 
2303  MythPainter *osd_painter = vo->GetOSDPainter();
2304  MythImage* image = nullptr;
2305  if (osd_painter)
2306  image = osd_painter->GetFormatImage();
2307 
2308  if (image)
2309  {
2310  image->Assign(img);
2311  MythUIImage *uiimage = new SubImage(this, "dvd_button", MythRect(scaled), 0);
2312  if (uiimage)
2313  {
2314  uiimage->SetImage(image);
2315  uiimage->SetArea(MythRect(scaled));
2316  SetElementAdded();
2317  }
2318  image->DecrRef();
2319  }
2320 }
2321 
2322 #ifdef USING_LIBASS
2323 static void myth_libass_log(int level, const char *fmt, va_list vl, void */*ctx*/)
2324 {
2325  uint64_t verbose_mask = VB_GENERAL;
2326  LogLevel_t verbose_level = LOG_INFO;
2327 
2328  switch (level)
2329  {
2330  case 0: //MSGL_FATAL
2331  verbose_level = LOG_EMERG;
2332  break;
2333  case 1: //MSGL_ERR
2334  verbose_level = LOG_ERR;
2335  break;
2336  case 2: //MSGL_WARN
2337  verbose_level = LOG_WARNING;
2338  break;
2339  case 4: //MSGL_INFO
2340  verbose_level = LOG_INFO;
2341  break;
2342  case 6: //MSGL_V
2343  case 7: //MSGL_DBG2
2344  verbose_level = LOG_DEBUG;
2345  break;
2346  default:
2347  return;
2348  }
2349 
2350  if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
2351  return;
2352 
2353  static QMutex s_stringLock;
2354  s_stringLock.lock();
2355 
2356  char str[1024];
2357  int bytes = vsnprintf(str, sizeof str, fmt, vl);
2358  // check for truncated messages and fix them
2359  int truncated = bytes - ((sizeof str)-1);
2360  if (truncated > 0)
2361  {
2362  LOG(VB_GENERAL, LOG_ERR,
2363  QString("libASS log output truncated %1 of %2 bytes written")
2364  .arg(truncated).arg(bytes));
2365  }
2366 
2367  LOG(verbose_mask, verbose_level, QString("libass: %1").arg(str));
2368  s_stringLock.unlock();
2369 }
2370 
2372 {
2373  if (m_assLibrary && m_assRenderer)
2374  return true;
2375 
2376  if (!m_assLibrary)
2377  {
2378  m_assLibrary = ass_library_init();
2379  if (!m_assLibrary)
2380  return false;
2381 
2382  ass_set_message_cb(m_assLibrary, myth_libass_log, nullptr);
2383  ass_set_extract_fonts(m_assLibrary, static_cast<int>(true));
2384  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
2385  }
2386 
2387  LoadAssFonts();
2388 
2389  if (!m_assRenderer)
2390  {
2391  m_assRenderer = ass_renderer_init(m_assLibrary);
2392  if (!m_assRenderer)
2393  return false;
2394 
2395 #ifdef Q_OS_ANDROID
2396  // fontconfig doesn't yet work for us on Android. For the
2397  // time being, more explicitly set a font we know should
2398  // exist. This was adapted from VLC master as of 2019-01-21.
2399  const char *psz_font = "/system/fonts/DroidSans.ttf";
2400  const char *psz_font_family = "Droid Sans";
2401 #else
2402  const char *psz_font = nullptr;
2403  const char *psz_font_family = "sans-serif";
2404 #endif
2405  ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1, nullptr, 1);
2406  ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2407  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
2408  }
2409 
2410  return true;
2411 }
2412 
2414 {
2415  if (!m_assLibrary || !m_player)
2416  return;
2417 
2419  if (m_assFontCount == count)
2420  return;
2421 
2422  ass_clear_fonts(m_assLibrary);
2423  m_assFontCount = 0;
2424 
2425  // TODO these need checking and/or reinitialising after a stream change
2426  for (uint i = 0; i < count; ++i)
2427  {
2428  QByteArray filename;
2429  QByteArray font;
2431  ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
2432  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
2433  .arg(filename.constData()));
2434  m_assFontCount++;
2435  }
2436 }
2437 
2439 {
2440  CleanupAssTrack();
2441 
2442  if (m_assRenderer)
2443  ass_renderer_done(m_assRenderer);
2444  m_assRenderer = nullptr;
2445 
2446  if (m_assLibrary)
2447  {
2448  ass_clear_fonts(m_assLibrary);
2449  m_assFontCount = 0;
2450  ass_library_done(m_assLibrary);
2451  }
2452  m_assLibrary = nullptr;
2453 }
2454 
2456 {
2457  if (!InitialiseAssLibrary() || !m_player)
2458  return;
2459 
2460  if (tracknum == m_assTrackNum && m_assTrack)
2461  return;
2462 
2463  LoadAssFonts();
2464  CleanupAssTrack();
2465  m_assTrack = ass_new_track(m_assLibrary);
2466  m_assTrackNum = tracknum;
2467 
2468  QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
2469  if (!header.isNull())
2470  ass_process_codec_private(m_assTrack, header.data(), header.size());
2471 
2474 }
2475 
2477 {
2478  if (m_assTrack)
2479  ass_free_track(m_assTrack);
2480  m_assTrack = nullptr;
2481 }
2482 
2484 {
2485  if (m_assTrack && event)
2486  ass_process_data(m_assTrack, event, strlen(event));
2487 }
2488 
2490 {
2491  ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
2492  ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2493  ass_set_use_margins(m_assRenderer, static_cast<int>(true));
2494  ass_set_font_scale(m_assRenderer, 1.0);
2495 }
2496 
2497 void SubtitleScreen::RenderAssTrack(uint64_t timecode)
2498 {
2499  if (!m_player || !m_assRenderer || !m_assTrack)
2500  return;
2501 
2503  if (!vo )
2504  return;
2505 
2506  QRect oldscreen = m_safeArea;
2507  m_safeArea = vo->GetMHEGBounds();
2508  if (oldscreen != m_safeArea)
2510 
2511  int changed = 0;
2512  ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack,
2513  timecode, &changed);
2514  if (!changed)
2515  return;
2516 
2517  MythPainter *osd_painter = vo->GetOSDPainter();
2518  if (!osd_painter)
2519  return;
2520 
2521  int count = 0;
2524  while (images)
2525  {
2526  if (images->w == 0 || images->h == 0)
2527  {
2528  images = images->next;
2529  continue;
2530  }
2531 
2532  uint8_t alpha = images->color & 0xFF;
2533  uint8_t blue = images->color >> 8 & 0xFF;
2534  uint8_t green = images->color >> 16 & 0xFF;
2535  uint8_t red = images->color >> 24 & 0xFF;
2536 
2537  if (alpha == 255)
2538  {
2539  images = images->next;
2540  continue;
2541  }
2542 
2543  QSize img_size(images->w, images->h);
2544  QRect img_rect(images->dst_x,images->dst_y,
2545  images->w, images->h);
2546  QImage qImage(img_size, QImage::Format_ARGB32);
2547  qImage.fill(0x00000000);
2548 
2549  unsigned char *src = images->bitmap;
2550  for (int y = 0; y < images->h; ++y)
2551  {
2552  for (int x = 0; x < images->w; ++x)
2553  {
2554  uint8_t value = src[x];
2555  if (value)
2556  {
2557  uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
2558  (red << 16) | (green << 8) | blue;
2559  qImage.setPixel(x, y, pixel);
2560  }
2561  }
2562  src += images->stride;
2563  }
2564 
2565  MythImage* image = nullptr;
2566  SubImage *uiimage = nullptr;
2567 
2568  if (osd_painter)
2569  image = osd_painter->GetFormatImage();
2570 
2571  if (image)
2572  {
2573  image->Assign(qImage);
2574  QString name = QString("asssub%1").arg(count);
2575  uiimage = new SubImage(this, name, MythRect(img_rect), 0);
2576  if (uiimage)
2577  {
2578  uiimage->SetImage(image);
2579  uiimage->SetArea(MythRect(img_rect));
2580  SetElementAdded();
2581  }
2582  image->DecrRef();
2583  }
2584  images = images->next;
2585  count++;
2586  }
2587 }
2588 #endif // USING_LIBASS
virtual QByteArray GetSubHeader(uint) const
Definition: decoderbase.h:254
static const QString kSubAttrOutline("outline")
virtual CC708Reader * GetCC708Reader(uint=0)
Definition: mythplayer.h:325
CC708Service * GetCurrentService(void)
Definition: cc708reader.h:23
class SubtitleFormat * GetSubtitleFormat(void)
CC708Window m_windows[k708MaxWindows]
Definition: cc708window.h:312
static const QString kSubAttrPixelsize("pixelsize")
QVector< MythUIType * > m_cleanup
QColor color(void) const
float GetVideoAspect(void) const
Definition: mythplayer.h:210
void SetOutline(bool on, const QColor &color, int size, int alpha)
int DisplayScaledAVSubtitles(const AVSubtitleRect *rect, QRect &bbox, bool top, QRect &display, int forced, const QString &imagename, long long displayuntil, long long late)
void SetElementResized(void)
Definition: cc.h:13
QRect GetImageRect(const QRect &Rect, QRect *DisplayRect=nullptr)
translates caption/dvd button rectangle into 'screen' space
void SetZoom(int percent)
CC608Reader * m_cc608reader
static void Complement(MythFontProperties *font, MythUIShape *bg)
int GetTrack(uint type) const
Definition: decoderbase.h:243
SubImage(MythUIType *parent, const QString &name, const MythRect &area, long long expireTime)
const uint k708AttrEdgeDepressed
Definition: cc708window.cpp:96
void SetImage(MythImage *img)
Should not be used unless absolutely necessary since it bypasses the image caching and threaded loade...
#define LOC
void ClearBuffers(void)
Definition: cc708reader.cpp:40
bool AddFont(const QString &text, MythFontProperties *fontProp)
virtual MythPainter * GetOSDPainter(void)
Definition: mythvideoout.h:124
bool Split(FormattedTextChunk &newChunk)
QHash< QString, int > m_pixelSizeMap
long long timecode
Definition: mythframe.h:149
QRect GetMHEGBounds(void)
void setX(const QString &sX)
Definition: mythrect.cpp:458
const SubtitleScreen * m_parent
static const float LINE_SPACING
static const QString kSubAttrShadowcolor("shadowcolor")
void SaveSetting(const QString &key, int newValue)
void InitialiseAssTrack(int tracknum)
static const QString kSubAttrOutlinesize("outlinesize")
void SetRedraw(void)
Definition: mythuitype.cpp:293
void DeleteChild(const QString &name)
Delete a named child of this UIType.
Definition: mythuitype.cpp:145
static const QString kSubAttrOutlinecolor("outlinecolor")
int DecrRef(void) override
Decrements reference count and deletes on 0.
Definition: mythimage.cpp:55
Image widget, displays a single image or multiple images in sequence.
Definition: mythuiimage.h:97
bool IsVisible(bool recurse=false) const
Definition: mythuitype.cpp:882
void Load(const QString &family, const CC708CharacterAttribute &attr)
void CleanupAssLibrary(void)
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:56
void SetFillBrush(QBrush fill)
Definition: mythuishape.cpp:42
QColor GetEdgeColor(void) const
Definition: cc708window.h:121
QHash< QString, int > m_outlineSizeMap
static QString extract_cc608(QString &text, int &color, bool &isItalic, bool &isUnderline)
Extract everything from the text buffer up until the next format control character.
virtual int CacheNum(void) const
QHash< QString, QPoint > m_shadowOffsetMap
MythRect GetOrigArea(void) const
void EnableSubtitles(int type, bool forced_only=false)
void GetShadow(QPoint &offset, QColor &color, int &alpha) const
const uint k708AttrEdgeUniform
Definition: cc708window.cpp:97
QList< FormattedTextSubtitle * > m_qInited
static void myth_libass_log(int level, const char *fmt, va_list vl, void *)
TextSubtitles * GetTextSubtitles(void)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
void SetColor(const QColor &color)
MythFontProperties * GetFont(const CC708CharacterAttribute &attr) const
void DeleteAllChildren(void)
Delete all child widgets.
Definition: mythuitype.cpp:202
static const QString kSubProvider("provider")
static const QString kSubAttrItalics("italics")
void Clear708Cache(uint64_t mask)
static const QString kSubAttrBGfill("bgfill")
long long GetExpireTime(void) const
void CalcPadding(bool isFirst, bool isLast, int &left, int &right) const
void DisplayCC608Subtitles(void)
SubShape * GetBackground(MythUIType *parent, const QString &name, const QString &family, const CC708CharacterAttribute &attr, const MythRect &area, int whichImageCache, int start, int duration)
void DrawTextSubtitles(const QStringList &subs, uint64_t start, uint64_t duration)
static const QString kSubFamily708("708")
void CleanupAssTrack(void)
void OptimiseDisplayedArea(void)
static guint32 * tmp
Definition: goom_core.c:35
QRect GetSafeRect(void)
Returns a QRect describing an area of the screen on which it is 'safe' to render the On Screen Displa...
The base class on which all widgets and screens are based.
Definition: mythuitype.h:63
unsigned int prevY
Definition: filters.c:109
void SetEnabled(bool enable)
Definition: cc708reader.h:24
int GetBackgroundAlpha(const QString &family)
void SetFontSize(int pixelSize)
QHash< QString, QSet< QString > > m_changeMap
MythFontProperties * m_textFont
virtual void GetAttachmentData(uint, QByteArray &, QByteArray &)
Definition: decoderbase.h:255
Simplified text widget, displays a text string.
static const QString kSubFamilyTeletext("teletext")
void ClearRawTextSubtitles(void)
const int m_swWhichImageCache
MythImage * GetImage(void)
int GetWhichImageCache(void) const
SubSimpleText(const QString &text, const MythFontProperties &font, const QRect &rect, Qt::Alignment align, MythUIType *parent, const QString &name, int whichImageCache, long long expireTime)
SubtitleScreen(MythPlayer *player, const char *name, int fontStretch)
uint m_anchor_vertical
Definition: cc708window.h:244
const long long m_swExpireTime
void EnableAVSubtitles(bool enable)
static const QString kSubAttrColor("color")
int GetZoom(void) const
void ClearNonDisplayedSubtitles(void)
void Init(const QStringList &subs)
virtual void SetVisible(bool visible)
void DisableForcedSubtitles(void)
void EnableTextSubtitles(bool enable)
QFont face(void) const
const uint k708AttrEdgeLeftDropShadow
Definition: cc708window.cpp:98
MythVideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:273
void Init(const CC708Window &win, const vector< CC708String * > &list, float aspect)
void SetElementDeleted(void)
QSize CalcSize(float layoutSpacing=0.0F) const
bool Create(void) override
static const QString kSubFamilyAV("AV")
static const QString kSubAttrOutlinealpha("outlinealpha")
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
CC708Reader * m_cc708reader
uint m_relative_pos
Definition: cc708window.h:243
static int GetTeletextBackgroundAlpha(void)
bool GetVisible(void) const
Definition: cc708window.h:280
float GetFrameRate(void) const
Definition: mythplayer.h:211
static const QString kSubAttrShadow("shadow")
void RenderAssTrack(uint64_t timecode)
static const QString kSubAttrShadowoffset("shadowoffset")
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:440
uint GetFGAlpha(void) const
Definition: cc708window.h:123
static QSize CalcShadowOffsetPadding(MythFontProperties *mythfont)
QStringList GetRawTextSubtitles(uint64_t &duration)
virtual void WrapLongLines(void)
SubtitleFormat(void)=default
Wrapper around QRect allowing us to handle percentage and other relative values for areas in mythui.
Definition: mythrect.h:17
static QString GetTeletextFontName(void)
static const float PAD_HEIGHT
const MythRect m_swOrigArea
const uint k708AttrSizeLarge
Definition: cc708window.cpp:79
static const float PAD_WIDTH
SubtitleReader * m_subreader
QHash< QString, MythFontProperties * > m_fontMap
unsigned int uint
Definition: compat.h:140
void Init(const vector< CC608Text * > &buffers)
static const QString kSubAttrUnderline("underline")
QColor GetFGColor(void) const
Definition: cc708window.h:108
A widget for rendering primitive shapes and lines.
Definition: mythuishape.h:21
bool PreRender(bool isFirst, bool isLast, int &x, int y, int height)
static QColor differentColor(const QColor &color)
void WrapLongLines(void) override
virtual void Layout(void)
QHash< QString, MythUIShape * > m_shapeMap
static const QString kSubFileName("osd_subtitle.xml")
QBrush m_fillBrush
Definition: mythuishape.h:42
void SetElementAdded(void)
void CalcPadding(const CC708CharacterAttribute &format, bool isFirst, bool isLast, int &left, int &right) const
bool GetChanged(void) const
Definition: cc708window.h:281
static bool LoadWindowFromXML(const QString &xmlfile, const QString &windowname, MythUIType *parent)
void DisplayDVDButton(AVSubtitle *dvdButton, QRect &buttonPos)
const uint k708AttrEdgeRightDropShadow
Definition: cc708window.cpp:99
void DisplayCC708Subtitles(void)
long long frameNumber
Definition: mythframe.h:147
void GetOutline(QColor &color, int &size, int &alpha) const
~SubtitleScreen() override
const uint k708AttrEdgeRaised
Definition: cc708window.cpp:95
uint GetBGAlpha(void) const
Definition: cc708window.h:130
QSize CalcTextSize(const QString &text, const CC708CharacterAttribute &format, float layoutSpacing) const
void Pulse(void) override
Pulse is called 70 times a second to trigger a single frame of an animation.
QString ToLogString(void) const
static const QString kSubFamilyText("text")
void Layout(void) override
void SetDelay(int ms)
vector< CC608Text * > m_buffers
Definition: cc608reader.h:51
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:589
virtual uint GetTrackCount(uint type) const
Definition: decoderbase.h:237
int GetNumSetting(const QString &key, int defaultval=0)
void setY(const QString &sY)
Definition: mythrect.cpp:468
ASS_Track * m_assTrack
QSize CalcSize(float layoutSpacing=0.0F) const
const uint k708AttrSizeSmall
Definition: cc708window.cpp:77
bool hasOutline(void) const
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
QVector< FormattedTextLine > m_lines
SubShape(MythUIType *parent, const QString &name, const MythRect &area, int whichImageCache, long long expireTime)
static const QString kSubAttrShadowalpha("shadowalpha")
bool HasSubtitleChanged(uint64_t timecode) const
Returns true in case the subtitle to display has changed since the last GetSubtitles() call.
QMutex m_lock
Definition: cc708window.h:302
static QString fontToString(MythFontProperties *f)
void ClearDisplayedSubtitles(void)
void ResizeAssRenderer(void)
vector< CC708String * > GetStrings(void) const
static const QString kSubAttrBold("bold")
static guint32 * pixel
--------------------------------------------------—**
Definition: goom_core.c:33
static void DisposeStrings(vector< CC708String * > &strings)
QColor GetBGColor(void) const
Definition: cc708window.h:115
virtual void GetOSDBounds(QRect &Total, QRect &Visible, float &VisibleAspect, float &FontScaling, float ThemeAspect) const
static QSet< QString > Diff(const QString &family, const CC708CharacterAttribute &attr, MythFontProperties *font1, MythFontProperties *font2, MythUIShape *bg1, MythUIShape *bg2)
SubWrapper(const MythRect &rect, long long expireTime, int whichImageCache=-1)
int height
Definition: mythframe.h:142
MythDeque< AVSubtitle > m_buffers
CC708CharacterAttribute m_format
static void FreeAVSubtitle(AVSubtitle &sub)
QHash< int, MythImage * > m_Images
Definition: mythuiimage.h:170
QList< FormattedTextChunk > chunks
void ClearAVSubtitles(void)
QStringList GetSubtitles(uint64_t timecode)
Returns the subtitles to display at the given timecode.
const uint k708AttrFontSmallCaps
Definition: cc708window.cpp:92
ASS_Library * m_assLibrary
bool m_fixPosition
ASS_Renderer * m_assRenderer
void ClearAllSubtitles(void)
class SubtitleFormat * m_format
void SetShadow(bool on, const QPoint &offset, const QColor &color, int alpha)
void DisplayTextSubtitles(void)
static QString MakePrefix(const QString &family, const CC708CharacterAttribute &attr)
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:85
virtual VideoFrame * GetLastShownFrame(void)
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
static const QString kSubWindowName("osd_subtitle")
static QString srtColorString(const QColor &color)
void LoadAssFonts(void)
MythFontProperties * GetFont(const QString &family, const CC708CharacterAttribute &attr, int pixelSize, int zoom, int stretch)
QStringList ToSRT(void) const
AVSubtitles * GetAVSubtitles(void)
virtual CC608Reader * GetCC608Reader(uint=0)
Definition: mythplayer.h:326
bool IsUnlocked(const QString &prefix, const QString &property) const
Screen in which all other widgets are contained and rendered.
void Assign(const QImage &img)
Definition: mythimage.cpp:80
SubtitleScreen * m_subScreen
void EnableRawTextSubtitles(bool enable)
int GetDelay(void) const
bool hasShadow(void) const
uint m_anchor_point
Definition: cc708window.h:242
const int k708MaxWindows
Definition: cc708window.h:72
void ClearBuffers(bool input, bool output, int outputStreamIdx=-1)
void AddAssEvent(char *event)
MythImage * GetFormatImage()
Returns a blank reference counted image in the format required for the Draw functions for this painte...
void SetEnabled(bool enable)
Definition: cc608reader.h:84
bool IsFrameBasedTiming(void) const
Returns true in case the subtitle timing data is frame-based.
bool InitialiseAssLibrary(void)
void NormPoint(void)
Definition: mythrect.cpp:399
bool GetExists(void) const
Definition: cc708window.h:279
void DisplayRawTextSubtitles(void)
static int x3
Definition: mythsocket.cpp:62
virtual void Draw(void)
void DisplayAVSubtitles(void)
uint m_anchor_horizontal
Definition: cc708window.h:245
void ResetChanged(void)
Definition: cc708window.h:298
static void CreateProviderDefault(const QString &family, const CC708CharacterAttribute &attr, MythUIType *parent, bool isComplement, MythFontProperties **font, MythUIShape **bg)
QList< MythUIType * > m_ChildrenList
Definition: mythuitype.h:228
DecoderBase * GetDecoder(void)
Returns the stream decoder currently in use.
Definition: mythplayer.h:306
QMutex m_lock
Definition: cc608reader.h:50
void ResetElementState(void)
void AddChild(MythUIType *child)
Add a child UIType.
Definition: mythuitype.cpp:85
void AddScaledImage(QImage &img, QRect &pos)
void Draw(void) override
static const QString kSubFamily608("608")
virtual int64_t NormalizeVideoTimecode(int64_t timecode)
Definition: decoderbase.h:186
MythPlayer * m_player
QRegExp m_removeHTML
virtual void PreRender(void)
virtual SubtitleReader * GetSubReader(uint=0)
Definition: mythplayer.h:327