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