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