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