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;
226  int size;
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;
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;
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  for (int i = 0; i < m_cleanup.size(); ++i)
349  {
350  m_cleanup[i]->DeleteAllChildren();
351  m_cleanup[i]->deleteLater();
352  m_cleanup[i] = nullptr; // just to be safe
353  }
354 }
355 
356 QString SubtitleFormat::MakePrefix(const QString &family,
357  const CC708CharacterAttribute &attr)
358 {
359  if (family == kSubFamily708)
360  return family + "_" + QString::number(attr.m_fontTag & 0x7);
361  return family;
362 }
363 
364 void SubtitleFormat::CreateProviderDefault(const QString &family,
365  const CC708CharacterAttribute &attr,
366  MythUIType *parent,
367  bool isComplement,
368  MythFontProperties **returnFont,
369  MythUIShape **returnBg)
370 {
371  auto *font = new MythFontProperties();
372  auto *bg = new MythUIShape(parent, kSubProvider);
373  if (family == kSubFamily608)
374  {
375  font->GetFace()->setFamily("FreeMono");
376  QBrush brush(Qt::black);
377  bg->SetFillBrush(brush);
378  bg->SetLinePen(QPen(brush, 0));
379  }
380  else if (family == kSubFamily708)
381  {
382  static const char *s_cc708Fonts[] = {
383  "FreeMono", // default
384  "FreeMono", // mono serif
385  "Droid Serif", // prop serif
386  "Droid Sans Mono", // mono sans
387  "Droid Sans", // prop sans
388  "Purisa", // casual
389  "TeX Gyre Chorus", // cursive
390  "Droid Serif" // small caps, QFont::SmallCaps will be applied
391  };
392  font->GetFace()->setFamily(s_cc708Fonts[attr.m_fontTag & 0x7]);
393  }
394  else if (family == kSubFamilyText)
395  {
396  font->GetFace()->setFamily("Droid Sans");
397  QBrush brush(Qt::black);
398  bg->SetFillBrush(brush);
399  bg->SetLinePen(QPen(brush, 0));
400  }
401  else if (family == kSubFamilyTeletext)
402  {
403  font->GetFace()->setFamily("FreeMono");
404  // Set up a background with default alpha=100% so that the theme can
405  // override the background alpha.
406  QBrush brush(Qt::black);
407  bg->SetFillBrush(brush);
408  bg->SetLinePen(QPen(brush, 0));
409  }
410  font->GetFace()->setPixelSize(10);
411 
412  if (isComplement)
413  Complement(font, bg);
414  parent->AddFont(kSubProvider, font);
415 
416  *returnFont = font;
417  *returnBg = bg;
418 }
419 
420 static QColor differentColor(const QColor &color)
421 {
422  return color == Qt::white ? Qt::black : Qt::white;
423 }
424 
425 // Change everything (with respect to the == operator) that the
426 // provider might define or override.
428 {
429  QPoint offset;
430  QColor color;
431  int alpha;
432  int size;
433  QFont *face = font->GetFace();
434  face->setItalic(!face->italic());
435  face->setPixelSize(face->pixelSize() + 1);
436  face->setUnderline(!face->underline());
437  face->setWeight((face->weight() + 1) % 32);
438  font->SetColor(differentColor(font->color()));
439 
440  font->GetShadow(offset, color, alpha);
441  offset.setX(offset.x() + 1);
442  font->SetShadow(!font->hasShadow(), offset, differentColor(color),
443  255 - alpha);
444 
445  font->GetOutline(color, size, alpha);
446  font->SetOutline(!font->hasOutline(), differentColor(color),
447  size + 1, 255 - alpha);
448 
449  bg->SetFillBrush(bg->m_fillBrush == Qt::NoBrush ?
450  Qt::SolidPattern : Qt::NoBrush);
451 }
452 
453 void SubtitleFormat::Load(const QString &family,
454  const CC708CharacterAttribute &attr)
455 {
456  // Widgets for the actual values
457  auto *baseParent = new MythUIType(nullptr, "base");
458  m_cleanup += baseParent;
459  MythFontProperties *providerBaseFont;
460  MythUIShape *providerBaseShape;
461  CreateProviderDefault(family, attr, baseParent, false,
462  &providerBaseFont, &providerBaseShape);
463 
464  // Widgets for the "negative" values
465  auto *negParent = new MythUIType(nullptr, "base");
466  m_cleanup += negParent;
467  MythFontProperties *negFont;
468  MythUIShape *negBG;
469  CreateProviderDefault(family, attr, negParent, true, &negFont, &negBG);
470 
471  bool posResult =
473  baseParent);
474  bool negResult =
476  negParent);
477  if (!posResult || !negResult)
478  LOG(VB_VBI, LOG_INFO,
479  QString("Couldn't load theme file %1").arg(kSubFileName));
480  QString prefix = MakePrefix(family, attr);
481  MythFontProperties *resultFont = baseParent->GetFont(prefix);
482  if (!resultFont)
483  resultFont = providerBaseFont;
484  auto *resultBG = dynamic_cast<MythUIShape *>(baseParent->GetChild(prefix));
485 
486  // The providerBaseShape object is not leaked here. It is added
487  // to a list of children in its base class constructor.
488  if (!resultBG)
489  resultBG = providerBaseShape;
490  MythFontProperties *testFont = negParent->GetFont(prefix);
491  if (!testFont)
492  testFont = negFont;
493  auto *testBG = dynamic_cast<MythUIShape *>(negParent->GetChild(prefix));
494  if (!testBG)
495  testBG = negBG;
496  if (family == kSubFamily708 &&
497  (attr.m_fontTag & 0x7) == k708AttrFontSmallCaps)
498  resultFont->GetFace()->setCapitalization(QFont::SmallCaps);
499  m_fontMap[prefix] = resultFont;
500  m_shapeMap[prefix] = resultBG;
501  LOG(VB_VBI, LOG_DEBUG,
502  QString("providerBaseFont = %1").arg(fontToString(providerBaseFont)));
503  LOG(VB_VBI, LOG_DEBUG,
504  QString("negFont = %1").arg(fontToString(negFont)));
505  LOG(VB_VBI, LOG_DEBUG,
506  QString("resultFont = %1").arg(fontToString(resultFont)));
507  LOG(VB_VBI, LOG_DEBUG,
508  QString("testFont = %1").arg(fontToString(testFont)));
509  m_changeMap[prefix] = Diff(family, attr, resultFont, testFont,
510  resultBG, testBG);
511  QPoint offset;
512  QColor color;
513  int alpha;
514  int size;
515  resultFont->GetShadow(offset, color, alpha);
516  resultFont->GetOutline(color, size, alpha);
517  m_outlineSizeMap[prefix] = size;
518  m_shadowOffsetMap[prefix] = offset;
519  m_pixelSizeMap[prefix] = resultFont->GetFace()->pixelSize();
520 
521  delete negFont;
522 }
523 
524 QSet<QString> SubtitleFormat::Diff(const QString &family,
525  const CC708CharacterAttribute &attr,
526  MythFontProperties *font1,
527  MythFontProperties *font2,
528  MythUIShape *bg1,
529  MythUIShape *bg2)
530 {
531  bool is708 = (family == kSubFamily708);
532  QSet<QString> result;
533  QFont *face1 = font1->GetFace();
534  QFont *face2 = font2->GetFace();
535  if (face1->italic() != face2->italic())
536  result << kSubAttrItalics;
537  if (face1->weight() != face2->weight())
538  result << kSubAttrBold;
539  if (face1->underline() != face2->underline())
540  result << kSubAttrUnderline;
541  if (face1->pixelSize() != face2->pixelSize())
542  result << kSubAttrPixelsize;
543  if (font1->color() != font2->color())
544  result << kSubAttrColor;
545  if (is708 && font1->hasShadow() != font2->hasShadow())
546  {
547  result << kSubAttrShadow;
548  QPoint offset1;
549  QPoint offset2;
550  QColor color1;
551  QColor color2;
552  int alpha1;
553  int alpha2;
554  font1->GetShadow(offset1, color1, alpha1);
555  font2->GetShadow(offset2, color2, alpha2);
556  if (offset1 != offset2)
557  result << kSubAttrShadowoffset;
558  if (color1 != color2)
559  result << kSubAttrShadowcolor;
560  if (alpha1 != alpha2)
561  result << kSubAttrShadowalpha;
562  }
563  if (is708 && font1->hasOutline() != font2->hasOutline())
564  {
565  result << kSubAttrOutline;
566  QColor color1;
567  QColor color2;
568  int size1;
569  int size2;
570  int alpha1;
571  int alpha2;
572  font1->GetOutline(color1, size1, alpha1);
573  font2->GetOutline(color2, size2, alpha2);
574  if (color1 != color2)
575  result << kSubAttrOutlinecolor;
576  if (size1 != size2)
577  result << kSubAttrOutlinesize;
578  if (alpha1 != alpha2)
579  result << kSubAttrOutlinealpha;
580  }
581  if (bg1->m_fillBrush != bg2->m_fillBrush)
582  result << kSubAttrBGfill;
583 
584  QString values = "";
585  QSet<QString>::const_iterator i = result.constBegin();
586  for (; 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;
723  int rightPadding;
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  for (int i = 0; i < m_lines.size(); i++)
789  {
790  QSize sz = m_lines[i].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  for (int i = 0; i < m_lines.size(); i++)
816  {
817  if (m_lines[i].m_xIndent < 0)
818  m_lines[i].m_xIndent = anchor_x;
819  if (m_lines[i].m_yIndent < 0)
820  m_lines[i].m_yIndent = y;
821  y += m_lines[i].CalcSize(LINE_SPACING).height();
822  // Prune leading all-whitespace chunks.
823  while (!m_lines[i].chunks.isEmpty() &&
824  m_lines[i].chunks.first().m_text.trimmed().isEmpty())
825  {
826  m_lines[i].m_xIndent +=
827  m_lines[i].chunks.first().CalcSize().width();
828  m_lines[i].chunks.removeFirst();
829  }
830  // Prune trailing all-whitespace chunks.
831  while (!m_lines[i].chunks.isEmpty() &&
832  m_lines[i].chunks.last().m_text.trimmed().isEmpty())
833  {
834  m_lines[i].chunks.removeLast();
835  }
836  // Trim trailing whitespace from last chunk. (Trimming
837  // leading whitespace from all chunks is handled in the Draw()
838  // routine.)
839  if (!m_lines[i].chunks.isEmpty())
840  {
841  QString *str = &m_lines[i].chunks.last().m_text;
842  int idx = str->length() - 1;
843  while (idx >= 0 && str->at(idx) == ' ')
844  --idx;
845  str->truncate(idx + 1);
846  }
847  }
848 }
849 
851 {
852  for (int i = 0; i < m_lines.size(); i++)
853  {
854  int x = m_lines[i].m_xIndent;
855  int y = m_lines[i].m_yIndent;
856  int height = m_lines[i].CalcSize().height();
857  QList<FormattedTextChunk>::iterator chunk;
858  bool isFirst = true;
859  for (chunk = m_lines[i].chunks.begin();
860  chunk != m_lines[i].chunks.end();
861  ++chunk)
862  {
863  bool isLast = (chunk + 1 == m_lines[i].chunks.end());
864  (*chunk).PreRender(isFirst, isLast, x, y, height);
865  isFirst = false;
866  }
867  }
868 }
869 
870 // Returns true if anything new was drawn, false if not. The caller
871 // should call SubtitleScreen::OptimiseDisplayedArea() if true is
872 // returned.
874 {
875  QList<MythUIType *> textList;
876  QList<MythUIType *> shapeList;
877  for (int i = 0; i < m_lines.size(); i++)
878  {
879  QList<FormattedTextChunk>::const_iterator chunk;
880  for (chunk = m_lines[i].chunks.constBegin();
881  chunk != m_lines[i].chunks.constEnd();
882  ++chunk)
883  {
884  MythFontProperties *mythfont =
885  m_subScreen->GetFont((*chunk).m_format);
886  if (!mythfont)
887  continue;
888  // Note: nullptr is passed as the parent argument to the
889  // MythUI constructors so that we can control the drawing
890  // order of the children. In particular, background
891  // shapes should be added/drawn first, and text drawn on
892  // top.
893  if ((*chunk).m_textRect.width() > 0) {
894  auto *text = new SubSimpleText((*chunk).m_text, *mythfont,
895  (*chunk).m_textRect,
896  Qt::AlignLeft|Qt::AlignTop,
897  /*m_subScreen*/nullptr,
898  (*chunk).m_textName, CacheNum(),
899  m_start + m_duration);
900  textList += text;
901  }
902  if ((*chunk).m_bgShapeRect.width() > 0) {
904  GetBackground(/*m_subScreen*/nullptr,
905  (*chunk).m_bgShapeName,
906  m_base, (*chunk).m_format,
907  MythRect((*chunk).m_bgShapeRect), CacheNum(),
909  if (bgshape)
910  {
911  bgshape->SetArea(MythRect((*chunk).m_bgShapeRect));
912  shapeList += bgshape;
913  }
914  }
915  }
916  }
917  while (!shapeList.isEmpty())
918  m_subScreen->AddChild(shapeList.takeFirst());
919  while (!textList.isEmpty())
920  m_subScreen->AddChild(textList.takeFirst());
921 }
922 
923 QStringList FormattedTextSubtitle::ToSRT(void) const
924 {
925  QStringList result;
926  for (int i = 0; i < m_lines.size(); i++)
927  {
928  QString line;
929  if (m_lines[i].m_origX > 0)
930  line.fill(' ', m_lines[i].m_origX);
931  QList<FormattedTextChunk>::const_iterator chunk;
932  for (chunk = m_lines[i].chunks.constBegin();
933  chunk != m_lines[i].chunks.constEnd();
934  ++chunk)
935  {
936  const QString &text = (*chunk).m_text;
937  const CC708CharacterAttribute &attr = (*chunk).m_format;
938  bool isBlank = !attr.m_underline && text.trimmed().isEmpty();
939  if (!isBlank)
940  {
941  if (attr.m_boldface)
942  line += "<b>";
943  if (attr.m_italics)
944  line += "<i>";
945  if (attr.m_underline)
946  line += "<u>";
947  if (attr.GetFGColor() != Qt::white)
948  line += QString("<font color=\"%1\">")
949  .arg(srtColorString(attr.GetFGColor()));
950  }
951  line += text;
952  if (!isBlank)
953  {
954  if (attr.GetFGColor() != Qt::white)
955  line += QString("</font>");
956  if (attr.m_underline)
957  line += "</u>";
958  if (attr.m_italics)
959  line += "</i>";
960  if (attr.m_boldface)
961  line += "</b>";
962  }
963  }
964  if (!line.trimmed().isEmpty())
965  result += line;
966  }
967  return result;
968 }
969 
970 void FormattedTextSubtitleSRT::Init(const QStringList &subs)
971 {
972  // Does a simplistic parsing of HTML tags from the strings.
973  // Nesting is not implemented. Stray whitespace may cause
974  // legitimate tags to be ignored. All other HTML tags are
975  // stripped and ignored.
976  //
977  // <i> - enable italics
978  // </i> - disable italics
979  // <b> - enable boldface
980  // </b> - disable boldface
981  // <u> - enable underline
982  // </u> - disable underline
983  // <font color="#xxyyzz"> - change font color
984  // </font> - reset font color to white
985 
986  int pixelSize = m_safeArea.height() / 20;
987  if (m_subScreen)
988  m_subScreen->SetFontSize(pixelSize);
989  m_xAnchorPoint = 1; // center
990  m_yAnchorPoint = 2; // bottom
991  m_xAnchor = m_safeArea.width() / 2;
992  m_yAnchor = m_safeArea.height();
993 
994  bool isItalic = false;
995  bool isBold = false;
996  bool isUnderline = false;
997  QColor color(Qt::white);
998  QRegExp htmlTag("</?.+>");
999  QString htmlPrefix("<font color=\"");
1000  QString htmlSuffix("\">");
1001  htmlTag.setMinimal(true);
1002  foreach (QString subtitle, subs)
1003  {
1004  FormattedTextLine line;
1005  QString text(subtitle);
1006  while (!text.isEmpty())
1007  {
1008  int pos = text.indexOf(htmlTag);
1009  if (pos != 0) // don't add a zero-length string
1010  {
1011  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1012  color);
1013  FormattedTextChunk chunk(text.left(pos), attr, m_subScreen);
1014  line.chunks += chunk;
1015  text = (pos < 0 ? "" : text.mid(pos));
1016  LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
1017  .arg(chunk.ToLogString()));
1018  }
1019  if (pos >= 0)
1020  {
1021  int htmlLen = htmlTag.matchedLength();
1022  QString html = text.left(htmlLen).toLower();
1023  text = text.mid(htmlLen);
1024  if (html == "<i>")
1025  isItalic = true;
1026  else if (html == "</i>")
1027  isItalic = false;
1028  else if (html.startsWith(htmlPrefix) &&
1029  html.endsWith(htmlSuffix))
1030  {
1031  int colorLen = html.length() -
1032  (htmlPrefix.length() + htmlSuffix.length());
1033  QString colorString(
1034  html.mid(htmlPrefix.length(), colorLen));
1035  QColor newColor(colorString);
1036  if (newColor.isValid())
1037  {
1038  color = newColor;
1039  }
1040  else
1041  {
1042  LOG(VB_VBI, LOG_INFO,
1043  QString("Ignoring invalid SRT color specification: "
1044  "'%1'").arg(colorString));
1045  }
1046  }
1047  else if (html == "</font>")
1048  color = Qt::white;
1049  else if (html == "<b>")
1050  isBold = true;
1051  else if (html == "</b>")
1052  isBold = false;
1053  else if (html == "<u>")
1054  isUnderline = true;
1055  else if (html == "</u>")
1056  isUnderline = false;
1057  else
1058  {
1059  LOG(VB_VBI, LOG_INFO,
1060  QString("Ignoring unknown SRT formatting: '%1'")
1061  .arg(html));
1062  }
1063 
1064  LOG(VB_VBI, LOG_INFO,
1065  QString("SRT formatting change '%1', "
1066  "new ital=%2 bold=%3 uline=%4 color=%5)")
1067  .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1068  .arg(srtColorString(color)));
1069  }
1070  }
1071  m_lines += line;
1072  }
1073 }
1074 
1076 {
1077  int maxWidth = m_safeArea.width();
1078  for (int i = 0; i < m_lines.size(); i++)
1079  {
1080  int width = m_lines[i].CalcSize().width();
1081  // Move entire chunks to the next line as necessary. Leave at
1082  // least one chunk on the current line.
1083  while (width > maxWidth && m_lines[i].chunks.size() > 1)
1084  {
1085  width -= m_lines[i].chunks.back().CalcSize().width();
1086  // Make sure there's a next line to wrap into.
1087  if (m_lines.size() == i + 1)
1088  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1089  m_lines[i].m_yIndent);
1090  m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
1091  LOG(VB_VBI, LOG_INFO,
1092  QString("Wrapping chunk to next line: '%1'")
1093  .arg(m_lines[i+1].chunks[0].m_text));
1094  }
1095  // Split the last chunk until width is small enough or until
1096  // no more splits are possible.
1097  bool isSplitPossible = true;
1098  while (width > maxWidth && isSplitPossible)
1099  {
1100  FormattedTextChunk newChunk;
1101  isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
1102  if (isSplitPossible)
1103  {
1104  // Make sure there's a next line to split into.
1105  if (m_lines.size() == i + 1)
1106  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1107  m_lines[i].m_yIndent);
1108  m_lines[i+1].chunks.prepend(newChunk);
1109  width = m_lines[i].CalcSize().width();
1110  }
1111  }
1112  }
1113 }
1114 
1120 static QString extract_cc608(QString &text, int &color,
1121  bool &isItalic, bool &isUnderline)
1122 {
1123  QString result;
1124  QString orig(text);
1125 
1126  // Handle an initial control sequence.
1127  if (text.length() >= 1 && text[0] >= 0x7000)
1128  {
1129  int op = text[0].unicode() - 0x7000;
1130  isUnderline = ((op & 0x1) != 0);
1131  switch (op & ~1)
1132  {
1133  case 0x0e:
1134  // color unchanged
1135  isItalic = true;
1136  break;
1137  case 0x1e:
1138  color = op >> 1;
1139  isItalic = true;
1140  break;
1141  case 0x20:
1142  // color unchanged
1143  // italics unchanged
1144  break;
1145  default:
1146  color = (op & 0xf) >> 1;
1147  isItalic = false;
1148  break;
1149  }
1150  text = text.mid(1);
1151  }
1152 
1153  // Copy the string into the result, up to the next control
1154  // character.
1155  int nextControl = text.indexOf(QRegExp("[\\x7000-\\x7fff]"));
1156  if (nextControl < 0)
1157  {
1158  result = text;
1159  text.clear();
1160  }
1161  else
1162  {
1163  result = text.left(nextControl);
1164  text = text.mid(nextControl);
1165  }
1166 
1167  return result;
1168 }
1169 
1170 // Adjusts the Y coordinates to avoid overlap, which could happen as a
1171 // result of a large text zoom factor. Then, if the total height
1172 // exceeds the safe area, compresses each piece of vertical blank
1173 // space proportionally to make it fit.
1175 {
1176  int i;
1177  int totalHeight = 0;
1178  int totalSpace = 0;
1179  int firstY = 0;
1180  int prevY = 0;
1181  QVector<int> heights(m_lines.size());
1182  QVector<int> spaceBefore(m_lines.size());
1183  // Calculate totalHeight and totalSpace
1184  for (i = 0; i < m_lines.size(); i++)
1185  {
1186  m_lines[i].m_yIndent = max(m_lines[i].m_yIndent, prevY); // avoid overlap
1187  int y = m_lines[i].m_yIndent;
1188  if (i == 0)
1189  firstY = prevY = y;
1190  int height = m_lines[i].CalcSize().height();
1191  heights[i] = height;
1192  spaceBefore[i] = y - prevY;
1193  totalSpace += (y - prevY);
1194  prevY = y + height;
1195  totalHeight += height;
1196  }
1197  int safeHeight = m_safeArea.height();
1198  int overage = min(totalHeight - safeHeight, totalSpace);
1199 
1200  // Recalculate Y coordinates, applying the shrink factor to space
1201  // between each line.
1202  if (overage > 0 && totalSpace > 0)
1203  {
1204  float shrink = (totalSpace - overage) / (float)totalSpace;
1205  prevY = firstY;
1206  for (i = 0; i < m_lines.size(); i++)
1207  {
1208  m_lines[i].m_yIndent = prevY + spaceBefore[i] * shrink;
1209  prevY = m_lines[i].m_yIndent + heights[i];
1210  }
1211  }
1212 
1213  // Shift Y coordinates back up into the safe area.
1214  int shift = min(firstY, max(0, prevY - safeHeight));
1215  for (i = 0; i < m_lines.size(); i++)
1216  m_lines[i].m_yIndent -= shift;
1217 
1219 }
1220 
1221 void FormattedTextSubtitle608::Init(const vector<CC608Text*> &buffers)
1222 {
1223  static const QColor kClr[8] =
1224  {
1225  Qt::white, Qt::green, Qt::blue, Qt::cyan,
1226  Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1227  };
1228 
1229  if (buffers.empty())
1230  return;
1231  auto i = buffers.cbegin();
1232  int xscale = 36;
1233  int yscale = 17;
1234  int pixelSize = m_safeArea.height() / (yscale * LINE_SPACING);
1235  int fontwidth = 0;
1236  int xmid = 0;
1237  int zoom = 100;
1238  if (m_subScreen)
1239  {
1240  zoom = m_subScreen->GetZoom();
1241  m_subScreen->SetFontSize(pixelSize);
1242  CC708CharacterAttribute def_attr(false, false, false, kClr[0]);
1243  QFont *font = m_subScreen->GetFont(def_attr)->GetFace();
1244  QFontMetrics fm(*font);
1245  fontwidth = fm.averageCharWidth();
1246  xmid = m_safeArea.width() / 2;
1247  // Disable centering for zoom factor >= 100%
1248  if (zoom >= 100)
1249  xscale = m_safeArea.width() / fontwidth;
1250  }
1251 
1252  for (; i != buffers.cend(); ++i)
1253  {
1254  CC608Text *cc = (*i);
1255  int color = 0;
1256  bool isItalic = false;
1257  bool isUnderline = false;
1258  const bool isBold = false;
1259  QString text(cc->m_text);
1260 
1261  int orig_x = cc->m_x;
1262  // position as if we use a fixed size font
1263  // - font size already has zoom factor applied
1264 
1265  int x;
1266  if (xmid)
1267  // center horizontally
1268  x = xmid + (orig_x - xscale / 2) * fontwidth;
1269  else
1270  // fallback
1271  x = (orig_x + 3) * m_safeArea.width() / xscale;
1272 
1273  int orig_y = cc->m_y;
1274  int y;
1275  if (orig_y < yscale / 2)
1276  // top half -- anchor up
1277  y = (orig_y * m_safeArea.height() * zoom / (yscale * 100));
1278  else
1279  // bottom half -- anchor down
1280  y = m_safeArea.height() -
1281  ((yscale - orig_y - 0.5) * m_safeArea.height() * zoom /
1282  (yscale * 100));
1283 
1284  FormattedTextLine line(x, y, orig_x, orig_y);
1285  while (!text.isNull())
1286  {
1287  QString captionText =
1288  extract_cc608(text, color, isItalic, isUnderline);
1289  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1290  kClr[min(max(0, color), 7)]);
1291  FormattedTextChunk chunk(captionText, attr, m_subScreen);
1292  line.chunks += chunk;
1293  LOG(VB_VBI, LOG_INFO,
1294  QString("Adding cc608 chunk (%1,%2): %3")
1295  .arg(cc->m_x).arg(cc->m_y).arg(chunk.ToLogString()));
1296  }
1297  m_lines += line;
1298  }
1299 }
1300 
1302 {
1303  // Draw the window background after calculating bounding rectangle
1304  // in Layout()
1305  if (m_bgFillAlpha) // TODO border?
1306  {
1307  QBrush fill(m_bgFillColor, Qt::SolidPattern);
1308  auto *shape = new SubShape(m_subScreen, QString("cc708bg%1").arg(m_num),
1310  shape->SetFillBrush(fill);
1311  shape->SetArea(MythRect(m_bounds));
1312  }
1313  // The background is drawn first so that it appears behind
1314  // everything else.
1316 }
1317 
1319  const vector<CC708String*> &list,
1320  float aspect)
1321 {
1322  LOG(VB_VBI, LOG_DEBUG,LOC +
1323  QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1324  "relative %5")
1325  .arg(m_num).arg(win.m_anchor_point).arg(win.m_anchor_horizontal)
1326  .arg(win.m_anchor_vertical).arg(win.m_relative_pos));
1327  int pixelSize = m_safeArea.height() / 20;
1328  if (m_subScreen)
1329  m_subScreen->SetFontSize(pixelSize);
1330 
1331  float xrange = win.m_relative_pos ? 100.0F :
1332  (aspect > 1.4F) ? 210.0F : 160.0F;
1333  float yrange = win.m_relative_pos ? 100.0F : 75.0F;
1334  float xmult = (float)m_safeArea.width() / xrange;
1335  float ymult = (float)m_safeArea.height() / yrange;
1336  uint anchor_x = (uint)(xmult * (float)win.m_anchor_horizontal);
1337  uint anchor_y = (uint)(ymult * (float)win.m_anchor_vertical);
1338  m_xAnchorPoint = win.m_anchor_point % 3;
1339  m_yAnchorPoint = win.m_anchor_point / 3;
1340  m_xAnchor = anchor_x;
1341  m_yAnchor = anchor_y;
1342 
1343  for (size_t i = 0; i < list.size(); i++)
1344  {
1345  if (list[i]->m_y >= (uint)m_lines.size())
1346  m_lines.resize(list[i]->m_y + 1);
1347  FormattedTextChunk chunk(list[i]->m_str, list[i]->m_attr, m_subScreen);
1348  m_lines[list[i]->m_y].chunks += chunk;
1349  LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
1350  .arg(m_num).arg(list[i]->m_y).arg(chunk.ToLogString()));
1351  }
1352 }
1353 
1355 
1356 SubtitleScreen::SubtitleScreen(MythPlayer *player, const char * name,
1357  int fontStretch) :
1358  MythScreenType((MythScreenType*)nullptr, name),
1359  m_player(player),
1360  m_fontStretch(fontStretch),
1361  m_format(new SubtitleFormat)
1362 {
1363  m_removeHTML.setMinimal(true);
1364 }
1365 
1367 {
1369  delete m_format;
1370 #ifdef USING_LIBASS
1372 #endif
1373 }
1374 
1375 void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
1376 {
1377  m_subtitleType = type;
1378 
1379  if (forced_only)
1380  {
1382  SetVisible(true);
1383  SetArea(MythRect());
1384  return;
1385  }
1386 
1387  if (m_subreader)
1388  {
1392  }
1393  if (m_608reader)
1395  if (m_708reader)
1399  SetArea(MythRect());
1400  switch (m_subtitleType)
1401  {
1402  case kDisplayTextSubtitle:
1405  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1406  break;
1407  case kDisplayCC608:
1409  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1410  break;
1411  case kDisplayCC708:
1413  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1414  break;
1415  case kDisplayAVSubtitle:
1417  m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
1418  break;
1419  }
1422 }
1423 
1425 {
1427  return;
1429  SetVisible(false);
1430  SetArea(MythRect());
1431 }
1432 
1434 {
1437 #ifdef USING_LIBASS
1438  if (m_assTrack)
1439  ass_flush_events(m_assTrack);
1440 #endif
1441 }
1442 
1444 {
1450  m_608reader->ClearBuffers(true, true);
1453 }
1454 
1456 {
1459 }
1460 
1461 void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
1462 {
1463  if (!dvdButton || !m_player)
1464  return;
1465 
1467  if (!vo)
1468  return;
1469 
1472 
1473  float tmp = 0.0;
1474  QRect dummy;
1475  vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1476 
1477  AVSubtitleRect *hl_button = dvdButton->rects[0];
1478  uint h = hl_button->h;
1479  uint w = hl_button->w;
1480  QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1481  QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1482  auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1483 
1484  QVector<uint32_t> bg_palette(4);
1485  for (int i = 0; i < 4; i++)
1486  bg_palette[i] = bgpalette[i];
1487  bg_image.setColorTable(bg_palette);
1488 
1489  // copy button region of background image
1490  const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1491  QImage fg_image = bg_image.copy(fg_rect);
1492  QVector<uint32_t> fg_palette(4);
1493  auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1494  if (fgpalette)
1495  {
1496  for (int i = 0; i < 4; i++)
1497  fg_palette[i] = fgpalette[i];
1498  fg_image.setColorTable(fg_palette);
1499  }
1500 
1501  bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1502  fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1503 
1504  // set pixel of highlight area to highlight color
1505  for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1506  {
1507  if ((x < 0) || (x > hl_button->w))
1508  continue;
1509  for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1510  {
1511  if ((y < 0) || (y > hl_button->h))
1512  continue;
1513  bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1514  }
1515  }
1516 
1517  AddScaledImage(bg_image, rect);
1518 }
1519 
1520 void SubtitleScreen::SetZoom(int percent)
1521 {
1522  m_textFontZoom = percent;
1523  if (m_family == kSubFamilyAV)
1524  gCoreContext->SaveSetting("OSDAVSubZoom", percent);
1525  else
1526  gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
1527 }
1528 
1530 {
1531  return m_textFontZoom;
1532 }
1533 
1535 {
1536  m_textFontDelayMs = ms;
1537 }
1538 
1540 {
1541  return m_textFontDelayMs;
1542 }
1543 
1545 {
1546  QList<MythUIType *> list = m_ChildrenList;
1547  QList<MythUIType *>::iterator it;
1548  for (it = list.begin(); it != list.end(); ++it)
1549  {
1550  MythUIType *child = *it;
1551  auto *wrapper = dynamic_cast<SubWrapper *>(child);
1552  if (wrapper)
1553  {
1554  int whichImageCache = wrapper->GetWhichImageCache();
1555  if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1556  {
1558  DeleteChild(child);
1559  }
1560  }
1561  }
1562 }
1563 
1564 // SetElementAdded() should be called after a new element is added to
1565 // the subtitle screen.
1567 {
1568  m_refreshModified = true;
1569 }
1570 
1571 // SetElementResized() should be called after a subtitle screen
1572 // element's size is changed.
1574 {
1575  SetElementAdded();
1576 }
1577 
1578 // SetElementAdded() should be called *before* an element is deleted
1579 // from the subtitle screen.
1581 {
1582  if (!m_refreshDeleted)
1583  SetRedraw();
1584  m_refreshDeleted = true;
1585 }
1586 
1587 // The QFontMetrics class does not account for the MythFontProperties
1588 // shadow and offset properties. This method calculates the
1589 // additional padding to the right and below that is needed for proper
1590 // bounding box computation.
1592 {
1593  QColor color;
1594  int alpha;
1595  int outlineSize = 0;
1596  int shadowWidth = 0;
1597  int shadowHeight = 0;
1598  if (mythfont->hasOutline())
1599  {
1600  mythfont->GetOutline(color, outlineSize, alpha);
1601  MythPoint outline(outlineSize, outlineSize);
1602  outline.NormPoint();
1603  outlineSize = outline.x();
1604  }
1605  if (mythfont->hasShadow())
1606  {
1607  MythPoint shadowOffset;
1608  mythfont->GetShadow(shadowOffset, color, alpha);
1609  shadowOffset.NormPoint();
1610  shadowWidth = abs(shadowOffset.x());
1611  shadowHeight = abs(shadowOffset.y());
1612  // Shadow and outline overlap, so don't just add them.
1613  shadowWidth = max(shadowWidth, outlineSize);
1614  shadowHeight = max(shadowHeight, outlineSize);
1615  }
1616  return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1617 }
1618 
1619 QSize SubtitleScreen::CalcTextSize(const QString &text,
1620  const CC708CharacterAttribute &format,
1621  float layoutSpacing) const
1622 {
1623  MythFontProperties *mythfont = GetFont(format);
1624  QFont *font = mythfont->GetFace();
1625  QFontMetrics fm(*font);
1626 #if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1627  int width = fm.width(text);
1628 #else
1629  int width = fm.horizontalAdvance(text);
1630 #endif
1631  int height = fm.height() * (1 + PAD_HEIGHT);
1632  if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1633  height = max(height, (int)(font->pixelSize() * layoutSpacing));
1634  height += CalcShadowOffsetPadding(mythfont).height();
1635  return {width, height};
1636 }
1637 
1638 // Padding calculation is different depending on whether the padding
1639 // is on the left side or the right side of the text. Padding on the
1640 // right needs to add the shadow and offset padding.
1642  bool isFirst, bool isLast,
1643  int &left, int &right) const
1644 {
1645  MythFontProperties *mythfont = GetFont(format);
1646  QFont *font = mythfont->GetFace();
1647  QFontMetrics fm(*font);
1648  int basicPadding = fm.maxWidth() * PAD_WIDTH;
1649  left = isFirst ? basicPadding : 0;
1650  right = CalcShadowOffsetPadding(mythfont).width() +
1651  (isLast ? basicPadding : 0);
1652 }
1653 
1656 {
1657  return m_format->GetFont(m_family, attr, m_fontSize,
1659 }
1660 
1662 {
1663  SubtitleFormat format;
1664  CC708CharacterAttribute attr(false, false, false, Qt::white);
1665  MythFontProperties *mythfont =
1666  format.GetFont(kSubFamilyTeletext, attr,
1667  /*pixelsize*/20, /*zoom*/100, /*stretch*/100);
1668  return mythfont->face().family();
1669 }
1670 
1672 {
1673  SubtitleFormat format;
1674  return format.GetBackgroundAlpha(kSubFamilyTeletext);
1675 }
1676 
1678 {
1679  if (!m_player)
1680  return false;
1681 
1685  if (!m_subreader)
1686  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
1687  if (!m_608reader)
1688  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
1689  if (!m_708reader)
1690  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
1691 
1692  return true;
1693 }
1694 
1696 {
1697  QList<MythUIType *>::iterator it;
1698  QList<MythUIType *>::iterator itNext;
1699 
1700  MythVideoOutput *videoOut = m_player->GetVideoOutput();
1701  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1702  long long now = currentFrame ? currentFrame->timecode : LLONG_MAX;
1703  bool needRescale = (m_textFontZoom != m_textFontZoomPrev);
1704 
1705  for (it = m_ChildrenList.begin(); it != m_ChildrenList.end(); it = itNext)
1706  {
1707  itNext = it + 1;
1708  MythUIType *child = *it;
1709  auto *wrapper = dynamic_cast<SubWrapper *>(child);
1710  if (!wrapper)
1711  continue;
1712 
1713  // Expire the subtitle object if needed.
1714  long long expireTime = wrapper->GetExpireTime();
1715  if (expireTime > 0 && expireTime < now)
1716  {
1717  DeleteChild(child);
1719  continue;
1720  }
1721 
1722  // Rescale the AV subtitle image if the zoom changed.
1723  if (expireTime > 0 && needRescale)
1724  {
1725  auto *image = dynamic_cast<SubImage *>(child);
1726  if (image)
1727  {
1728  double factor = m_textFontZoom / (double)m_textFontZoomPrev;
1729  QSize size = image->GetImage()->size();
1730  size *= factor;
1731  image->GetImage()->Resize(size);
1733  }
1734  }
1735  }
1736 
1737  DisplayAVSubtitles(); // allow forced subtitles to work
1738 
1741  else if (kDisplayCC608 == m_subtitleType)
1743  else if (kDisplayCC708 == m_subtitleType)
1747  while (!m_qInited.isEmpty())
1748  {
1749  FormattedTextSubtitle *fsub = m_qInited.takeFirst();
1750  fsub->WrapLongLines();
1751  fsub->Layout();
1752  fsub->PreRender();
1753  fsub->Draw();
1754  delete fsub;
1755  SetElementAdded();
1756  }
1757 
1763 }
1764 
1766 {
1767  m_refreshModified = false;
1768  m_refreshDeleted = false;
1769 }
1770 
1772 {
1773  if (!m_refreshModified)
1774  return;
1775 
1776  QRegion visible;
1777  QListIterator<MythUIType *> i(m_ChildrenList);
1778  while (i.hasNext())
1779  {
1780  MythUIType *img = i.next();
1781  auto *wrapper = dynamic_cast<SubWrapper *>(img);
1782  if (wrapper && img->IsVisible())
1783  visible = visible.united(wrapper->GetOrigArea());
1784  }
1785 
1786  if (visible.isEmpty())
1787  return;
1788 
1789  QRect bounding = visible.boundingRect();
1790  bounding = bounding.translated(m_safeArea.topLeft());
1791  bounding = m_safeArea.intersected(bounding);
1792  int left = m_safeArea.left() - bounding.left();
1793  int top = m_safeArea.top() - bounding.top();
1794  SetArea(MythRect(bounding));
1795 
1796  i.toFront();
1797  while (i.hasNext())
1798  {
1799  MythUIType *img = i.next();
1800  auto *wrapper = dynamic_cast<SubWrapper *>(img);
1801  if (wrapper && img->IsVisible())
1802  img->SetArea(wrapper->GetOrigArea().translated(left, top));
1803  }
1804 }
1805 
1807 {
1808  if (!m_player || !m_subreader)
1809  return;
1810 
1812  QMutexLocker lock(&(subs->m_lock));
1813  if (subs->m_buffers.empty() && (kDisplayAVSubtitle != m_subtitleType))
1814  return;
1815 
1816  MythVideoOutput *videoOut = m_player->GetVideoOutput();
1817  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1818 
1819  if (!currentFrame || !videoOut)
1820  return;
1821 
1822  float tmp = 0.0;
1823  QRect dummy;
1824  videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1825 
1826  while (!subs->m_buffers.empty())
1827  {
1828  AVSubtitle subtitle = subs->m_buffers.front();
1829  if (subtitle.start_display_time > currentFrame->timecode)
1830  break;
1831 
1832  long long displayfor = subtitle.end_display_time -
1833  subtitle.start_display_time;
1834  if (displayfor == 0)
1835  displayfor = 60000;
1836  displayfor = (displayfor < 50) ? 50 : displayfor;
1837  long long late = currentFrame->timecode -
1838  subtitle.start_display_time;
1839 
1841  subs->m_buffers.pop_front();
1842  for (std::size_t i = 0; i < subtitle.num_rects; ++i)
1843  {
1844  AVSubtitleRect* rect = subtitle.rects[i];
1845 
1846  bool displaysub = true;
1847  if (!subs->m_buffers.empty() &&
1848  subs->m_buffers.front().end_display_time <
1849  currentFrame->timecode)
1850  {
1851  displaysub = false;
1852  }
1853 
1854  if (displaysub && rect->type == SUBTITLE_BITMAP)
1855  {
1856  QRect display(rect->display_x, rect->display_y,
1857  rect->display_w, rect->display_h);
1858 
1859  // XSUB and some DVD/DVB subs are based on the original video
1860  // size before the video was converted. We need to guess the
1861  // original size and allow for the difference
1862 
1863  int right = rect->x + rect->w;
1864  int bottom = rect->y + rect->h;
1865  if (subs->m_fixPosition || (currentFrame->height < bottom) ||
1866  (currentFrame->width < right) ||
1867  !display.width() || !display.height())
1868  {
1869  int sd_height = 576;
1870  if ((m_player->GetFrameRate() > 26.0F ||
1871  m_player->GetFrameRate() < 24.0F) && bottom <= 480)
1872  sd_height = 480;
1873  int height = ((currentFrame->height <= sd_height) &&
1874  (bottom <= sd_height)) ? sd_height :
1875  ((currentFrame->height <= 720) && bottom <= 720)
1876  ? 720 : 1080;
1877  int width = ((currentFrame->width <= 720) &&
1878  (right <= 720)) ? 720 :
1879  ((currentFrame->width <= 1280) &&
1880  (right <= 1280)) ? 1280 : 1920;
1881  display = QRect(0, 0, width, height);
1882  }
1883 
1884  // split into upper/lower to allow zooming
1885  QRect bbox;
1886  int uh = display.height() / 2 - rect->y;
1887  int lh;
1888  long long displayuntil = currentFrame->timecode + displayfor;
1889  if (uh > 0)
1890  {
1891  bbox = QRect(0, 0, rect->w, uh);
1892  uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
1893  subtitle.forced,
1894  QString("avsub%1t").arg(i),
1895  displayuntil, late);
1896  }
1897  else
1898  uh = 0;
1899  lh = rect->h - uh;
1900  if (lh > 0)
1901  {
1902  bbox = QRect(0, uh, rect->w, lh);
1903  DisplayScaledAVSubtitles(rect, bbox, false, display,
1904  subtitle.forced,
1905  QString("avsub%1b").arg(i),
1906  displayuntil, late);
1907  }
1908  }
1909 #ifdef USING_LIBASS
1910  else if (displaysub && rect->type == SUBTITLE_ASS)
1911  {
1913  AddAssEvent(rect->ass);
1914  }
1915 #endif
1916  }
1918  }
1919 #ifdef USING_LIBASS
1920  RenderAssTrack(currentFrame->timecode);
1921 #endif
1922 }
1923 
1924 int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
1925  QRect &bbox, bool top,
1926  QRect &display, int forced,
1927  const QString& imagename,
1928  long long displayuntil,
1929  long long late)
1930 {
1931  // split image vertically if it spans middle of display
1932  // - split point is empty line nearest the middle
1933  // crop image to reduce scaling time
1934  bool prev_empty = false;
1935 
1936  // initialize to opposite edges
1937  int xmin = bbox.right();
1938  int xmax = bbox.left();
1939  int ymin = bbox.bottom();
1940  int ymax = bbox.top();
1941  int ylast = bbox.top();
1942  int ysplit = bbox.bottom();
1943 
1944  // find bounds of active image
1945  for (int y = bbox.top(); y <= bbox.bottom(); ++y)
1946  {
1947  if (y >= rect->h)
1948  {
1949  // end of image
1950  if (!prev_empty)
1951  ylast = y;
1952  break;
1953  }
1954 
1955  bool empty = true;
1956  for (int x = bbox.left(); x <= bbox.right(); ++x)
1957  {
1958  const uint8_t color =
1959  rect->data[0][y * rect->linesize[0] + x];
1960  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
1961  if (pixel & 0xff000000)
1962  {
1963  empty = false;
1964  if (x < xmin)
1965  xmin = x;
1966  if (x > xmax)
1967  xmax = x;
1968  }
1969  }
1970 
1971  if (!empty)
1972  {
1973  if (y < ymin)
1974  ymin = y;
1975  if (y > ymax)
1976  ymax = y;
1977  }
1978  else if (!prev_empty)
1979  {
1980  // remember uppermost empty line
1981  ylast = y;
1982  }
1983  prev_empty = empty;
1984  }
1985 
1986  if (ymax <= ymin)
1987  return 0;
1988 
1989  if (top)
1990  {
1991  if (ylast < ymin)
1992  // no empty lines
1993  return 0;
1994 
1995  if (ymax == bbox.bottom())
1996  {
1997  ymax = ylast;
1998  ysplit = ylast;
1999  }
2000  }
2001 
2002  // set new bounds
2003  bbox.setLeft(xmin);
2004  bbox.setRight(xmax);
2005  bbox.setTop(ymin);
2006  bbox.setBottom(ymax);
2007 
2008  // copy active region
2009  // AVSubtitleRect's image data's not guaranteed to be 4 byte
2010  // aligned.
2011 
2012  QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2013 
2014  QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2015  for (int y = 0; y < bbox.height(); ++y)
2016  {
2017  int ysrc = y + bbox.top();
2018  for (int x = 0; x < bbox.width(); ++x)
2019  {
2020  int xsrc = x + bbox.left();
2021  const uint8_t color =
2022  rect->data[0][ysrc * rect->linesize[0] + xsrc];
2023  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
2024  qImage.setPixel(x, y, pixel);
2025  }
2026  }
2027 
2028  // translate to absolute coordinates
2029  bbox.translate(rect->x, rect->y);
2030 
2031  // scale and move according to zoom factor
2032  bbox.setWidth(bbox.width() * m_textFontZoom / 100);
2033  bbox.setHeight(bbox.height() * m_textFontZoom / 100);
2034 
2035  MythVideoOutput *videoOut = m_player->GetVideoOutput();
2036  QRect scaled = videoOut->GetImageRect(bbox, &display);
2037 
2038  if (scaled.size() != orig_rect.size())
2039  qImage = qImage.scaled(scaled.width(), scaled.height(),
2040  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2041 
2042  int hsize = m_safeArea.width();
2043  int vsize = m_safeArea.height();
2044 
2045  scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 +
2046  m_textFontZoom * scaled.left()) /
2047  100);
2048  if (top)
2049  // anchor up
2050  scaled.moveTop(scaled.top() * m_textFontZoom / 100);
2051  else
2052  // anchor down
2053  scaled.moveTop(((100 - m_textFontZoom) * vsize +
2054  m_textFontZoom * scaled.top()) /
2055  100);
2056 
2057 
2058  MythPainter *osd_painter = videoOut->GetOSDPainter();
2059  MythImage *image = nullptr;
2060  if (osd_painter)
2061  image = osd_painter->GetFormatImage();
2062 
2063  SubImage *uiimage = nullptr;
2064  if (image)
2065  {
2066  image->Assign(qImage);
2067  uiimage = new SubImage(this, imagename,
2068  MythRect(scaled), displayuntil);
2069  if (uiimage)
2070  {
2071  uiimage->SetImage(image);
2072  uiimage->SetArea(MythRect(scaled));
2073  SetElementAdded();
2074  }
2075  image->DecrRef();
2076  image = nullptr;
2077  }
2078  if (uiimage)
2079  {
2080  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2081  QString("Display %1AV sub until %2ms")
2082  .arg(forced ? "FORCED " : "")
2083  .arg(displayuntil));
2084  if (late > 50)
2085  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2086  QString("AV Sub was %1ms late").arg(late));
2087  }
2088 
2089  return (ysplit + 1);
2090 }
2091 
2093 {
2094  if (!m_player || !m_subreader)
2095  return;
2096 
2097  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2098  changed |= (m_textFontDelayMs != m_textFontDelayMsPrev);
2100  if (!vo)
2101  return;
2102  m_safeArea = vo->GetSafeRect();
2103 
2104  VideoFrame *currentFrame = vo->GetLastShownFrame();
2105  if (!currentFrame)
2106  return;
2107 
2109  subs->Lock();
2110  uint64_t playPos = 0;
2111  int playPosAdj = m_textFontDelayMs;
2112  if (subs->IsFrameBasedTiming())
2113  {
2114  // frame based subtitles get out of synch after running mythcommflag
2115  // for the file, i.e., the following number is wrong and does not
2116  // match the subtitle frame numbers:
2117  playPos = currentFrame->frameNumber;
2118  playPosAdj /= m_player->GetFrameRate();
2119  }
2120  else
2121  {
2122  // Use timecodes for time based SRT subtitles. Feeding this into
2123  // NormalizeVideoTimecode() should adjust for non-zero start times
2124  // and wraps. For MPEG, wraps will occur just once every 26.5 hours
2125  // and other formats less frequently so this should be sufficient.
2126  // Note: timecodes should now always be valid even in the case
2127  // when a frame doesn't have a valid timestamp. If an exception is
2128  // found where this is not true then we need to use the frameNumber
2129  // when timecode is not defined by uncommenting the following lines.
2130  //if (currentFrame->timecode == 0)
2131  // playPos = (uint64_t)
2132  // ((currentFrame->frameNumber / video_frame_rate) * 1000);
2133  //else
2134  playPos = m_player->GetDecoder()->NormalizeVideoTimecode(currentFrame->timecode);
2135  }
2136  playPos -= playPosAdj;
2137  if (playPos != 0)
2138  changed |= subs->HasSubtitleChanged(playPos);
2139  if (!changed)
2140  {
2141  subs->Unlock();
2142  return;
2143  }
2144 
2147 
2148  if (playPos == 0)
2149  {
2150  subs->Unlock();
2151  return;
2152  }
2153 
2154  QStringList rawsubs = subs->GetSubtitles(playPos);
2155  if (rawsubs.empty())
2156  {
2157  subs->Unlock();
2158  return;
2159  }
2160 
2161  subs->Unlock();
2162  DrawTextSubtitles(rawsubs, 0, 0);
2163 }
2164 
2166 {
2167  if (!m_player || !m_subreader)
2168  return;
2169 
2170  uint64_t duration;
2171  QStringList subs = m_subreader->GetRawTextSubtitles(duration);
2172  if (subs.empty())
2173  return;
2174 
2176  if (!vo)
2177  return;
2178 
2179  VideoFrame *currentFrame = vo->GetLastShownFrame();
2180  if (!currentFrame)
2181  return;
2182 
2183  m_safeArea = vo->GetSafeRect();
2184 
2185  // delete old subs that may still be on screen
2188  DrawTextSubtitles(subs, currentFrame->timecode, duration);
2189 }
2190 
2191 void SubtitleScreen::DrawTextSubtitles(const QStringList &subs,
2192  uint64_t start, uint64_t duration)
2193 {
2194  auto *fsub = new FormattedTextSubtitleSRT(m_family, m_safeArea, start,
2195  duration, this, subs);
2196  m_qInited.append(fsub);
2197 }
2198 
2200 {
2201  if (!m_608reader)
2202  return;
2203 
2204  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2205 
2206  if (!m_player || !m_player->GetVideoOutput())
2207  return;
2209 
2210  CC608Buffer* textlist = m_608reader->GetOutputText(changed);
2211  if (!changed)
2212  return;
2213 
2216 
2217  if (!textlist)
2218  return;
2219 
2220  QMutexLocker locker(&textlist->m_lock);
2221 
2222  if (textlist->m_buffers.empty())
2223  return;
2224 
2225  auto *fsub = new FormattedTextSubtitle608(textlist->m_buffers, m_family,
2226  m_safeArea, this/*, m_textFontZoom*/);
2227  m_qInited.append(fsub);
2228 }
2229 
2231 {
2232  if (!m_708reader || !m_player || !m_player->GetVideoOutput())
2233  return;
2234 
2235  CC708Service *cc708service = m_708reader->GetCurrentService();
2236  float video_aspect = m_player->GetVideoAspect();
2237  QRect oldsafe = m_safeArea;
2239  bool changed = (oldsafe != m_safeArea || m_textFontZoom != m_textFontZoomPrev);
2240  if (changed)
2241  {
2242  for (int i = 0; i < k708MaxWindows; i++)
2243  cc708service->m_windows[i].SetChanged();
2244  }
2245 
2246  uint64_t clearMask = 0;
2247  QList<FormattedTextSubtitle *> addList;
2248  for (int i = 0; i < k708MaxWindows; i++)
2249  {
2250  CC708Window &win = cc708service->m_windows[i];
2251  if (win.GetExists() && win.GetVisible() && !win.GetChanged())
2252  continue;
2253  if (!win.GetChanged())
2254  continue;
2255 
2256  clearMask |= (1UL << i);
2257  win.ResetChanged();
2258  if (!win.GetExists() || !win.GetVisible())
2259  continue;
2260 
2261  QMutexLocker locker(&win.m_lock);
2262  vector<CC708String*> list = win.GetStrings();
2263  if (!list.empty())
2264  {
2265  auto *fsub = new FormattedTextSubtitle708(win, i, list, m_family,
2266  m_safeArea, this, video_aspect);
2267  addList.append(fsub);
2268  }
2270  }
2271  if (clearMask)
2272  {
2273  Clear708Cache(clearMask);
2274  }
2275  if (!addList.empty())
2276  m_qInited.append(addList);
2277 }
2278 
2279 void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
2280 {
2282  if (!vo)
2283  return;
2284 
2285  QRect scaled = vo->GetImageRect(pos);
2286  if (scaled.size() != pos.size())
2287  {
2288  img = img.scaled(scaled.width(), scaled.height(),
2289  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2290  }
2291 
2292  MythPainter *osd_painter = vo->GetOSDPainter();
2293  MythImage* image = nullptr;
2294  if (osd_painter)
2295  image = osd_painter->GetFormatImage();
2296 
2297  if (image)
2298  {
2299  image->Assign(img);
2300  MythUIImage *uiimage = new SubImage(this, "dvd_button", MythRect(scaled), 0);
2301  if (uiimage)
2302  {
2303  uiimage->SetImage(image);
2304  uiimage->SetArea(MythRect(scaled));
2305  SetElementAdded();
2306  }
2307  image->DecrRef();
2308  }
2309 }
2310 
2311 #ifdef USING_LIBASS
2312 static void myth_libass_log(int level, const char *fmt, va_list vl, void */*ctx*/)
2313 {
2314  uint64_t verbose_mask = VB_GENERAL;
2315  LogLevel_t verbose_level = LOG_INFO;
2316 
2317  switch (level)
2318  {
2319  case 0: //MSGL_FATAL
2320  verbose_level = LOG_EMERG;
2321  break;
2322  case 1: //MSGL_ERR
2323  verbose_level = LOG_ERR;
2324  break;
2325  case 2: //MSGL_WARN
2326  verbose_level = LOG_WARNING;
2327  break;
2328  case 4: //MSGL_INFO
2329  verbose_level = LOG_INFO;
2330  break;
2331  case 6: //MSGL_V
2332  case 7: //MSGL_DBG2
2333  verbose_level = LOG_DEBUG;
2334  break;
2335  default:
2336  return;
2337  }
2338 
2339  if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
2340  return;
2341 
2342  static QMutex s_stringLock;
2343  s_stringLock.lock();
2344 
2345  char str[1024];
2346  int bytes = vsnprintf(str, sizeof str, fmt, vl);
2347  // check for truncated messages and fix them
2348  int truncated = bytes - ((sizeof str)-1);
2349  if (truncated > 0)
2350  {
2351  LOG(VB_GENERAL, LOG_ERR,
2352  QString("libASS log output truncated %1 of %2 bytes written")
2353  .arg(truncated).arg(bytes));
2354  }
2355 
2356  LOG(verbose_mask, verbose_level, QString("libass: %1").arg(str));
2357  s_stringLock.unlock();
2358 }
2359 
2361 {
2362  if (m_assLibrary && m_assRenderer)
2363  return true;
2364 
2365  if (!m_assLibrary)
2366  {
2367  m_assLibrary = ass_library_init();
2368  if (!m_assLibrary)
2369  return false;
2370 
2371  ass_set_message_cb(m_assLibrary, myth_libass_log, nullptr);
2372  ass_set_extract_fonts(m_assLibrary, static_cast<int>(true));
2373  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
2374  }
2375 
2376  LoadAssFonts();
2377 
2378  if (!m_assRenderer)
2379  {
2380  m_assRenderer = ass_renderer_init(m_assLibrary);
2381  if (!m_assRenderer)
2382  return false;
2383 
2384 #ifdef Q_OS_ANDROID
2385  // fontconfig doesn't yet work for us on Android. For the
2386  // time being, more explicitly set a font we know should
2387  // exist. This was adapted from VLC master as of 2019-01-21.
2388  const char *psz_font = "/system/fonts/DroidSans.ttf";
2389  const char *psz_font_family = "Droid Sans";
2390 #else
2391  const char *psz_font = nullptr;
2392  const char *psz_font_family = "sans-serif";
2393 #endif
2394  ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1, nullptr, 1);
2395  ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2396  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
2397  }
2398 
2399  return true;
2400 }
2401 
2403 {
2404  if (!m_assLibrary || !m_player)
2405  return;
2406 
2408  if (m_assFontCount == count)
2409  return;
2410 
2411  ass_clear_fonts(m_assLibrary);
2412  m_assFontCount = 0;
2413 
2414  // TODO these need checking and/or reinitialising after a stream change
2415  for (uint i = 0; i < count; ++i)
2416  {
2417  QByteArray filename;
2418  QByteArray font;
2420  ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
2421  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
2422  .arg(filename.constData()));
2423  m_assFontCount++;
2424  }
2425 }
2426 
2428 {
2429  CleanupAssTrack();
2430 
2431  if (m_assRenderer)
2432  ass_renderer_done(m_assRenderer);
2433  m_assRenderer = nullptr;
2434 
2435  if (m_assLibrary)
2436  {
2437  ass_clear_fonts(m_assLibrary);
2438  m_assFontCount = 0;
2439  ass_library_done(m_assLibrary);
2440  }
2441  m_assLibrary = nullptr;
2442 }
2443 
2445 {
2446  if (!InitialiseAssLibrary() || !m_player)
2447  return;
2448 
2449  if (tracknum == m_assTrackNum && m_assTrack)
2450  return;
2451 
2452  LoadAssFonts();
2453  CleanupAssTrack();
2454  m_assTrack = ass_new_track(m_assLibrary);
2455  m_assTrackNum = tracknum;
2456 
2457  QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
2458  if (!header.isNull())
2459  ass_process_codec_private(m_assTrack, header.data(), header.size());
2460 
2463 }
2464 
2466 {
2467  if (m_assTrack)
2468  ass_free_track(m_assTrack);
2469  m_assTrack = nullptr;
2470 }
2471 
2473 {
2474  if (m_assTrack && event)
2475  ass_process_data(m_assTrack, event, strlen(event));
2476 }
2477 
2479 {
2480  ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
2481  ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2482  ass_set_use_margins(m_assRenderer, static_cast<int>(true));
2483  ass_set_font_scale(m_assRenderer, 1.0);
2484 }
2485 
2486 void SubtitleScreen::RenderAssTrack(uint64_t timecode)
2487 {
2488  if (!m_player || !m_assRenderer || !m_assTrack)
2489  return;
2490 
2492  if (!vo )
2493  return;
2494 
2495  QRect oldscreen = m_safeArea;
2496  m_safeArea = vo->GetMHEGBounds();
2497  if (oldscreen != m_safeArea)
2499 
2500  int changed = 0;
2501  ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack,
2502  timecode, &changed);
2503  if (!changed)
2504  return;
2505 
2506  MythPainter *osd_painter = vo->GetOSDPainter();
2507  if (!osd_painter)
2508  return;
2509 
2510  int count = 0;
2513  while (images)
2514  {
2515  if (images->w == 0 || images->h == 0)
2516  {
2517  images = images->next;
2518  continue;
2519  }
2520 
2521  uint8_t alpha = images->color & 0xFF;
2522  uint8_t blue = images->color >> 8 & 0xFF;
2523  uint8_t green = images->color >> 16 & 0xFF;
2524  uint8_t red = images->color >> 24 & 0xFF;
2525 
2526  if (alpha == 255)
2527  {
2528  images = images->next;
2529  continue;
2530  }
2531 
2532  QSize img_size(images->w, images->h);
2533  QRect img_rect(images->dst_x,images->dst_y,
2534  images->w, images->h);
2535  QImage qImage(img_size, QImage::Format_ARGB32);
2536  qImage.fill(0x00000000);
2537 
2538  unsigned char *src = images->bitmap;
2539  for (int y = 0; y < images->h; ++y)
2540  {
2541  for (int x = 0; x < images->w; ++x)
2542  {
2543  uint8_t value = src[x];
2544  if (value)
2545  {
2546  uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
2547  (red << 16) | (green << 8) | blue;
2548  qImage.setPixel(x, y, pixel);
2549  }
2550  }
2551  src += images->stride;
2552  }
2553 
2554  MythImage* image = nullptr;
2555  SubImage *uiimage = nullptr;
2556 
2557  if (osd_painter)
2558  image = osd_painter->GetFormatImage();
2559 
2560  if (image)
2561  {
2562  image->Assign(qImage);
2563  QString name = QString("asssub%1").arg(count);
2564  uiimage = new SubImage(this, name, MythRect(img_rect), 0);
2565  if (uiimage)
2566  {
2567  uiimage->SetImage(image);
2568  uiimage->SetArea(MythRect(img_rect));
2569  SetElementAdded();
2570  }
2571  image->DecrRef();
2572  }
2573  images = images->next;
2574  count++;
2575  }
2576 }
2577 #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:298
CC708Service * GetCurrentService(void)
Definition: cc708reader.h:23
class SubtitleFormat * GetSubtitleFormat(void)
CC708Window m_windows[k708MaxWindows]
Definition: cc708window.h:309
static const QString kSubAttrPixelsize("pixelsize")
QVector< MythUIType * > m_cleanup
QColor color(void) const
float GetVideoAspect(void) const
Definition: mythplayer.h:189
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)
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:125
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:450
const SubtitleScreen * m_parent
static const float LINE_SPACING
static const QString kSubAttrShadowcolor("shadowcolor")
CC708Reader * m_708reader
void SaveSetting(const QString &key, int newValue)
void InitialiseAssTrack(int tracknum)
static const QString kSubAttrOutlinesize("outlinesize")
void SetRedraw(void)
Definition: mythuitype.cpp:295
void DeleteChild(const QString &name)
Delete a named child of this UIType.
Definition: mythuitype.cpp:147
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:884
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:118
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:204
static const QString kSubProvider("provider")
CC608Reader * m_608reader
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)
virtual ~SubtitleScreen()
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:241
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:252
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
uint m_relative_pos
Definition: cc708window.h:240
static int GetTeletextBackgroundAlpha(void)
bool GetVisible(void) const
Definition: cc708window.h:277
float GetFrameRate(void) const
Definition: mythplayer.h:190
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:442
uint GetFGAlpha(void) const
Definition: cc708window.h:120
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:105
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
void SetChanged(void)
Definition: cc708window.h:291
bool GetChanged(void) const
Definition: cc708window.h:278
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
const uint k708AttrEdgeRaised
Definition: cc708window.cpp:95
uint GetBGAlpha(void) const
Definition: cc708window.h:127
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:53
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:591
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:460
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:299
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:112
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:86
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:299
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:239
const int k708MaxWindows
Definition: cc708window.h:69
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:86
bool IsFrameBasedTiming(void) const
Returns true in case the subtitle timing data is frame-based.
bool InitialiseAssLibrary(void)
void NormPoint(void)
Definition: mythrect.cpp:391
bool GetExists(void) const
Definition: cc708window.h:276
void DisplayRawTextSubtitles(void)
static int x3
Definition: mythsocket.cpp:62
virtual void Draw(void)
void DisplayAVSubtitles(void)
uint m_anchor_horizontal
Definition: cc708window.h:242
void ResetChanged(void)
Definition: cc708window.h:295
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:285
QMutex m_lock
Definition: cc608reader.h:52
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:300