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_pen_size & 0x3) == k708AttrSizeSmall)
252  scale = scale * 32 / 42;
253  else if ((attr.m_pen_size & 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_edge_type == 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_edge_type == k708AttrEdgeUniform ||
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_font_tag & 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 {
372  MythUIShape *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 *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(cc708Fonts[attr.m_font_tag & 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  MythUIType *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  MythUIType *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  MythUIShape *resultBG =
485  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  MythUIShape *testBG =
495  dynamic_cast<MythUIShape *>(negParent->GetChild(prefix));
496  if (!testBG)
497  testBG = negBG;
498  if (family == kSubFamily708 &&
499  (attr.m_font_tag & 0x7) == k708AttrFontSmallCaps)
500  resultFont->GetFace()->setCapitalization(QFont::SmallCaps);
501  m_fontMap[prefix] = resultFont;
502  m_shapeMap[prefix] = resultBG;
503  LOG(VB_VBI, LOG_DEBUG,
504  QString("providerBaseFont = %1").arg(fontToString(providerBaseFont)));
505  LOG(VB_VBI, LOG_DEBUG,
506  QString("negFont = %1").arg(fontToString(negFont)));
507  LOG(VB_VBI, LOG_DEBUG,
508  QString("resultFont = %1").arg(fontToString(resultFont)));
509  LOG(VB_VBI, LOG_DEBUG,
510  QString("testFont = %1").arg(fontToString(testFont)));
511  m_changeMap[prefix] = Diff(family, attr, resultFont, testFont,
512  resultBG, testBG);
513  QPoint offset;
514  QColor color;
515  int alpha;
516  int size;
517  resultFont->GetShadow(offset, color, alpha);
518  resultFont->GetOutline(color, size, alpha);
519  m_outlineSizeMap[prefix] = size;
520  m_shadowOffsetMap[prefix] = offset;
521  m_pixelSizeMap[prefix] = resultFont->GetFace()->pixelSize();
522 
523  delete negFont;
524 }
525 
526 QSet<QString> SubtitleFormat::Diff(const QString &family,
527  const CC708CharacterAttribute &attr,
528  MythFontProperties *font1,
529  MythFontProperties *font2,
530  MythUIShape *bg1,
531  MythUIShape *bg2)
532 {
533  bool is708 = (family == kSubFamily708);
534  QSet<QString> result;
535  QFont *face1 = font1->GetFace();
536  QFont *face2 = font2->GetFace();
537  if (face1->italic() != face2->italic())
538  result << kSubAttrItalics;
539  if (face1->weight() != face2->weight())
540  result << kSubAttrBold;
541  if (face1->underline() != face2->underline())
542  result << kSubAttrUnderline;
543  if (face1->pixelSize() != face2->pixelSize())
544  result << kSubAttrPixelsize;
545  if (font1->color() != font2->color())
546  result << kSubAttrColor;
547  if (is708 && font1->hasShadow() != font2->hasShadow())
548  {
549  result << kSubAttrShadow;
550  QPoint offset1, offset2;
551  QColor color1, color2;
552  int alpha1, alpha2;
553  font1->GetShadow(offset1, color1, alpha1);
554  font2->GetShadow(offset2, color2, alpha2);
555  if (offset1 != offset2)
556  result << kSubAttrShadowoffset;
557  if (color1 != color2)
558  result << kSubAttrShadowcolor;
559  if (alpha1 != alpha2)
560  result << kSubAttrShadowalpha;
561  }
562  if (is708 && font1->hasOutline() != font2->hasOutline())
563  {
564  result << kSubAttrOutline;
565  QColor color1, color2;
566  int size1, size2;
567  int alpha1, alpha2;
568  font1->GetOutline(color1, size1, alpha1);
569  font2->GetOutline(color2, size2, alpha2);
570  if (color1 != color2)
571  result << kSubAttrOutlinecolor;
572  if (size1 != size2)
573  result << kSubAttrOutlinesize;
574  if (alpha1 != alpha2)
575  result << kSubAttrOutlinealpha;
576  }
577  if (bg1->m_fillBrush != bg2->m_fillBrush)
578  result << kSubAttrBGfill;
579 
580  QString values = "";
581  QSet<QString>::const_iterator i = result.constBegin();
582  for (; i != result.constEnd(); ++i)
583  values += " " + (*i);
584  LOG(VB_VBI, LOG_INFO,
585  QString("Subtitle family %1 allows provider to change:%2")
586  .arg(MakePrefix(family, attr)).arg(values));
587 
588  return result;
589 }
590 
591 SubShape *
593  const QString &family,
594  const CC708CharacterAttribute &attr,
595  const MythRect &area,
596  int whichImageCache,
597  int start,
598  int duration)
599 {
600  QString prefix = MakePrefix(family, attr);
601  if (!m_shapeMap.contains(prefix))
602  Load(family, attr);
603  if (m_shapeMap[prefix]->GetAlpha() == 0)
604  return nullptr;
605  SubShape *result = new SubShape(parent, name, area, whichImageCache,
606  start + duration);
607  result->CopyFrom(m_shapeMap[prefix]);
608  if (family == kSubFamily708)
609  {
611  {
612  QBrush brush(attr.GetBGColor());
613  result->SetFillBrush(brush);
614  result->SetLinePen(QPen(brush, 0));
615  }
616  }
617  else if (family == kSubFamilyTeletext)
618  {
619  // add code here when teletextscreen.cpp is refactored
620  }
621  LOG(VB_VBI, LOG_DEBUG,
622  QString("GetBackground(prefix=%1) = "
623  "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
624  .arg(prefix).arg(result->m_type).arg(result->GetAlpha())
625  .arg(result->m_fillBrush.style())
626  .arg(srtColorString(result->m_fillBrush.color())));
627  return result;
628 }
629 
630 int SubtitleFormat::GetBackgroundAlpha(const QString &family)
631 {
632  // This is a "temporary" hack for allowing teletextscreen.cpp to get the
633  // background alpha value from osd_subtitle.xml.
634  CC708CharacterAttribute attr(false, false, false, Qt::white);
635  SubShape *bgShape = GetBackground(nullptr, "dummyName", family, attr,
636  MythRect(), -1, 0, -1);
637  return bgShape->m_fillBrush.color().alpha();
638 }
639 
641 
642 QSize FormattedTextChunk::CalcSize(float layoutSpacing) const
643 {
644  return m_parent->CalcTextSize(m_text, m_format, layoutSpacing);
645 }
646 
647 void FormattedTextChunk::CalcPadding(bool isFirst, bool isLast,
648  int &left, int &right) const
649 {
650  m_parent->CalcPadding(m_format, isFirst, isLast, left, right);
651 }
652 
654 {
655  LOG(VB_VBI, LOG_INFO,
656  QString("Attempting to split chunk '%1'").arg(m_text));
657  int lastSpace = m_text.lastIndexOf(' ', -2); // -2 to ignore trailing space
658  if (lastSpace < 0)
659  {
660  LOG(VB_VBI, LOG_INFO,
661  QString("Failed to split chunk '%1'").arg(m_text));
662  return false;
663  }
664  newChunk.m_parent = m_parent;
665  newChunk.m_format = m_format;
666  newChunk.m_text = m_text.mid(lastSpace + 1).trimmed() + ' ';
667  m_text = m_text.left(lastSpace).trimmed();
668  LOG(VB_VBI, LOG_INFO,
669  QString("Split chunk into '%1' + '%2'").arg(m_text).arg(newChunk.m_text));
670  return true;
671 }
672 
674 {
675  QString str("");
676  str += QString("fg=%1.%2 ")
678  .arg(m_format.GetFGAlpha());
679  str += QString("bg=%1.%2 ")
681  .arg(m_format.GetBGAlpha());
682  str += QString("edge=%1.%2 ")
684  .arg(m_format.m_edge_type);
685  str += QString("off=%1 pensize=%2 ")
686  .arg(m_format.m_offset)
687  .arg(m_format.m_pen_size);
688  str += QString("it=%1 ul=%2 bf=%3 ")
689  .arg(m_format.m_italics)
690  .arg(m_format.m_underline)
691  .arg(m_format.m_boldface);
692  str += QString("font=%1 ").arg(m_format.m_font_tag);
693  str += QString(" text='%1'").arg(m_text);
694  return str;
695 }
696 
697 bool FormattedTextChunk::PreRender(bool isFirst, bool isLast,
698  int &x, int y, int height)
699 {
701  if (!m_textFont)
702  return false;
703  QFontMetrics font(*(m_textFont->GetFace()));
704  // If the chunk starts with whitespace, the leading whitespace
705  // ultimately gets lost due to the text.trimmed() operation in the
706  // MythUISimpleText constructor. To compensate, we manually
707  // indent the chunk accordingly.
708  int count = 0;
709  while (count < m_text.length() && m_text.at(count) == ' ')
710  {
711  ++count;
712  }
713 #if QT_VERSION < QT_VERSION_CHECK(5,11,0)
714  int x_adjust = count * font.width(" ");
715 #else
716  int x_adjust = count * font.horizontalAdvance(" ");
717 #endif
718  int leftPadding, rightPadding;
719  CalcPadding(isFirst, isLast, leftPadding, rightPadding);
720  // Account for extra padding before the first chunk.
721  if (isFirst)
722  x += leftPadding;
723  QSize chunk_sz = CalcSize();
724  QRect bgrect(x - leftPadding, y,
725  chunk_sz.width() + leftPadding + rightPadding,
726  height);
727  // Don't draw a background behind leading spaces.
728  if (isFirst)
729  bgrect.setLeft(bgrect.left() + x_adjust);
730  m_bgShapeName = QString("subbg%1x%2@%3,%4")
731  .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
732  m_bgShapeRect = bgrect;
733  // Shift to the right to account for leading spaces that
734  // are removed by the MythUISimpleText constructor. Also
735  // add in padding at the end to avoid clipping.
736  m_textName = QString("subtxt%1x%2@%3,%4")
737  .arg(chunk_sz.width()).arg(height).arg(x).arg(y);
738  m_textRect = QRect(x + x_adjust, y,
739  chunk_sz.width() - x_adjust + rightPadding, height);
740 
741  x += chunk_sz.width();
742  return true;
743 }
744 
745 QSize FormattedTextLine::CalcSize(float layoutSpacing) const
746 {
747  int height = 0, width = 0;
748  int leftPadding = 0, rightPadding = 0;
749  QList<FormattedTextChunk>::const_iterator it;
750  bool isFirst = true;
751  for (it = chunks.constBegin(); it != chunks.constEnd(); ++it)
752  {
753  bool isLast = (it + 1 == chunks.constEnd());
754  QSize tmp = (*it).CalcSize(layoutSpacing);
755  height = max(height, tmp.height());
756  width += tmp.width();
757  (*it).CalcPadding(isFirst, isLast, leftPadding, rightPadding);
758  if (it == chunks.constBegin())
759  width += leftPadding;
760  isFirst = false;
761  }
762  return {width + rightPadding, height};
763 }
764 
765 // Normal font height is designed to be 1/20 of safe area height, with
766 // extra blank space between lines to make 17 lines within the safe
767 // area.
768 static const float LINE_SPACING = (20.0 / 17.0);
769 static const float PAD_WIDTH = 0.5;
770 static const float PAD_HEIGHT = 0.04;
771 
772 // Resolves any TBD x_indent and y_indent values in FormattedTextLine
773 // objects. Calculates m_bounds. Prunes most leading and all
774 // trailing whitespace from each line so that displaying with a black
775 // background doesn't look clumsy.
777 {
778  // Calculate dimensions of bounding rectangle
779  int anchor_width = 0;
780  int anchor_height = 0;
781  for (int i = 0; i < m_lines.size(); i++)
782  {
783  QSize sz = m_lines[i].CalcSize(LINE_SPACING);
784  anchor_width = max(anchor_width, sz.width());
785  anchor_height += sz.height();
786  }
787 
788  // Adjust the anchor point according to actual width and height
789  int anchor_x = m_xAnchor;
790  int anchor_y = m_yAnchor;
791  if (m_xAnchorPoint == 1)
792  anchor_x -= anchor_width / 2;
793  else if (m_xAnchorPoint == 2)
794  anchor_x -= anchor_width;
795  if (m_yAnchorPoint == 1)
796  anchor_y -= anchor_height / 2;
797  else if (m_yAnchorPoint == 2)
798  anchor_y -= anchor_height;
799 
800  // Shift the anchor point back into the safe area if necessary/possible.
801  anchor_y = max(0, min(anchor_y, m_safeArea.height() - anchor_height));
802  anchor_x = max(0, min(anchor_x, m_safeArea.width() - anchor_width));
803 
804  m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
805 
806  // Fill in missing coordinates
807  int y = anchor_y;
808  for (int i = 0; i < m_lines.size(); i++)
809  {
810  if (m_lines[i].m_xIndent < 0)
811  m_lines[i].m_xIndent = anchor_x;
812  if (m_lines[i].m_yIndent < 0)
813  m_lines[i].m_yIndent = y;
814  y += m_lines[i].CalcSize(LINE_SPACING).height();
815  // Prune leading all-whitespace chunks.
816  while (!m_lines[i].chunks.isEmpty() &&
817  m_lines[i].chunks.first().m_text.trimmed().isEmpty())
818  {
819  m_lines[i].m_xIndent +=
820  m_lines[i].chunks.first().CalcSize().width();
821  m_lines[i].chunks.removeFirst();
822  }
823  // Prune trailing all-whitespace chunks.
824  while (!m_lines[i].chunks.isEmpty() &&
825  m_lines[i].chunks.last().m_text.trimmed().isEmpty())
826  {
827  m_lines[i].chunks.removeLast();
828  }
829  // Trim trailing whitespace from last chunk. (Trimming
830  // leading whitespace from all chunks is handled in the Draw()
831  // routine.)
832  if (!m_lines[i].chunks.isEmpty())
833  {
834  QString *str = &m_lines[i].chunks.last().m_text;
835  int idx = str->length() - 1;
836  while (idx >= 0 && str->at(idx) == ' ')
837  --idx;
838  str->truncate(idx + 1);
839  }
840  }
841 }
842 
844 {
845  for (int i = 0; i < m_lines.size(); i++)
846  {
847  int x = m_lines[i].m_xIndent;
848  int y = m_lines[i].m_yIndent;
849  int height = m_lines[i].CalcSize().height();
850  QList<FormattedTextChunk>::iterator chunk;
851  bool isFirst = true;
852  for (chunk = m_lines[i].chunks.begin();
853  chunk != m_lines[i].chunks.end();
854  ++chunk)
855  {
856  bool isLast = (chunk + 1 == m_lines[i].chunks.end());
857  (*chunk).PreRender(isFirst, isLast, x, y, height);
858  isFirst = false;
859  }
860  }
861 }
862 
863 // Returns true if anything new was drawn, false if not. The caller
864 // should call SubtitleScreen::OptimiseDisplayedArea() if true is
865 // returned.
867 {
868  QList<MythUIType *> textList, shapeList;
869  for (int i = 0; i < m_lines.size(); i++)
870  {
871  QList<FormattedTextChunk>::const_iterator chunk;
872  for (chunk = m_lines[i].chunks.constBegin();
873  chunk != m_lines[i].chunks.constEnd();
874  ++chunk)
875  {
876  MythFontProperties *mythfont =
877  m_subScreen->GetFont((*chunk).m_format);
878  if (!mythfont)
879  continue;
880  // Note: nullptr is passed as the parent argument to the
881  // MythUI constructors so that we can control the drawing
882  // order of the children. In particular, background
883  // shapes should be added/drawn first, and text drawn on
884  // top.
885  if ((*chunk).m_textRect.width() > 0) {
886  SubSimpleText *text =
887  new SubSimpleText((*chunk).m_text, *mythfont,
888  (*chunk).m_textRect,
889  Qt::AlignLeft|Qt::AlignTop,
890  /*m_subScreen*/nullptr,
891  (*chunk).m_textName, CacheNum(),
892  m_start + m_duration);
893  textList += text;
894  }
895  if ((*chunk).m_bgShapeRect.width() > 0) {
897  GetBackground(/*m_subScreen*/nullptr,
898  (*chunk).m_bgShapeName,
899  m_base, (*chunk).m_format,
900  MythRect((*chunk).m_bgShapeRect), CacheNum(),
902  if (bgshape)
903  {
904  bgshape->SetArea(MythRect((*chunk).m_bgShapeRect));
905  shapeList += bgshape;
906  }
907  }
908  }
909  }
910  while (!shapeList.isEmpty())
911  m_subScreen->AddChild(shapeList.takeFirst());
912  while (!textList.isEmpty())
913  m_subScreen->AddChild(textList.takeFirst());
914 }
915 
916 QStringList FormattedTextSubtitle::ToSRT(void) const
917 {
918  QStringList result;
919  for (int i = 0; i < m_lines.size(); i++)
920  {
921  QString line;
922  if (m_lines[i].m_origX > 0)
923  line.fill(' ', m_lines[i].m_origX);
924  QList<FormattedTextChunk>::const_iterator chunk;
925  for (chunk = m_lines[i].chunks.constBegin();
926  chunk != m_lines[i].chunks.constEnd();
927  ++chunk)
928  {
929  const QString &text = (*chunk).m_text;
930  const CC708CharacterAttribute &attr = (*chunk).m_format;
931  bool isBlank = !attr.m_underline && text.trimmed().isEmpty();
932  if (!isBlank)
933  {
934  if (attr.m_boldface)
935  line += "<b>";
936  if (attr.m_italics)
937  line += "<i>";
938  if (attr.m_underline)
939  line += "<u>";
940  if (attr.GetFGColor() != Qt::white)
941  line += QString("<font color=\"%1\">")
942  .arg(srtColorString(attr.GetFGColor()));
943  }
944  line += text;
945  if (!isBlank)
946  {
947  if (attr.GetFGColor() != Qt::white)
948  line += QString("</font>");
949  if (attr.m_underline)
950  line += "</u>";
951  if (attr.m_italics)
952  line += "</i>";
953  if (attr.m_boldface)
954  line += "</b>";
955  }
956  }
957  if (!line.trimmed().isEmpty())
958  result += line;
959  }
960  return result;
961 }
962 
963 void FormattedTextSubtitleSRT::Init(const QStringList &subs)
964 {
965  // Does a simplistic parsing of HTML tags from the strings.
966  // Nesting is not implemented. Stray whitespace may cause
967  // legitimate tags to be ignored. All other HTML tags are
968  // stripped and ignored.
969  //
970  // <i> - enable italics
971  // </i> - disable italics
972  // <b> - enable boldface
973  // </b> - disable boldface
974  // <u> - enable underline
975  // </u> - disable underline
976  // <font color="#xxyyzz"> - change font color
977  // </font> - reset font color to white
978 
979  int pixelSize = m_safeArea.height() / 20;
980  if (m_subScreen)
981  m_subScreen->SetFontSize(pixelSize);
982  m_xAnchorPoint = 1; // center
983  m_yAnchorPoint = 2; // bottom
984  m_xAnchor = m_safeArea.width() / 2;
985  m_yAnchor = m_safeArea.height();
986 
987  bool isItalic = false;
988  bool isBold = false;
989  bool isUnderline = false;
990  QColor color(Qt::white);
991  QRegExp htmlTag("</?.+>");
992  QString htmlPrefix("<font color=\""), htmlSuffix("\">");
993  htmlTag.setMinimal(true);
994  foreach (QString subtitle, subs)
995  {
996  FormattedTextLine line;
997  QString text(subtitle);
998  while (!text.isEmpty())
999  {
1000  int pos = text.indexOf(htmlTag);
1001  if (pos != 0) // don't add a zero-length string
1002  {
1003  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1004  color);
1005  FormattedTextChunk chunk(text.left(pos), attr, m_subScreen);
1006  line.chunks += chunk;
1007  text = (pos < 0 ? "" : text.mid(pos));
1008  LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
1009  .arg(chunk.ToLogString()));
1010  }
1011  if (pos >= 0)
1012  {
1013  int htmlLen = htmlTag.matchedLength();
1014  QString html = text.left(htmlLen).toLower();
1015  text = text.mid(htmlLen);
1016  if (html == "<i>")
1017  isItalic = true;
1018  else if (html == "</i>")
1019  isItalic = false;
1020  else if (html.startsWith(htmlPrefix) &&
1021  html.endsWith(htmlSuffix))
1022  {
1023  int colorLen = html.length() -
1024  (htmlPrefix.length() + htmlSuffix.length());
1025  QString colorString(
1026  html.mid(htmlPrefix.length(), colorLen));
1027  QColor newColor(colorString);
1028  if (newColor.isValid())
1029  {
1030  color = newColor;
1031  }
1032  else
1033  {
1034  LOG(VB_VBI, LOG_INFO,
1035  QString("Ignoring invalid SRT color specification: "
1036  "'%1'").arg(colorString));
1037  }
1038  }
1039  else if (html == "</font>")
1040  color = Qt::white;
1041  else if (html == "<b>")
1042  isBold = true;
1043  else if (html == "</b>")
1044  isBold = false;
1045  else if (html == "<u>")
1046  isUnderline = true;
1047  else if (html == "</u>")
1048  isUnderline = false;
1049  else
1050  {
1051  LOG(VB_VBI, LOG_INFO,
1052  QString("Ignoring unknown SRT formatting: '%1'")
1053  .arg(html));
1054  }
1055 
1056  LOG(VB_VBI, LOG_INFO,
1057  QString("SRT formatting change '%1', "
1058  "new ital=%2 bold=%3 uline=%4 color=%5)")
1059  .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1060  .arg(srtColorString(color)));
1061  }
1062  }
1063  m_lines += line;
1064  }
1065 }
1066 
1068 {
1069  int maxWidth = m_safeArea.width();
1070  for (int i = 0; i < m_lines.size(); i++)
1071  {
1072  int width = m_lines[i].CalcSize().width();
1073  // Move entire chunks to the next line as necessary. Leave at
1074  // least one chunk on the current line.
1075  while (width > maxWidth && m_lines[i].chunks.size() > 1)
1076  {
1077  width -= m_lines[i].chunks.back().CalcSize().width();
1078  // Make sure there's a next line to wrap into.
1079  if (m_lines.size() == i + 1)
1080  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1081  m_lines[i].m_yIndent);
1082  m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
1083  LOG(VB_VBI, LOG_INFO,
1084  QString("Wrapping chunk to next line: '%1'")
1085  .arg(m_lines[i+1].chunks[0].m_text));
1086  }
1087  // Split the last chunk until width is small enough or until
1088  // no more splits are possible.
1089  bool isSplitPossible = true;
1090  while (width > maxWidth && isSplitPossible)
1091  {
1092  FormattedTextChunk newChunk;
1093  isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
1094  if (isSplitPossible)
1095  {
1096  // Make sure there's a next line to split into.
1097  if (m_lines.size() == i + 1)
1098  m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1099  m_lines[i].m_yIndent);
1100  m_lines[i+1].chunks.prepend(newChunk);
1101  width = m_lines[i].CalcSize().width();
1102  }
1103  }
1104  }
1105 }
1106 
1112 static QString extract_cc608(QString &text, int &color,
1113  bool &isItalic, bool &isUnderline)
1114 {
1115  QString result;
1116  QString orig(text);
1117 
1118  // Handle an initial control sequence.
1119  if (text.length() >= 1 && text[0] >= 0x7000)
1120  {
1121  int op = text[0].unicode() - 0x7000;
1122  isUnderline = ((op & 0x1) != 0);
1123  switch (op & ~1)
1124  {
1125  case 0x0e:
1126  // color unchanged
1127  isItalic = true;
1128  break;
1129  case 0x1e:
1130  color = op >> 1;
1131  isItalic = true;
1132  break;
1133  case 0x20:
1134  // color unchanged
1135  // italics unchanged
1136  break;
1137  default:
1138  color = (op & 0xf) >> 1;
1139  isItalic = false;
1140  break;
1141  }
1142  text = text.mid(1);
1143  }
1144 
1145  // Copy the string into the result, up to the next control
1146  // character.
1147  int nextControl = text.indexOf(QRegExp("[\\x7000-\\x7fff]"));
1148  if (nextControl < 0)
1149  {
1150  result = text;
1151  text.clear();
1152  }
1153  else
1154  {
1155  result = text.left(nextControl);
1156  text = text.mid(nextControl);
1157  }
1158 
1159  return result;
1160 }
1161 
1162 // Adjusts the Y coordinates to avoid overlap, which could happen as a
1163 // result of a large text zoom factor. Then, if the total height
1164 // exceeds the safe area, compresses each piece of vertical blank
1165 // space proportionally to make it fit.
1167 {
1168  int i;
1169  int totalHeight = 0;
1170  int totalSpace = 0;
1171  int firstY = 0;
1172  int prevY = 0;
1173  QVector<int> heights(m_lines.size());
1174  QVector<int> spaceBefore(m_lines.size());
1175  // Calculate totalHeight and totalSpace
1176  for (i = 0; i < m_lines.size(); i++)
1177  {
1178  m_lines[i].m_yIndent = max(m_lines[i].m_yIndent, prevY); // avoid overlap
1179  int y = m_lines[i].m_yIndent;
1180  if (i == 0)
1181  firstY = prevY = y;
1182  int height = m_lines[i].CalcSize().height();
1183  heights[i] = height;
1184  spaceBefore[i] = y - prevY;
1185  totalSpace += (y - prevY);
1186  prevY = y + height;
1187  totalHeight += height;
1188  }
1189  int safeHeight = m_safeArea.height();
1190  int overage = min(totalHeight - safeHeight, totalSpace);
1191 
1192  // Recalculate Y coordinates, applying the shrink factor to space
1193  // between each line.
1194  if (overage > 0 && totalSpace > 0)
1195  {
1196  float shrink = (totalSpace - overage) / (float)totalSpace;
1197  prevY = firstY;
1198  for (i = 0; i < m_lines.size(); i++)
1199  {
1200  m_lines[i].m_yIndent = prevY + spaceBefore[i] * shrink;
1201  prevY = m_lines[i].m_yIndent + heights[i];
1202  }
1203  }
1204 
1205  // Shift Y coordinates back up into the safe area.
1206  int shift = min(firstY, max(0, prevY - safeHeight));
1207  for (i = 0; i < m_lines.size(); i++)
1208  m_lines[i].m_yIndent -= shift;
1209 
1211 }
1212 
1213 void FormattedTextSubtitle608::Init(const vector<CC608Text*> &buffers)
1214 {
1215  static const QColor clr[8] =
1216  {
1217  Qt::white, Qt::green, Qt::blue, Qt::cyan,
1218  Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1219  };
1220 
1221  if (buffers.empty())
1222  return;
1223  vector<CC608Text*>::const_iterator i = buffers.begin();
1224  int xscale = 36;
1225  int yscale = 17;
1226  int pixelSize = m_safeArea.height() / (yscale * LINE_SPACING);
1227  int fontwidth = 0;
1228  int xmid = 0;
1229  int zoom = 100;
1230  if (m_subScreen)
1231  {
1232  zoom = m_subScreen->GetZoom();
1233  m_subScreen->SetFontSize(pixelSize);
1234  CC708CharacterAttribute def_attr(false, false, false, clr[0]);
1235  QFont *font = m_subScreen->GetFont(def_attr)->GetFace();
1236  QFontMetrics fm(*font);
1237  fontwidth = fm.averageCharWidth();
1238  xmid = m_safeArea.width() / 2;
1239  // Disable centering for zoom factor >= 100%
1240  if (zoom >= 100)
1241  xscale = m_safeArea.width() / fontwidth;
1242  }
1243 
1244  for (; i != buffers.end(); ++i)
1245  {
1246  CC608Text *cc = (*i);
1247  int color = 0;
1248  bool isItalic = false, isUnderline = false;
1249  const bool isBold = false;
1250  QString text(cc->text);
1251 
1252  int orig_x = cc->x;
1253  // position as if we use a fixed size font
1254  // - font size already has zoom factor applied
1255 
1256  int x;
1257  if (xmid)
1258  // center horizontally
1259  x = xmid + (orig_x - xscale / 2) * fontwidth;
1260  else
1261  // fallback
1262  x = (orig_x + 3) * m_safeArea.width() / xscale;
1263 
1264  int orig_y = cc->y;
1265  int y;
1266  if (orig_y < yscale / 2)
1267  // top half -- anchor up
1268  y = (orig_y * m_safeArea.height() * zoom / (yscale * 100));
1269  else
1270  // bottom half -- anchor down
1271  y = m_safeArea.height() -
1272  ((yscale - orig_y - 0.5) * m_safeArea.height() * zoom /
1273  (yscale * 100));
1274 
1275  FormattedTextLine line(x, y, orig_x, orig_y);
1276  while (!text.isNull())
1277  {
1278  QString captionText =
1279  extract_cc608(text, color, isItalic, isUnderline);
1280  CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1281  clr[min(max(0, color), 7)]);
1282  FormattedTextChunk chunk(captionText, attr, m_subScreen);
1283  line.chunks += chunk;
1284  LOG(VB_VBI, LOG_INFO,
1285  QString("Adding cc608 chunk (%1,%2): %3")
1286  .arg(cc->x).arg(cc->y).arg(chunk.ToLogString()));
1287  }
1288  m_lines += line;
1289  }
1290 }
1291 
1293 {
1294  // Draw the window background after calculating bounding rectangle
1295  // in Layout()
1296  if (m_bgFillAlpha) // TODO border?
1297  {
1298  QBrush fill(m_bgFillColor, Qt::SolidPattern);
1299  SubShape *shape =
1300  new SubShape(m_subScreen, QString("cc708bg%1").arg(m_num),
1302  shape->SetFillBrush(fill);
1303  shape->SetArea(MythRect(m_bounds));
1304  }
1305  // The background is drawn first so that it appears behind
1306  // everything else.
1308 }
1309 
1311  const vector<CC708String*> &list,
1312  float aspect)
1313 {
1314  LOG(VB_VBI, LOG_DEBUG,LOC +
1315  QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1316  "relative %5")
1317  .arg(m_num).arg(win.m_anchor_point).arg(win.m_anchor_horizontal)
1318  .arg(win.m_anchor_vertical).arg(win.m_relative_pos));
1319  int pixelSize = m_safeArea.height() / 20;
1320  if (m_subScreen)
1321  m_subScreen->SetFontSize(pixelSize);
1322 
1323  float xrange = win.m_relative_pos ? 100.0F :
1324  (aspect > 1.4F) ? 210.0F : 160.0F;
1325  float yrange = win.m_relative_pos ? 100.0F : 75.0F;
1326  float xmult = (float)m_safeArea.width() / xrange;
1327  float ymult = (float)m_safeArea.height() / yrange;
1328  uint anchor_x = (uint)(xmult * (float)win.m_anchor_horizontal);
1329  uint anchor_y = (uint)(ymult * (float)win.m_anchor_vertical);
1330  m_xAnchorPoint = win.m_anchor_point % 3;
1331  m_yAnchorPoint = win.m_anchor_point / 3;
1332  m_xAnchor = anchor_x;
1333  m_yAnchor = anchor_y;
1334 
1335  for (size_t i = 0; i < list.size(); i++)
1336  {
1337  if (list[i]->y >= (uint)m_lines.size())
1338  m_lines.resize(list[i]->y + 1);
1339  FormattedTextChunk chunk(list[i]->str, list[i]->attr, m_subScreen);
1340  m_lines[list[i]->y].chunks += chunk;
1341  LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
1342  .arg(m_num).arg(list[i]->y).arg(chunk.ToLogString()));
1343  }
1344 }
1345 
1347 
1349  int fontStretch) :
1350  MythScreenType((MythScreenType*)nullptr, name),
1351  m_player(player),
1352  m_fontStretch(fontStretch),
1353  m_format(new SubtitleFormat)
1354 {
1355  m_removeHTML.setMinimal(true);
1356 }
1357 
1359 {
1361  delete m_format;
1362 #ifdef USING_LIBASS
1364 #endif
1365 }
1366 
1367 void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
1368 {
1369  m_subtitleType = type;
1370 
1371  if (forced_only)
1372  {
1374  SetVisible(true);
1375  SetArea(MythRect());
1376  return;
1377  }
1378 
1379  if (m_subreader)
1380  {
1384  }
1385  if (m_608reader)
1387  if (m_708reader)
1391  SetArea(MythRect());
1392  switch (m_subtitleType)
1393  {
1394  case kDisplayTextSubtitle:
1397  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1398  break;
1399  case kDisplayCC608:
1401  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1402  break;
1403  case kDisplayCC708:
1405  m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1406  break;
1407  case kDisplayAVSubtitle:
1409  m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
1410  break;
1411  }
1414 }
1415 
1417 {
1419  return;
1421  SetVisible(false);
1422  SetArea(MythRect());
1423 }
1424 
1426 {
1429 #ifdef USING_LIBASS
1430  if (m_assTrack)
1431  ass_flush_events(m_assTrack);
1432 #endif
1433 }
1434 
1436 {
1442  m_608reader->ClearBuffers(true, true);
1445 }
1446 
1448 {
1451 }
1452 
1453 void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
1454 {
1455  if (!dvdButton || !m_player)
1456  return;
1457 
1459  if (!vo)
1460  return;
1461 
1464 
1465  float tmp = 0.0;
1466  QRect dummy;
1467  vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1468 
1469  AVSubtitleRect *hl_button = dvdButton->rects[0];
1470  uint h = hl_button->h;
1471  uint w = hl_button->w;
1472  QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1473  QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1474  uint32_t *bgpalette = (uint32_t *)(hl_button->data[1]);
1475 
1476  QVector<uint32_t> bg_palette(4);
1477  for (int i = 0; i < 4; i++)
1478  bg_palette[i] = bgpalette[i];
1479  bg_image.setColorTable(bg_palette);
1480 
1481  // copy button region of background image
1482  const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1483  QImage fg_image = bg_image.copy(fg_rect);
1484  QVector<uint32_t> fg_palette(4);
1485  uint32_t *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1486  if (fgpalette)
1487  {
1488  for (int i = 0; i < 4; i++)
1489  fg_palette[i] = fgpalette[i];
1490  fg_image.setColorTable(fg_palette);
1491  }
1492 
1493  bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1494  fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1495 
1496  // set pixel of highlight area to highlight color
1497  for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1498  {
1499  if ((x < 0) || (x > hl_button->w))
1500  continue;
1501  for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1502  {
1503  if ((y < 0) || (y > hl_button->h))
1504  continue;
1505  bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1506  }
1507  }
1508 
1509  AddScaledImage(bg_image, rect);
1510 }
1511 
1512 void SubtitleScreen::SetZoom(int percent)
1513 {
1514  m_textFontZoom = percent;
1515  if (m_family == kSubFamilyAV)
1516  gCoreContext->SaveSetting("OSDAVSubZoom", percent);
1517  else
1518  gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
1519 }
1520 
1522 {
1523  return m_textFontZoom;
1524 }
1525 
1527 {
1528  m_textFontDelayMs = ms;
1529 }
1530 
1532 {
1533  return m_textFontDelayMs;
1534 }
1535 
1537 {
1538  QList<MythUIType *> list = m_ChildrenList;
1539  QList<MythUIType *>::iterator it;
1540  for (it = list.begin(); it != list.end(); ++it)
1541  {
1542  MythUIType *child = *it;
1543  SubWrapper *wrapper = dynamic_cast<SubWrapper *>(child);
1544  if (wrapper)
1545  {
1546  int whichImageCache = wrapper->GetWhichImageCache();
1547  if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1548  {
1550  DeleteChild(child);
1551  }
1552  }
1553  }
1554 }
1555 
1556 // SetElementAdded() should be called after a new element is added to
1557 // the subtitle screen.
1559 {
1560  m_refreshModified = true;
1561 }
1562 
1563 // SetElementResized() should be called after a subtitle screen
1564 // element's size is changed.
1566 {
1567  SetElementAdded();
1568 }
1569 
1570 // SetElementAdded() should be called *before* an element is deleted
1571 // from the subtitle screen.
1573 {
1574  if (!m_refreshDeleted)
1575  SetRedraw();
1576  m_refreshDeleted = true;
1577 }
1578 
1579 // The QFontMetrics class does not account for the MythFontProperties
1580 // shadow and offset properties. This method calculates the
1581 // additional padding to the right and below that is needed for proper
1582 // bounding box computation.
1584 {
1585  QColor color;
1586  int alpha;
1587  int outlineSize = 0;
1588  int shadowWidth = 0, shadowHeight = 0;
1589  if (mythfont->hasOutline())
1590  {
1591  mythfont->GetOutline(color, outlineSize, alpha);
1592  MythPoint outline(outlineSize, outlineSize);
1593  outline.NormPoint();
1594  outlineSize = outline.x();
1595  }
1596  if (mythfont->hasShadow())
1597  {
1598  MythPoint shadowOffset;
1599  mythfont->GetShadow(shadowOffset, color, alpha);
1600  shadowOffset.NormPoint();
1601  shadowWidth = abs(shadowOffset.x());
1602  shadowHeight = abs(shadowOffset.y());
1603  // Shadow and outline overlap, so don't just add them.
1604  shadowWidth = max(shadowWidth, outlineSize);
1605  shadowHeight = max(shadowHeight, outlineSize);
1606  }
1607  return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1608 }
1609 
1610 QSize SubtitleScreen::CalcTextSize(const QString &text,
1611  const CC708CharacterAttribute &format,
1612  float layoutSpacing) const
1613 {
1614  MythFontProperties *mythfont = GetFont(format);
1615  QFont *font = mythfont->GetFace();
1616  QFontMetrics fm(*font);
1617 #if QT_VERSION < QT_VERSION_CHECK(5,11,0)
1618  int width = fm.width(text);
1619 #else
1620  int width = fm.horizontalAdvance(text);
1621 #endif
1622  int height = fm.height() * (1 + PAD_HEIGHT);
1623  if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1624  height = max(height, (int)(font->pixelSize() * layoutSpacing));
1625  height += CalcShadowOffsetPadding(mythfont).height();
1626  return {width, height};
1627 }
1628 
1629 // Padding calculation is different depending on whether the padding
1630 // is on the left side or the right side of the text. Padding on the
1631 // right needs to add the shadow and offset padding.
1633  bool isFirst, bool isLast,
1634  int &left, int &right) const
1635 {
1636  MythFontProperties *mythfont = GetFont(format);
1637  QFont *font = mythfont->GetFace();
1638  QFontMetrics fm(*font);
1639  int basicPadding = fm.maxWidth() * PAD_WIDTH;
1640  left = isFirst ? basicPadding : 0;
1641  right = CalcShadowOffsetPadding(mythfont).width() +
1642  (isLast ? basicPadding : 0);
1643 }
1644 
1647 {
1648  return m_format->GetFont(m_family, attr, m_fontSize,
1650 }
1651 
1653 {
1654  SubtitleFormat format;
1655  CC708CharacterAttribute attr(false, false, false, Qt::white);
1656  MythFontProperties *mythfont =
1657  format.GetFont(kSubFamilyTeletext, attr,
1658  /*pixelsize*/20, /*zoom*/100, /*stretch*/100);
1659  return mythfont->face().family();
1660 }
1661 
1663 {
1664  SubtitleFormat format;
1665  return format.GetBackgroundAlpha(kSubFamilyTeletext);
1666 }
1667 
1669 {
1670  if (!m_player)
1671  return false;
1672 
1676  if (!m_subreader)
1677  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
1678  if (!m_608reader)
1679  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
1680  if (!m_708reader)
1681  LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
1682 
1683  return true;
1684 }
1685 
1687 {
1688  QList<MythUIType *>::iterator it, itNext;
1689 
1690  VideoOutput *videoOut = m_player->GetVideoOutput();
1691  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1692  long long now = currentFrame ? currentFrame->timecode : LLONG_MAX;
1693  bool needRescale = (m_textFontZoom != m_textFontZoomPrev);
1694 
1695  for (it = m_ChildrenList.begin(); it != m_ChildrenList.end(); it = itNext)
1696  {
1697  itNext = it + 1;
1698  MythUIType *child = *it;
1699  SubWrapper *wrapper = dynamic_cast<SubWrapper *>(child);
1700  if (!wrapper)
1701  continue;
1702 
1703  // Expire the subtitle object if needed.
1704  long long expireTime = wrapper->GetExpireTime();
1705  if (expireTime > 0 && expireTime < now)
1706  {
1707  DeleteChild(child);
1709  continue;
1710  }
1711 
1712  // Rescale the AV subtitle image if the zoom changed.
1713  if (expireTime > 0 && needRescale)
1714  {
1715  SubImage *image = dynamic_cast<SubImage *>(child);
1716  if (image)
1717  {
1718  double factor = m_textFontZoom / (double)m_textFontZoomPrev;
1719  QSize size = image->GetImage()->size();
1720  size *= factor;
1721  image->GetImage()->Resize(size);
1723  }
1724  }
1725  }
1726 
1727  DisplayAVSubtitles(); // allow forced subtitles to work
1728 
1731  else if (kDisplayCC608 == m_subtitleType)
1733  else if (kDisplayCC708 == m_subtitleType)
1737  while (!m_qInited.isEmpty())
1738  {
1739  FormattedTextSubtitle *fsub = m_qInited.takeFirst();
1740  fsub->WrapLongLines();
1741  fsub->Layout();
1742  fsub->PreRender();
1743  fsub->Draw();
1744  delete fsub;
1745  SetElementAdded();
1746  }
1747 
1753 }
1754 
1756 {
1757  m_refreshModified = false;
1758  m_refreshDeleted = false;
1759 }
1760 
1762 {
1763  if (!m_refreshModified)
1764  return;
1765 
1766  QRegion visible;
1767  QListIterator<MythUIType *> i(m_ChildrenList);
1768  while (i.hasNext())
1769  {
1770  MythUIType *img = i.next();
1771  SubWrapper *wrapper = dynamic_cast<SubWrapper *>(img);
1772  if (wrapper && img->IsVisible())
1773  visible = visible.united(wrapper->GetOrigArea());
1774  }
1775 
1776  if (visible.isEmpty())
1777  return;
1778 
1779  QRect bounding = visible.boundingRect();
1780  bounding = bounding.translated(m_safeArea.topLeft());
1781  bounding = m_safeArea.intersected(bounding);
1782  int left = m_safeArea.left() - bounding.left();
1783  int top = m_safeArea.top() - bounding.top();
1784  SetArea(MythRect(bounding));
1785 
1786  i.toFront();
1787  while (i.hasNext())
1788  {
1789  MythUIType *img = i.next();
1790  SubWrapper *wrapper = dynamic_cast<SubWrapper *>(img);
1791  if (wrapper && img->IsVisible())
1792  img->SetArea(wrapper->GetOrigArea().translated(left, top));
1793  }
1794 }
1795 
1797 {
1798  if (!m_player || !m_subreader)
1799  return;
1800 
1802  QMutexLocker lock(&(subs->m_lock));
1803  if (subs->m_buffers.empty() && (kDisplayAVSubtitle != m_subtitleType))
1804  return;
1805 
1806  VideoOutput *videoOut = m_player->GetVideoOutput();
1807  VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1808 
1809  if (!currentFrame || !videoOut)
1810  return;
1811 
1812  float tmp = 0.0;
1813  QRect dummy;
1814  videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1815 
1816  while (!subs->m_buffers.empty())
1817  {
1818  const AVSubtitle subtitle = subs->m_buffers.front();
1819  if (subtitle.start_display_time > currentFrame->timecode)
1820  break;
1821 
1822  long long displayfor = subtitle.end_display_time -
1823  subtitle.start_display_time;
1824  if (displayfor == 0)
1825  displayfor = 60000;
1826  displayfor = (displayfor < 50) ? 50 : displayfor;
1827  long long late = currentFrame->timecode -
1828  subtitle.start_display_time;
1829 
1831  subs->m_buffers.pop_front();
1832  for (std::size_t i = 0; i < subtitle.num_rects; ++i)
1833  {
1834  AVSubtitleRect* rect = subtitle.rects[i];
1835 
1836  bool displaysub = true;
1837  if (!subs->m_buffers.empty() &&
1838  subs->m_buffers.front().end_display_time <
1839  currentFrame->timecode)
1840  {
1841  displaysub = false;
1842  }
1843 
1844  if (displaysub && rect->type == SUBTITLE_BITMAP)
1845  {
1846  QRect display(rect->display_x, rect->display_y,
1847  rect->display_w, rect->display_h);
1848 
1849  // XSUB and some DVD/DVB subs are based on the original video
1850  // size before the video was converted. We need to guess the
1851  // original size and allow for the difference
1852 
1853  int right = rect->x + rect->w;
1854  int bottom = rect->y + rect->h;
1855  if (subs->m_fixPosition || (currentFrame->height < bottom) ||
1856  (currentFrame->width < right) ||
1857  !display.width() || !display.height())
1858  {
1859  int sd_height = 576;
1860  if ((m_player->GetFrameRate() > 26.0F ||
1861  m_player->GetFrameRate() < 24.0F) && bottom <= 480)
1862  sd_height = 480;
1863  int height = ((currentFrame->height <= sd_height) &&
1864  (bottom <= sd_height)) ? sd_height :
1865  ((currentFrame->height <= 720) && bottom <= 720)
1866  ? 720 : 1080;
1867  int width = ((currentFrame->width <= 720) &&
1868  (right <= 720)) ? 720 :
1869  ((currentFrame->width <= 1280) &&
1870  (right <= 1280)) ? 1280 : 1920;
1871  display = QRect(0, 0, width, height);
1872  }
1873 
1874  // split into upper/lower to allow zooming
1875  QRect bbox;
1876  int uh = display.height() / 2 - rect->y;
1877  int lh;
1878  long long displayuntil = currentFrame->timecode + displayfor;
1879  if (uh > 0)
1880  {
1881  bbox = QRect(0, 0, rect->w, uh);
1882  uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
1883  subtitle.forced,
1884  QString("avsub%1t").arg(i),
1885  displayuntil, late);
1886  }
1887  else
1888  uh = 0;
1889  lh = rect->h - uh;
1890  if (lh > 0)
1891  {
1892  bbox = QRect(0, uh, rect->w, lh);
1893  DisplayScaledAVSubtitles(rect, bbox, false, display,
1894  subtitle.forced,
1895  QString("avsub%1b").arg(i),
1896  displayuntil, late);
1897  }
1898  }
1899 #ifdef USING_LIBASS
1900  else if (displaysub && rect->type == SUBTITLE_ASS)
1901  {
1903  AddAssEvent(rect->ass);
1904  }
1905 #endif
1906  }
1907  m_subreader->FreeAVSubtitle(subtitle);
1908  }
1909 #ifdef USING_LIBASS
1910  RenderAssTrack(currentFrame->timecode);
1911 #endif
1912 }
1913 
1914 int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
1915  QRect &bbox, bool top,
1916  QRect &display, int forced,
1917  const QString& imagename,
1918  long long displayuntil,
1919  long long late)
1920 {
1921  // split image vertically if it spans middle of display
1922  // - split point is empty line nearest the middle
1923  // crop image to reduce scaling time
1924  int xmin, xmax, ymin, ymax;
1925  int ylast, ysplit;
1926  bool prev_empty = false;
1927 
1928  // initialize to opposite edges
1929  xmin = bbox.right();
1930  xmax = bbox.left();
1931  ymin = bbox.bottom();
1932  ymax = bbox.top();
1933  ylast = bbox.top();
1934  ysplit = bbox.bottom();
1935 
1936  // find bounds of active image
1937  for (int y = bbox.top(); y <= bbox.bottom(); ++y)
1938  {
1939  if (y >= rect->h)
1940  {
1941  // end of image
1942  if (!prev_empty)
1943  ylast = y;
1944  break;
1945  }
1946 
1947  bool empty = true;
1948  for (int x = bbox.left(); x <= bbox.right(); ++x)
1949  {
1950  const uint8_t color =
1951  rect->data[0][y * rect->linesize[0] + x];
1952  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
1953  if (pixel & 0xff000000)
1954  {
1955  empty = false;
1956  if (x < xmin)
1957  xmin = x;
1958  if (x > xmax)
1959  xmax = x;
1960  }
1961  }
1962 
1963  if (!empty)
1964  {
1965  if (y < ymin)
1966  ymin = y;
1967  if (y > ymax)
1968  ymax = y;
1969  }
1970  else if (!prev_empty)
1971  {
1972  // remember uppermost empty line
1973  ylast = y;
1974  }
1975  prev_empty = empty;
1976  }
1977 
1978  if (ymax <= ymin)
1979  return 0;
1980 
1981  if (top)
1982  {
1983  if (ylast < ymin)
1984  // no empty lines
1985  return 0;
1986 
1987  if (ymax == bbox.bottom())
1988  {
1989  ymax = ylast;
1990  ysplit = ylast;
1991  }
1992  }
1993 
1994  // set new bounds
1995  bbox.setLeft(xmin);
1996  bbox.setRight(xmax);
1997  bbox.setTop(ymin);
1998  bbox.setBottom(ymax);
1999 
2000  // copy active region
2001  // AVSubtitleRect's image data's not guaranteed to be 4 byte
2002  // aligned.
2003 
2004  QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2005 
2006  QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2007  for (int y = 0; y < bbox.height(); ++y)
2008  {
2009  int ysrc = y + bbox.top();
2010  for (int x = 0; x < bbox.width(); ++x)
2011  {
2012  int xsrc = x + bbox.left();
2013  const uint8_t color =
2014  rect->data[0][ysrc * rect->linesize[0] + xsrc];
2015  const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
2016  qImage.setPixel(x, y, pixel);
2017  }
2018  }
2019 
2020  // translate to absolute coordinates
2021  bbox.translate(rect->x, rect->y);
2022 
2023  // scale and move according to zoom factor
2024  bbox.setWidth(bbox.width() * m_textFontZoom / 100);
2025  bbox.setHeight(bbox.height() * m_textFontZoom / 100);
2026 
2027  VideoOutput *videoOut = m_player->GetVideoOutput();
2028  QRect scaled = videoOut->GetImageRect(bbox, &display);
2029 
2030  if (scaled.size() != orig_rect.size())
2031  qImage = qImage.scaled(scaled.width(), scaled.height(),
2032  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2033 
2034  int hsize = m_safeArea.width();
2035  int vsize = m_safeArea.height();
2036 
2037  scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 +
2038  m_textFontZoom * scaled.left()) /
2039  100);
2040  if (top)
2041  // anchor up
2042  scaled.moveTop(scaled.top() * m_textFontZoom / 100);
2043  else
2044  // anchor down
2045  scaled.moveTop(((100 - m_textFontZoom) * vsize +
2046  m_textFontZoom * scaled.top()) /
2047  100);
2048 
2049 
2050  MythPainter *osd_painter = videoOut->GetOSDPainter();
2051  MythImage *image = nullptr;
2052  if (osd_painter)
2053  image = osd_painter->GetFormatImage();
2054 
2055  SubImage *uiimage = nullptr;
2056  if (image)
2057  {
2058  image->Assign(qImage);
2059  uiimage = new SubImage(this, imagename,
2060  MythRect(scaled), displayuntil);
2061  if (uiimage)
2062  {
2063  uiimage->SetImage(image);
2064  uiimage->SetArea(MythRect(scaled));
2065  SetElementAdded();
2066  }
2067  image->DecrRef();
2068  image = nullptr;
2069  }
2070  if (uiimage)
2071  {
2072  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2073  QString("Display %1AV sub until %2ms")
2074  .arg(forced ? "FORCED " : "")
2075  .arg(displayuntil));
2076  if (late > 50)
2077  LOG(VB_PLAYBACK, LOG_INFO, LOC +
2078  QString("AV Sub was %1ms late").arg(late));
2079  }
2080 
2081  return (ysplit + 1);
2082 }
2083 
2085 {
2086  if (!m_player || !m_subreader)
2087  return;
2088 
2089  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2090  changed |= (m_textFontDelayMs != m_textFontDelayMsPrev);
2092  if (!vo)
2093  return;
2094  m_safeArea = vo->GetSafeRect();
2095 
2096  VideoFrame *currentFrame = vo->GetLastShownFrame();
2097  if (!currentFrame)
2098  return;
2099 
2101  subs->Lock();
2102  uint64_t playPos = 0;
2103  int playPosAdj = m_textFontDelayMs;
2104  if (subs->IsFrameBasedTiming())
2105  {
2106  // frame based subtitles get out of synch after running mythcommflag
2107  // for the file, i.e., the following number is wrong and does not
2108  // match the subtitle frame numbers:
2109  playPos = currentFrame->frameNumber;
2110  playPosAdj /= m_player->GetFrameRate();
2111  }
2112  else
2113  {
2114  // Use timecodes for time based SRT subtitles. Feeding this into
2115  // NormalizeVideoTimecode() should adjust for non-zero start times
2116  // and wraps. For MPEG, wraps will occur just once every 26.5 hours
2117  // and other formats less frequently so this should be sufficient.
2118  // Note: timecodes should now always be valid even in the case
2119  // when a frame doesn't have a valid timestamp. If an exception is
2120  // found where this is not true then we need to use the frameNumber
2121  // when timecode is not defined by uncommenting the following lines.
2122  //if (currentFrame->timecode == 0)
2123  // playPos = (uint64_t)
2124  // ((currentFrame->frameNumber / video_frame_rate) * 1000);
2125  //else
2126  playPos = m_player->GetDecoder()->NormalizeVideoTimecode(currentFrame->timecode);
2127  }
2128  playPos -= playPosAdj;
2129  if (playPos != 0)
2130  changed |= subs->HasSubtitleChanged(playPos);
2131  if (!changed)
2132  {
2133  subs->Unlock();
2134  return;
2135  }
2136 
2139 
2140  if (playPos == 0)
2141  {
2142  subs->Unlock();
2143  return;
2144  }
2145 
2146  QStringList rawsubs = subs->GetSubtitles(playPos);
2147  if (rawsubs.empty())
2148  {
2149  subs->Unlock();
2150  return;
2151  }
2152 
2153  subs->Unlock();
2154  DrawTextSubtitles(rawsubs, 0, 0);
2155 }
2156 
2158 {
2159  if (!m_player || !m_subreader)
2160  return;
2161 
2162  uint64_t duration;
2163  QStringList subs = m_subreader->GetRawTextSubtitles(duration);
2164  if (subs.empty())
2165  return;
2166 
2168  if (!vo)
2169  return;
2170 
2171  VideoFrame *currentFrame = vo->GetLastShownFrame();
2172  if (!currentFrame)
2173  return;
2174 
2175  m_safeArea = vo->GetSafeRect();
2176 
2177  // delete old subs that may still be on screen
2180  DrawTextSubtitles(subs, currentFrame->timecode, duration);
2181 }
2182 
2183 void SubtitleScreen::DrawTextSubtitles(const QStringList &subs,
2184  uint64_t start, uint64_t duration)
2185 {
2186  FormattedTextSubtitleSRT *fsub =
2187  new FormattedTextSubtitleSRT(m_family, m_safeArea, start, duration,
2188  this, subs);
2189  m_qInited.append(fsub);
2190 }
2191 
2193 {
2194  if (!m_608reader)
2195  return;
2196 
2197  bool changed = (m_textFontZoom != m_textFontZoomPrev);
2198 
2199  if (!m_player || !m_player->GetVideoOutput())
2200  return;
2202 
2203  CC608Buffer* textlist = m_608reader->GetOutputText(changed);
2204  if (!changed)
2205  return;
2206 
2209 
2210  if (!textlist)
2211  return;
2212 
2213  QMutexLocker locker(&textlist->lock);
2214 
2215  if (textlist->buffers.empty())
2216  return;
2217 
2218  FormattedTextSubtitle608 *fsub =
2219  new FormattedTextSubtitle608(textlist->buffers, m_family,
2220  m_safeArea, this/*, m_textFontZoom*/);
2221  m_qInited.append(fsub);
2222 }
2223 
2225 {
2226  if (!m_708reader || !m_player || !m_player->GetVideoOutput())
2227  return;
2228 
2229  CC708Service *cc708service = m_708reader->GetCurrentService();
2230  float video_aspect = m_player->GetVideoAspect();
2231  QRect oldsafe = m_safeArea;
2233  bool changed = (oldsafe != m_safeArea || m_textFontZoom != m_textFontZoomPrev);
2234  if (changed)
2235  {
2236  for (int i = 0; i < k708MaxWindows; i++)
2237  cc708service->m_windows[i].SetChanged();
2238  }
2239 
2240  uint64_t clearMask = 0;
2241  QList<FormattedTextSubtitle *> addList;
2242  for (int i = 0; i < k708MaxWindows; i++)
2243  {
2244  CC708Window &win = cc708service->m_windows[i];
2245  if (win.GetExists() && win.GetVisible() && !win.GetChanged())
2246  continue;
2247  if (!win.GetChanged())
2248  continue;
2249 
2250  clearMask |= (1UL << i);
2251  win.ResetChanged();
2252  if (!win.GetExists() || !win.GetVisible())
2253  continue;
2254 
2255  QMutexLocker locker(&win.m_lock);
2256  vector<CC708String*> list = win.GetStrings();
2257  if (!list.empty())
2258  {
2259  FormattedTextSubtitle708 *fsub =
2260  new FormattedTextSubtitle708(win, i, list, m_family,
2261  m_safeArea,
2262  this, video_aspect);
2263  addList.append(fsub);
2264  }
2265  win.DisposeStrings(list);
2266  }
2267  if (clearMask)
2268  {
2269  Clear708Cache(clearMask);
2270  }
2271  if (!addList.empty())
2272  m_qInited.append(addList);
2273 }
2274 
2275 void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
2276 {
2278  if (!vo)
2279  return;
2280 
2281  QRect scaled = vo->GetImageRect(pos);
2282  if (scaled.size() != pos.size())
2283  {
2284  img = img.scaled(scaled.width(), scaled.height(),
2285  Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2286  }
2287 
2288  MythPainter *osd_painter = vo->GetOSDPainter();
2289  MythImage* image = nullptr;
2290  if (osd_painter)
2291  image = osd_painter->GetFormatImage();
2292 
2293  if (image)
2294  {
2295  image->Assign(img);
2296  MythUIImage *uiimage = new SubImage(this, "dvd_button", MythRect(scaled), 0);
2297  if (uiimage)
2298  {
2299  uiimage->SetImage(image);
2300  uiimage->SetArea(MythRect(scaled));
2301  SetElementAdded();
2302  }
2303  image->DecrRef();
2304  }
2305 }
2306 
2307 #ifdef USING_LIBASS
2308 static void myth_libass_log(int level, const char *fmt, va_list vl, void */*ctx*/)
2309 {
2310  uint64_t verbose_mask = VB_GENERAL;
2311  LogLevel_t verbose_level = LOG_INFO;
2312 
2313  switch (level)
2314  {
2315  case 0: //MSGL_FATAL
2316  verbose_level = LOG_EMERG;
2317  break;
2318  case 1: //MSGL_ERR
2319  verbose_level = LOG_ERR;
2320  break;
2321  case 2: //MSGL_WARN
2322  verbose_level = LOG_WARNING;
2323  break;
2324  case 4: //MSGL_INFO
2325  verbose_level = LOG_INFO;
2326  break;
2327  case 6: //MSGL_V
2328  case 7: //MSGL_DBG2
2329  verbose_level = LOG_DEBUG;
2330  break;
2331  default:
2332  return;
2333  }
2334 
2335  if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
2336  return;
2337 
2338  static QMutex string_lock;
2339  string_lock.lock();
2340 
2341  char str[1024];
2342  int bytes = vsnprintf(str, sizeof str, fmt, vl);
2343  // check for truncated messages and fix them
2344  int truncated = bytes - ((sizeof str)-1);
2345  if (truncated > 0)
2346  {
2347  LOG(VB_GENERAL, LOG_ERR,
2348  QString("libASS log output truncated %1 of %2 bytes written")
2349  .arg(truncated).arg(bytes));
2350  }
2351 
2352  LOG(verbose_mask, verbose_level, QString("libass: %s").arg(str));
2353  string_lock.unlock();
2354 }
2355 
2357 {
2358  if (m_assLibrary && m_assRenderer)
2359  return true;
2360 
2361  if (!m_assLibrary)
2362  {
2363  m_assLibrary = ass_library_init();
2364  if (!m_assLibrary)
2365  return false;
2366 
2367  ass_set_message_cb(m_assLibrary, myth_libass_log, nullptr);
2368  ass_set_extract_fonts(m_assLibrary, static_cast<int>(true));
2369  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
2370  }
2371 
2372  LoadAssFonts();
2373 
2374  if (!m_assRenderer)
2375  {
2376  m_assRenderer = ass_renderer_init(m_assLibrary);
2377  if (!m_assRenderer)
2378  return false;
2379 
2380 #ifdef Q_OS_ANDROID
2381  // fontconfig doesn't yet work for us on Android. For the
2382  // time being, more explicitly set a font we know should
2383  // exist. This was adapted from VLC master as of 2019-01-21.
2384  const char *psz_font = "/system/fonts/DroidSans.ttf";
2385  const char *psz_font_family = "Droid Sans";
2386 #else
2387  const char *psz_font = nullptr;
2388  const char *psz_font_family = "sans-serif";
2389 #endif
2390  ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1, nullptr, 1);
2391  ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2392  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
2393  }
2394 
2395  return true;
2396 }
2397 
2399 {
2400  if (!m_assLibrary || !m_player)
2401  return;
2402 
2404  if (m_assFontCount == count)
2405  return;
2406 
2407  ass_clear_fonts(m_assLibrary);
2408  m_assFontCount = 0;
2409 
2410  // TODO these need checking and/or reinitialising after a stream change
2411  for (uint i = 0; i < count; ++i)
2412  {
2413  QByteArray filename;
2414  QByteArray font;
2415  m_player->GetDecoder()->GetAttachmentData(i, filename, font);
2416  ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
2417  LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
2418  .arg(filename.constData()));
2419  m_assFontCount++;
2420  }
2421 }
2422 
2424 {
2425  CleanupAssTrack();
2426 
2427  if (m_assRenderer)
2428  ass_renderer_done(m_assRenderer);
2429  m_assRenderer = nullptr;
2430 
2431  if (m_assLibrary)
2432  {
2433  ass_clear_fonts(m_assLibrary);
2434  m_assFontCount = 0;
2435  ass_library_done(m_assLibrary);
2436  }
2437  m_assLibrary = nullptr;
2438 }
2439 
2441 {
2442  if (!InitialiseAssLibrary() || !m_player)
2443  return;
2444 
2445  if (tracknum == m_assTrackNum && m_assTrack)
2446  return;
2447 
2448  LoadAssFonts();
2449  CleanupAssTrack();
2450  m_assTrack = ass_new_track(m_assLibrary);
2451  m_assTrackNum = tracknum;
2452 
2453  QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
2454  if (!header.isNull())
2455  ass_process_codec_private(m_assTrack, header.data(), header.size());
2456 
2459 }
2460 
2462 {
2463  if (m_assTrack)
2464  ass_free_track(m_assTrack);
2465  m_assTrack = nullptr;
2466 }
2467 
2469 {
2470  if (m_assTrack && event)
2471  ass_process_data(m_assTrack, event, strlen(event));
2472 }
2473 
2475 {
2476  // TODO this probably won't work properly for anamorphic content and XVideo
2477  ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
2478  ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2479  ass_set_use_margins(m_assRenderer, static_cast<int>(true));
2480  ass_set_font_scale(m_assRenderer, 1.0);
2481 }
2482 
2483 void SubtitleScreen::RenderAssTrack(uint64_t timecode)
2484 {
2485  if (!m_player || !m_assRenderer || !m_assTrack)
2486  return;
2487 
2489  if (!vo )
2490  return;
2491 
2492  QRect oldscreen = m_safeArea;
2493  m_safeArea = vo->GetMHEGBounds();
2494  if (oldscreen != m_safeArea)
2496 
2497  int changed = 0;
2498  ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack,
2499  timecode, &changed);
2500  if (!changed)
2501  return;
2502 
2503  MythPainter *osd_painter = vo->GetOSDPainter();
2504  if (!osd_painter)
2505  return;
2506 
2507  int count = 0;
2510  while (images)
2511  {
2512  if (images->w == 0 || images->h == 0)
2513  {
2514  images = images->next;
2515  continue;
2516  }
2517 
2518  uint8_t alpha = images->color & 0xFF;
2519  uint8_t blue = images->color >> 8 & 0xFF;
2520  uint8_t green = images->color >> 16 & 0xFF;
2521  uint8_t red = images->color >> 24 & 0xFF;
2522 
2523  if (alpha == 255)
2524  {
2525  images = images->next;
2526  continue;
2527  }
2528 
2529  QSize img_size(images->w, images->h);
2530  QRect img_rect(images->dst_x,images->dst_y,
2531  images->w, images->h);
2532  QImage qImage(img_size, QImage::Format_ARGB32);
2533  qImage.fill(0x00000000);
2534 
2535  unsigned char *src = images->bitmap;
2536  for (int y = 0; y < images->h; ++y)
2537  {
2538  for (int x = 0; x < images->w; ++x)
2539  {
2540  uint8_t value = src[x];
2541  if (value)
2542  {
2543  uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
2544  (red << 16) | (green << 8) | blue;
2545  qImage.setPixel(x, y, pixel);
2546  }
2547  }
2548  src += images->stride;
2549  }
2550 
2551  MythImage* image = nullptr;
2552  SubImage *uiimage = nullptr;
2553 
2554  if (osd_painter)
2555  image = osd_painter->GetFormatImage();
2556 
2557  if (image)
2558  {
2559  image->Assign(qImage);
2560  QString name = QString("asssub%1").arg(count);
2561  uiimage = new SubImage(this, name, MythRect(img_rect), 0);
2562  if (uiimage)
2563  {
2564  uiimage->SetImage(image);
2565  uiimage->SetArea(MythRect(img_rect));
2566  SetElementAdded();
2567  }
2568  image->DecrRef();
2569  }
2570  images = images->next;
2571  count++;
2572  }
2573 }
2574 #endif // USING_LIBASS
virtual QByteArray GetSubHeader(uint) const
Definition: decoderbase.h:256
static const QString kSubAttrOutline("outline")
virtual CC708Reader * GetCC708Reader(uint=0)
Definition: mythplayer.h:295
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:175
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
void SetZoom(int percent)
static void Complement(MythFontProperties *font, MythUIShape *bg)
int GetTrack(uint type) const
Definition: decoderbase.h:245
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)
bool Split(FormattedTextChunk &newChunk)
QHash< QString, int > m_pixelSizeMap
VideoOutput * GetVideoOutput(void)
Definition: mythplayer.h:244
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:881
void Load(const QString &family, const CC708CharacterAttribute &attr)
void CleanupAssLibrary(void)
CC608Buffer * GetOutputText(bool &changed, int &streamIdx)
Definition: cc608reader.cpp:57
void SetFillBrush(QBrush fill)
Definition: mythuishape.cpp:42
QColor GetEdgeColor(void) const
Definition: cc708window.h:118
QHash< QString, int > m_outlineSizeMap
vector< CC608Text * > buffers
Definition: cc608reader.h:54
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
void DisposeStrings(vector< CC708String * > &strings) const
QList< FormattedTextSubtitle * > m_qInited
static void myth_libass_log(int level, const char *fmt, va_list vl, void *)
TextSubtitles * GetTextSubtitles(void)
unsigned int uint
Definition: compat.h:140
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
QRect GetMHEGBounds(void)
static const QString kSubProvider("provider")
CC608Reader * m_608reader
long long timecode
Definition: mythframe.h:49
static const QString kSubAttrItalics("italics")
QRect GetSafeRect(void)
Returns a QRect describing an area of the screen on which it is 'safe' to render the On Screen Displa...
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
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
QMutex lock
Definition: cc608reader.h:53
virtual void GetAttachmentData(uint, QByteArray &, QByteArray &)
Definition: decoderbase.h:257
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")
This class serves as the base class for all video output methods.
Definition: videooutbase.h:46
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
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")
void SetLinePen(QPen pen)
Definition: mythuishape.cpp:47
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
int height
Definition: mythframe.h:42
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:176
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
long long frameNumber
Definition: mythframe.h:48
const MythRect m_swOrigArea
const uint k708AttrSizeLarge
Definition: cc708window.cpp:79
static const float PAD_WIDTH
SubtitleReader * m_subreader
QHash< QString, MythFontProperties * > m_fontMap
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)
const char * name
Definition: ParseText.cpp:328
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)
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)
MythFontProperties * GetFont(const QString &text) const
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:591
virtual uint GetTrackCount(uint type) const
Definition: decoderbase.h:239
QRect GetImageRect(const QRect &rect, QRect *display=nullptr)
translates caption/dvd button rectangle into 'screen' space
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
QColor GetBGColor(void) const
Definition: cc708window.h:112
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)
virtual VideoFrame * GetLastShownFrame(void)
Returns frame from the head of the ready to be displayed queue, if StartDisplayingFrame has been call...
Definition: videooutbase.h:239
MythDeque< AVSubtitle > m_buffers
CC708CharacterAttribute m_format
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)
virtual void GetOSDBounds(QRect &total, QRect &visible, float &visible_aspect, float &font_scaling, float themeAspect) const
Wrapper around QPoint allowing us to handle percentage and other relative values for positioning in m...
Definition: mythrect.h:86
void Resize(const QSize &newSize, bool preserveAspect=false)
Definition: mythimage.cpp:146
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:296
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
virtual MythPainter * GetOSDPainter(void)
Definition: videooutbase.h:268
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:87
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
MythUIType * GetChild(const QString &name) const
Get a named child of this UIType.
Definition: mythuitype.cpp:132
void DisplayRawTextSubtitles(void)
static int x3
Definition: mythsocket.cpp:62
void CopyFrom(MythUIType *base) override
Copy this widgets state from another.
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:278
void FreeAVSubtitle(const AVSubtitle &sub)
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:297
int GetAlpha(void) const
Definition: mythuitype.cpp:933