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 for (const auto & ftl : std::as_const(m_lines))
953 {
954 QString line;
955 if (ftl.m_origX > 0)
956 line.fill(' ', ftl.m_origX);
957 QList<FormattedTextChunk>::const_iterator chunk;
958 for (chunk = ftl.chunks.constBegin();
959 chunk != ftl.chunks.constEnd();
960 ++chunk)
961 {
962 const QString &text = (*chunk).m_text;
963 const CC708CharacterAttribute &attr = (*chunk).m_format;
964 bool isBlank = !attr.m_underline && text.trimmed().isEmpty();
965 if (!isBlank)
966 {
967 if (attr.m_boldface)
968 line += "<b>";
969 if (attr.m_italics)
970 line += "<i>";
971 if (attr.m_underline)
972 line += "<u>";
973 if (attr.GetFGColor() != Qt::white)
974 line += QString("<font color=\"%1\">")
975 .arg(srtColorString(attr.GetFGColor()));
976 }
977 line += text;
978 if (!isBlank)
979 {
980 if (attr.GetFGColor() != Qt::white)
981 line += QString("</font>");
982 if (attr.m_underline)
983 line += "</u>";
984 if (attr.m_italics)
985 line += "</i>";
986 if (attr.m_boldface)
987 line += "</b>";
988 }
989 }
990 if (!line.trimmed().isEmpty())
991 result += line;
992 }
993 return result;
994}
995
996void FormattedTextSubtitleSRT::Init(const QStringList &subs)
997{
998 // Does a simplistic parsing of HTML tags from the strings.
999 // Nesting is not implemented. Stray whitespace may cause
1000 // legitimate tags to be ignored. All other HTML tags are
1001 // stripped and ignored.
1002 //
1003 // <i> - enable italics
1004 // </i> - disable italics
1005 // <b> - enable boldface
1006 // </b> - disable boldface
1007 // <u> - enable underline
1008 // </u> - disable underline
1009 // <font color="#xxyyzz"> - change font color
1010 // </font> - reset font color to white
1011
1012 int pixelSize = m_safeArea.height() / 20;
1013 if (m_subScreen)
1014 m_subScreen->SetFontSize(pixelSize);
1015 m_xAnchorPoint = 1; // center
1016 m_yAnchorPoint = 2; // bottom
1017 m_xAnchor = m_safeArea.width() / 2;
1018 m_yAnchor = m_safeArea.height();
1019
1020 bool isItalic = false;
1021 bool isBold = false;
1022 bool isUnderline = false;
1023 QColor color(Qt::white);
1024 static const QRegularExpression htmlTag {
1025 "</?.+>", QRegularExpression::InvertedGreedinessOption };
1026 QString htmlPrefix("<font color=\"");
1027 QString htmlSuffix("\">");
1028 for (const QString& subtitle : std::as_const(subs))
1029 {
1030 FormattedTextLine line;
1031 QString text(subtitle);
1032 while (!text.isEmpty())
1033 {
1034 auto match = htmlTag.match(text);
1035 int pos = match.capturedStart();
1036 if (pos != 0) // don't add a zero-length string
1037 {
1038 CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1039 color);
1040 FormattedTextChunk chunk(text.left(pos), attr, m_subScreen);
1041 line.chunks += chunk;
1042 text = (pos < 0 ? "" : text.mid(pos));
1043 LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
1044 .arg(chunk.ToLogString()));
1045 }
1046 if (pos >= 0)
1047 {
1048 int htmlLen = match.capturedLength();
1049 QString html = text.left(htmlLen).toLower();
1050 text = text.mid(htmlLen);
1051 if (html == "<i>")
1052 isItalic = true;
1053 else if (html == "</i>")
1054 isItalic = false;
1055 else if (html.startsWith(htmlPrefix) &&
1056 html.endsWith(htmlSuffix))
1057 {
1058 int colorLen = html.length() -
1059 (htmlPrefix.length() + htmlSuffix.length());
1060 QString colorString(
1061 html.mid(htmlPrefix.length(), colorLen));
1062 QColor newColor(colorString);
1063 if (newColor.isValid())
1064 {
1065 color = newColor;
1066 }
1067 else
1068 {
1069 LOG(VB_VBI, LOG_INFO,
1070 QString("Ignoring invalid SRT color specification: "
1071 "'%1'").arg(colorString));
1072 }
1073 }
1074 else if (html == "</font>")
1075 {
1076 color = Qt::white;
1077 }
1078 else if (html == "<b>")
1079 {
1080 isBold = true;
1081 }
1082 else if (html == "</b>")
1083 {
1084 isBold = false;
1085 }
1086 else if (html == "<u>")
1087 {
1088 isUnderline = true;
1089 }
1090 else if (html == "</u>")
1091 {
1092 isUnderline = false;
1093 }
1094 else
1095 {
1096 LOG(VB_VBI, LOG_INFO,
1097 QString("Ignoring unknown SRT formatting: '%1'")
1098 .arg(html));
1099 }
1100
1101 LOG(VB_VBI, LOG_INFO,
1102 QString("SRT formatting change '%1', "
1103 "new ital=%2 bold=%3 uline=%4 color=%5)")
1104 .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
1105 .arg(srtColorString(color)));
1106 }
1107 }
1108 m_lines += line;
1109 }
1110}
1111
1113{
1114 int maxWidth = m_safeArea.width();
1115 for (int i = 0; i < m_lines.size(); i++)
1116 {
1117 int width = m_lines[i].CalcSize().width();
1118 // Move entire chunks to the next line as necessary. Leave at
1119 // least one chunk on the current line.
1120 while (width > maxWidth && m_lines[i].chunks.size() > 1)
1121 {
1122 width -= m_lines[i].chunks.back().CalcSize().width();
1123 // Make sure there's a next line to wrap into.
1124 if (m_lines.size() == i + 1)
1125 m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1126 m_lines[i].m_yIndent);
1127 m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
1128 LOG(VB_VBI, LOG_INFO,
1129 QString("Wrapping chunk to next line: '%1'")
1130 .arg(m_lines[i+1].chunks[0].m_text));
1131 }
1132 // Split the last chunk until width is small enough or until
1133 // no more splits are possible.
1134 bool isSplitPossible = true;
1135 while (width > maxWidth && isSplitPossible)
1136 {
1137 FormattedTextChunk newChunk;
1138 isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
1139 if (isSplitPossible)
1140 {
1141 // Make sure there's a next line to split into.
1142 if (m_lines.size() == i + 1)
1143 m_lines += FormattedTextLine(m_lines[i].m_xIndent,
1144 m_lines[i].m_yIndent);
1145 m_lines[i+1].chunks.prepend(newChunk);
1146 width = m_lines[i].CalcSize().width();
1147 }
1148 }
1149 }
1150}
1151
1157static QString extract_cc608(QString &text, int &color,
1158 bool &isItalic, bool &isUnderline)
1159{
1160 QString result;
1161
1162 // Handle an initial control sequence.
1163 if (text.length() >= 1 && text[0] >= QChar(0x7000))
1164 {
1165 int op = text[0].unicode() - 0x7000;
1166 isUnderline = ((op & 0x1) != 0);
1167 switch (op & ~1)
1168 {
1169 case 0x0e:
1170 // color unchanged
1171 isItalic = true;
1172 break;
1173 case 0x1e:
1174 color = op >> 1;
1175 isItalic = true;
1176 break;
1177 case 0x20:
1178 // color unchanged
1179 // italics unchanged
1180 break;
1181 default:
1182 color = (op & 0xf) >> 1;
1183 isItalic = false;
1184 break;
1185 }
1186 text = text.mid(1);
1187 }
1188
1189 // Copy the string into the result, up to the next control
1190 // character.
1191 static const QRegularExpression kControlCharsRE { "[\\x{7000}-\\x{7fff}]" };
1192 int nextControl = text.indexOf(kControlCharsRE);
1193 if (nextControl < 0)
1194 {
1195 result = text;
1196 text.clear();
1197 }
1198 else
1199 {
1200 result = text.left(nextControl);
1201 text = text.mid(nextControl);
1202 }
1203
1204 return result;
1205}
1206
1207// Adjusts the Y coordinates to avoid overlap, which could happen as a
1208// result of a large text zoom factor. Then, if the total height
1209// exceeds the safe area, compresses each piece of vertical blank
1210// space proportionally to make it fit.
1212{
1213 int totalHeight = 0;
1214 int totalSpace = 0;
1215 int firstY = 0;
1216 int prevY = 0;
1217 QVector<int> heights(m_lines.size());
1218 QVector<int> spaceBefore(m_lines.size());
1219 // Calculate totalHeight and totalSpace
1220 for (int i = 0; i < m_lines.size(); i++)
1221 {
1222 m_lines[i].m_yIndent = std::max(m_lines[i].m_yIndent, prevY); // avoid overlap
1223 int y = m_lines[i].m_yIndent;
1224 if (i == 0)
1225 firstY = prevY = y;
1226 int height = m_lines[i].CalcSize().height();
1227 heights[i] = height;
1228 spaceBefore[i] = y - prevY;
1229 totalSpace += (y - prevY);
1230 prevY = y + height;
1231 totalHeight += height;
1232 }
1233 int safeHeight = m_safeArea.height();
1234 int overage = std::min(totalHeight - safeHeight, totalSpace);
1235
1236 // Recalculate Y coordinates, applying the shrink factor to space
1237 // between each line.
1238 if (overage > 0 && totalSpace > 0)
1239 {
1240 float shrink = (totalSpace - overage) / (float)totalSpace;
1241 prevY = firstY;
1242 for (int i = 0; i < m_lines.size(); i++)
1243 {
1244 m_lines[i].m_yIndent = prevY + (spaceBefore[i] * shrink);
1245 prevY = m_lines[i].m_yIndent + heights[i];
1246 }
1247 }
1248
1249 // Shift Y coordinates back up into the safe area.
1250 int shift = std::min(firstY, std::max(0, prevY - safeHeight));
1251 // NOLINTNEXTLINE(modernize-loop-convert)
1252 for (int i = 0; i < m_lines.size(); i++)
1253 m_lines[i].m_yIndent -= shift;
1254
1256}
1257
1258void FormattedTextSubtitle608::Init(const std::vector<CC608Text*> &buffers)
1259{
1260 static const std::array<const QColor,8> kClr
1261 {
1262 Qt::white, Qt::green, Qt::blue, Qt::cyan,
1263 Qt::red, Qt::yellow, Qt::magenta, Qt::white,
1264 };
1265
1266 if (buffers.empty())
1267 return;
1268 auto i = buffers.cbegin();
1269 int xscale = 36;
1270 int yscale = 17;
1271 int pixelSize = m_safeArea.height() / (yscale * LINE_SPACING);
1272 int fontwidth = 0;
1273 int xmid = 0;
1274 int zoom = 100;
1275 if (m_subScreen)
1276 {
1277 zoom = m_subScreen->GetZoom();
1278 m_subScreen->SetFontSize(pixelSize);
1279 CC708CharacterAttribute def_attr(false, false, false, kClr[0]);
1280 QFont *font = m_subScreen->GetFont(def_attr)->GetFace();
1281 QFontMetrics fm(*font);
1282 fontwidth = fm.averageCharWidth();
1283 xmid = m_safeArea.width() / 2;
1284 // Disable centering for zoom factor >= 100%
1285 if (zoom >= 100)
1286 xscale = m_safeArea.width() / fontwidth;
1287 }
1288
1289 for (; i != buffers.cend(); ++i)
1290 {
1291 CC608Text *cc = (*i);
1292 int color = 0;
1293 bool isItalic = false;
1294 bool isUnderline = false;
1295 const bool isBold = false;
1296 QString text(cc->m_text);
1297
1298 int orig_x = cc->m_x;
1299 // position as if we use a fixed size font
1300 // - font size already has zoom factor applied
1301
1302 int x = 0;
1303 if (xmid)
1304 {
1305 // center horizontally
1306 x = xmid + ((orig_x - xscale / 2) * fontwidth);
1307 }
1308 else
1309 {
1310 // fallback
1311 x = (orig_x + 3) * m_safeArea.width() / xscale;
1312 }
1313
1314 int orig_y = cc->m_y;
1315 int y = 0;
1316 if (orig_y < yscale / 2)
1317 {
1318 // top half -- anchor up
1319 y = (orig_y * m_safeArea.height() * zoom / (yscale * 100));
1320 }
1321 else
1322 {
1323 // bottom half -- anchor down
1324 y = m_safeArea.height() -
1325 ((yscale - orig_y - 0.5) * m_safeArea.height() * zoom /
1326 (yscale * 100));
1327 }
1328
1329 FormattedTextLine line(x, y, orig_x, orig_y);
1330 while (!text.isNull())
1331 {
1332 QString captionText =
1333 extract_cc608(text, color, isItalic, isUnderline);
1334 CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
1335 kClr[std::clamp(color, 0, 7)]);
1336 FormattedTextChunk chunk(captionText, attr, m_subScreen);
1337 line.chunks += chunk;
1338 LOG(VB_VBI, LOG_INFO,
1339 QString("Adding cc608 chunk (%1,%2): %3")
1340 .arg(cc->m_x).arg(cc->m_y).arg(chunk.ToLogString()));
1341 }
1342 m_lines += line;
1343 }
1344}
1345
1347{
1348 // Draw the window background after calculating bounding rectangle
1349 // in Layout()
1350 if (m_bgFillAlpha) // TODO border?
1351 {
1352 QBrush fill(m_bgFillColor, Qt::SolidPattern);
1353 auto *shape = new SubShape(m_subScreen, QString("cc708bg%1").arg(m_num),
1355 shape->SetFillBrush(fill);
1356 shape->SetArea(MythRect(m_bounds));
1357 }
1358 // The background is drawn first so that it appears behind
1359 // everything else.
1361}
1362
1364 const std::vector<CC708String*> &list,
1365 float aspect)
1366{
1367 LOG(VB_VBI, LOG_DEBUG,LOC +
1368 QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
1369 "relative %5")
1370 .arg(m_num).arg(win.m_anchor_point).arg(win.m_anchor_horizontal)
1371 .arg(win.m_anchor_vertical).arg(win.m_relative_pos));
1372 int pixelSize = m_safeArea.height() / 20;
1373 if (m_subScreen)
1374 m_subScreen->SetFontSize(pixelSize);
1375
1376 float xrange { 160.0F };
1377 if (win.m_relative_pos)
1378 xrange = 100.0F;
1379 else if (aspect > 1.4F)
1380 xrange = 210.0F;
1381 float yrange = win.m_relative_pos ? 100.0F : 75.0F;
1382 float xmult = (float)m_safeArea.width() / xrange;
1383 float ymult = (float)m_safeArea.height() / yrange;
1384 uint anchor_x = (uint)(xmult * (float)win.m_anchor_horizontal);
1385 uint anchor_y = (uint)(ymult * (float)win.m_anchor_vertical);
1388 m_xAnchor = anchor_x;
1389 m_yAnchor = anchor_y;
1390
1391 for (auto *str708 : list)
1392 {
1393 if (str708->m_y >= (uint)m_lines.size())
1394 m_lines.resize(str708->m_y + 1);
1395 FormattedTextChunk chunk(str708->m_str, str708->m_attr, m_subScreen);
1396 m_lines[str708->m_y].chunks += chunk;
1397 LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
1398 .arg(m_num).arg(str708->m_y).arg(chunk.ToLogString()));
1399 }
1400}
1401
1403
1405 const QString &Name, int FontStretch)
1406 : MythScreenType(static_cast<MythScreenType*>(nullptr), Name),
1407 m_player(Player),
1408 m_fontStretch(FontStretch),
1409 m_format(new SubtitleFormat)
1410{
1411 m_painter = Painter;
1412
1413 connect(m_player, &MythPlayer::SeekingDone, this, [&]()
1414 {
1415 LOG(VB_PLAYBACK, LOG_INFO, LOC + "SeekingDone signal received.");
1416 m_atEnd = false;
1417 });
1418}
1419
1421{
1423 delete m_format;
1424#if CONFIG_LIBASS
1425 CleanupAssLibrary();
1426#endif
1427}
1428
1429void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
1430{
1431 int prevType = m_subtitleType;
1433
1434 if (forced_only)
1435 {
1436 if (prevType == kDisplayNone)
1437 {
1439 SetVisible(true);
1440 SetArea(MythRect());
1441 }
1442 }
1443 else
1444 {
1445 if (m_subreader)
1446 {
1450 }
1451 if (m_cc608reader)
1453 if (m_cc708reader)
1457 SetArea(MythRect());
1458 }
1459 if (!forced_only || m_family.isEmpty()) {
1460 switch (m_subtitleType)
1461 {
1465 m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1466 break;
1467 case kDisplayCC608:
1469 m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1470 break;
1471 case kDisplayCC708:
1473 m_textFontZoom = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
1474 break;
1475 case kDisplayAVSubtitle:
1477 m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
1478 break;
1479 }
1480 }
1485}
1486
1488{
1490 return;
1492 SetVisible(false);
1493 SetArea(MythRect());
1494}
1495
1497{
1500#if CONFIG_LIBASS
1501 if (m_assTrack)
1502 ass_flush_events(m_assTrack);
1503#endif
1504}
1505
1507{
1513 m_cc608reader->ClearBuffers(true, true);
1516}
1517
1519{
1522}
1523
1524void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
1525{
1526 if (!dvdButton || !m_player)
1527 return;
1528
1530 if (!vo)
1531 return;
1532
1535
1536 float tmp = 0.0;
1537 QRect dummy;
1538 vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1539
1540 AVSubtitleRect *hl_button = dvdButton->rects[0];
1541 uint h = hl_button->h;
1542 uint w = hl_button->w;
1543 QRect rect = QRect(hl_button->x, hl_button->y, w, h);
1544 QImage bg_image(hl_button->data[0], w, h, w, QImage::Format_Indexed8);
1545 auto *bgpalette = (uint32_t *)(hl_button->data[1]);
1546
1547 QVector<uint32_t> bg_palette(4);
1548 for (int i = 0; i < 4; i++)
1549 bg_palette[i] = bgpalette[i];
1550 bg_image.setColorTable(bg_palette);
1551
1552 // copy button region of background image
1553 const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
1554 QImage fg_image = bg_image.copy(fg_rect);
1555 QVector<uint32_t> fg_palette(4);
1556 auto *fgpalette = (uint32_t *)(dvdButton->rects[1]->data[1]);
1557 if (fgpalette)
1558 {
1559 for (int i = 0; i < 4; i++)
1560 fg_palette[i] = fgpalette[i];
1561 fg_image.setColorTable(fg_palette);
1562 }
1563
1564 bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
1565 fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
1566
1567 // set pixel of highlight area to highlight color
1568 for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
1569 {
1570 if ((x < 0) || (x > hl_button->w))
1571 continue;
1572 for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
1573 {
1574 if ((y < 0) || (y > hl_button->h))
1575 continue;
1576 bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
1577 }
1578 }
1579
1580 AddScaledImage(bg_image, rect);
1581}
1582
1584{
1585 m_textFontZoom = percent;
1586 if (m_family == kSubFamilyAV)
1587 gCoreContext->SaveSetting("OSDAVSubZoom", percent);
1588 else
1589 gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
1590}
1591
1593{
1594 return m_textFontZoom;
1595}
1596
1597void SubtitleScreen::SetDelay(std::chrono::milliseconds ms)
1598{
1599 m_textFontDelayMs = ms;
1600}
1601
1602std::chrono::milliseconds SubtitleScreen::GetDelay(void) const
1603{
1604 return m_textFontDelayMs;
1605}
1606
1608{
1609 QList<MythUIType *> list = m_childrenList;
1610 QList<MythUIType *>::iterator it;
1611 for (it = list.begin(); it != list.end(); ++it)
1612 {
1613 MythUIType *child = *it;
1614 auto *wrapper = dynamic_cast<SubWrapper *>(child);
1615 if (wrapper)
1616 {
1617 int whichImageCache = wrapper->GetWhichImageCache();
1618 if (whichImageCache != -1 && (mask & (1UL << whichImageCache)))
1619 {
1621 DeleteChild(child);
1622 }
1623 }
1624 }
1625}
1626
1627// SetElementAdded() should be called after a new element is added to
1628// the subtitle screen.
1630{
1631 m_refreshModified = true;
1632}
1633
1634// SetElementResized() should be called after a subtitle screen
1635// element's size is changed.
1637{
1639}
1640
1641// SetElementAdded() should be called *before* an element is deleted
1642// from the subtitle screen.
1644{
1645 if (!m_refreshDeleted)
1646 SetRedraw();
1647 m_refreshDeleted = true;
1648}
1649
1650// The QFontMetrics class does not account for the MythFontProperties
1651// shadow and offset properties. This method calculates the
1652// additional padding to the right and below that is needed for proper
1653// bounding box computation.
1655{
1656 QColor color;
1657 int alpha = 0;
1658 int outlineSize = 0;
1659 int shadowWidth = 0;
1660 int shadowHeight = 0;
1661 if (mythfont->hasOutline())
1662 {
1663 mythfont->GetOutline(color, outlineSize, alpha);
1664 MythPoint outline(outlineSize, outlineSize);
1665 outline.NormPoint();
1666 outlineSize = outline.x();
1667 }
1668 if (mythfont->hasShadow())
1669 {
1670 MythPoint shadowOffset;
1671 mythfont->GetShadow(shadowOffset, color, alpha);
1672 shadowOffset.NormPoint();
1673 shadowWidth = abs(shadowOffset.x());
1674 shadowHeight = abs(shadowOffset.y());
1675 // Shadow and outline overlap, so don't just add them.
1676 shadowWidth = std::max(shadowWidth, outlineSize);
1677 shadowHeight = std::max(shadowHeight, outlineSize);
1678 }
1679 return {shadowWidth + outlineSize, shadowHeight + outlineSize};
1680}
1681
1682QSize SubtitleScreen::CalcTextSize(const QString &text,
1683 const CC708CharacterAttribute &format,
1684 float layoutSpacing) const
1685{
1686 MythFontProperties *mythfont = GetFont(format);
1687 QFont *font = mythfont->GetFace();
1688 QFontMetrics fm(*font);
1689 int width = fm.horizontalAdvance(text);
1690 int height = fm.height() * (1 + PAD_HEIGHT);
1691 if (layoutSpacing > 0 && !text.trimmed().isEmpty())
1692 height = std::max(height, (int)(font->pixelSize() * layoutSpacing));
1693 height += CalcShadowOffsetPadding(mythfont).height();
1694 return {width, height};
1695}
1696
1697// Padding calculation is different depending on whether the padding
1698// is on the left side or the right side of the text. Padding on the
1699// right needs to add the shadow and offset padding.
1701 bool isFirst, bool isLast,
1702 int &left, int &right) const
1703{
1704 MythFontProperties *mythfont = GetFont(format);
1705 QFont *font = mythfont->GetFace();
1706 QFontMetrics fm(*font);
1707 int basicPadding = fm.maxWidth() * PAD_WIDTH;
1708 left = isFirst ? basicPadding : 0;
1709 right = CalcShadowOffsetPadding(mythfont).width() +
1710 (isLast ? basicPadding : 0);
1711}
1712
1715{
1716 return m_format->GetFont(m_family, attr, m_fontSize,
1718}
1719
1721{
1722 SubtitleFormat format;
1723 CC708CharacterAttribute attr(false, false, false, Qt::white);
1724 MythFontProperties *mythfont =
1725 format.GetFont(kSubFamilyTeletext, attr,
1726 /*pixelsize*/20, /*zoom*/100, /*stretch*/100);
1727 return mythfont->face().family();
1728}
1729
1731{
1732 SubtitleFormat format;
1734}
1735
1737{
1738 if (!m_player)
1739 return false;
1740
1744 if (!m_subreader)
1745 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
1746 if (!m_cc608reader)
1747 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
1748 if (!m_cc708reader)
1749 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
1750
1751 return true;
1752}
1753
1755{
1756 QList<MythUIType *>::iterator it;
1757 QList<MythUIType *>::iterator itNext;
1758
1760 MythVideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1761 std::chrono::milliseconds now =
1762 currentFrame ? currentFrame->m_timecode : std::chrono::milliseconds::max();
1763 bool needRescale = (m_textFontZoom != m_textFontZoomPrev);
1764
1765 for (it = m_childrenList.begin(); it != m_childrenList.end(); it = itNext)
1766 {
1767 itNext = it + 1;
1768 MythUIType *child = *it;
1769 auto *wrapper = dynamic_cast<SubWrapper *>(child);
1770 if (!wrapper)
1771 continue;
1772
1773 // Expire the subtitle object if needed.
1774 std::chrono::milliseconds expireTime = wrapper->GetExpireTime();
1775 if (expireTime > 0ms && expireTime < now)
1776 {
1777 DeleteChild(child);
1779 continue;
1780 }
1781
1782 // Rescale the AV subtitle image if the zoom changed.
1783 if (expireTime > 0ms && needRescale)
1784 {
1785 auto *image = dynamic_cast<SubImage *>(child);
1786 if (image)
1787 {
1788 double factor = m_textFontZoom / (double)m_textFontZoomPrev;
1789 QSize size = image->GetImage()->size();
1790 size *= factor;
1791 image->GetImage()->Resize(size);
1793 }
1794 }
1795 }
1796
1797 DisplayAVSubtitles(); // allow forced subtitles to work
1798
1801 else if (kDisplayCC708 == m_subtitleType)
1805 while (!m_qInited.isEmpty())
1806 {
1807 FormattedTextSubtitle *fsub = m_qInited.takeFirst();
1808 fsub->WrapLongLines();
1809 fsub->Layout();
1810 fsub->PreRender();
1811 fsub->Draw();
1812 delete fsub;
1814 }
1815
1823}
1824
1826{
1827 m_refreshModified = false;
1828 m_refreshDeleted = false;
1829}
1830
1832{
1833 if (!m_refreshModified)
1834 return;
1835
1836 QRegion visible;
1837 for (const auto *img : std::as_const(m_childrenList))
1838 {
1839 const auto *wrapper = dynamic_cast<const SubWrapper *>(img);
1840 if (wrapper && img->IsVisible())
1841 visible = visible.united(wrapper->GetOrigArea());
1842 }
1843
1844 if (visible.isEmpty())
1845 return;
1846
1847 QRect bounding = visible.boundingRect();
1848 bounding = bounding.translated(m_safeArea.topLeft());
1849 bounding = m_safeArea.intersected(bounding);
1850 int left = m_safeArea.left() - bounding.left();
1851 int top = m_safeArea.top() - bounding.top();
1852 SetArea(MythRect(bounding));
1853
1854 for (auto *img : std::as_const(m_childrenList))
1855 {
1856 auto *wrapper = dynamic_cast<SubWrapper *>(img);
1857 if (wrapper && img->IsVisible())
1858 img->SetArea(MythRect(wrapper->GetOrigArea().translated(left, top)));
1859 }
1860}
1861
1863{
1864 if (!m_player || !m_subreader)
1865 return;
1866
1868 QMutexLocker lock(&(subs->m_lock));
1869 if (subs->m_buffers.empty()
1872 return;
1873
1875 MythVideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : nullptr;
1876 int ret {0};
1877
1878 if (!currentFrame || !videoOut)
1879 return;
1880
1881 if (subs->m_buffers.empty() && (m_subreader->GetParser() != nullptr))
1882 {
1883 if (subs->m_needSync)
1884 {
1885 // Seeking on a subtitle stream is a pain. The internals
1886 // of ff_subtitles_queue_seek() calls search_sub_ts(),
1887 // which always returns the subtitle that starts on or
1888 // before the current timestamp.
1889 //
1890 // If avformat_seek_file() has been asked to search
1891 // forward, then the subsequent range check will always
1892 // fail because avformat_seek_file() has set the minimum
1893 // timestamp to the requested timestamp. This call only
1894 // succeeds if the timestamp matches to the millisecond.
1895 //
1896 // If avformat_seek_file() has been asked to search
1897 // backward then a subtitle will be returned, but because
1898 // that subtitle is in the past the code below this
1899 // comment will always consume that subtitle, resulting in
1900 // a new seek every time this function is called.
1901 //
1902 // The solution seems to be to seek backwards so that we
1903 // get the subtitle that should have most recently been
1904 // displayed, then skip that subtitle to get the one that
1905 // should be displayed next.
1906 lock.unlock();
1907 m_subreader->SeekFrame(currentFrame->m_timecode.count()*1000,
1908 AVSEEK_FLAG_BACKWARD);
1910 if (ret < 0)
1911 {
1912 m_atEnd = true;
1913#ifdef DEBUG_SUBTITLES
1914 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
1915 {
1916 LOG(VB_PLAYBACK, LOG_DEBUG,
1917 LOC + QString("time %1, no subtitle available after seek")
1918 .arg(toString(currentFrame)));
1919 }
1920#endif
1921 }
1922 lock.relock();
1923 subs->m_needSync = false;
1924
1925 // extra check to avoid segfault
1926 if (subs->m_buffers.empty())
1927 return;
1928 AVSubtitle subtitle = subs->m_buffers.front();
1929 if (subtitle.end_display_time < currentFrame->m_timecode.count())
1930 {
1931 subs->m_buffers.pop_front();
1932#ifdef DEBUG_SUBTITLES
1933 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
1934 {
1935 LOG(VB_PLAYBACK, LOG_DEBUG,
1936 LOC + QString("time %1, drop %2")
1937 .arg(toString(currentFrame), toString(subtitle)));
1938 }
1939#endif
1940 }
1941 }
1942
1943 // Always add one subtitle.
1944 lock.unlock();
1945 if (!m_atEnd)
1946 {
1948 if (ret < 0)
1949 {
1950 m_atEnd = true;
1951#ifdef DEBUG_SUBTITLES
1952 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
1953 {
1954 LOG(VB_PLAYBACK, LOG_DEBUG,
1955 LOC + QString("time %1, no subtitle available")
1956 .arg(toString(currentFrame)));
1957 }
1958#endif
1959 }
1960 }
1961 lock.relock();
1962 }
1963
1964 float tmp = 0.0;
1965 QRect dummy;
1966 videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
1967
1968 [[maybe_unused]] bool assForceNext {false};
1969 while (!subs->m_buffers.empty())
1970 {
1971 AVSubtitle subtitle = subs->m_buffers.front();
1972 if (subtitle.start_display_time > currentFrame->m_timecode.count())
1973 {
1974#ifdef DEBUG_SUBTITLES
1975 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
1976 {
1977 LOG(VB_PLAYBACK, LOG_DEBUG,
1978 LOC + QString("time %1, next %2")
1979 .arg(toString(currentFrame), toString(subtitle)));
1980 }
1981#endif
1982 break;
1983 }
1984
1985 // If this is the most recently displayed subtitle and a
1986 // backward jump means that it needs to be displayed again,
1987 // the call to ass_render_frame will say there is no work to
1988 // be done. Force RenderAssTrack to display the subtitle
1989 // anyway.
1990 assForceNext = true;
1991
1992 auto displayfor = std::chrono::milliseconds(subtitle.end_display_time -
1993 subtitle.start_display_time);
1994#ifdef DEBUG_SUBTITLES
1995 if (VERBOSE_LEVEL_CHECK(VB_PLAYBACK, LOG_DEBUG))
1996 {
1997 LOG(VB_PLAYBACK, LOG_DEBUG,
1998 LOC + QString("time %1, show %2")
1999 .arg(toString(currentFrame), toString(subtitle)));
2000 }
2001#endif
2002 if (displayfor == 0ms)
2003 displayfor = 60s;
2004 displayfor = (displayfor < 50ms) ? 50ms : displayfor;
2005 std::chrono::milliseconds late = currentFrame->m_timecode -
2006 std::chrono::milliseconds(subtitle.start_display_time);
2007
2009 subs->m_buffers.pop_front();
2010 for (std::size_t i = 0; i < subtitle.num_rects; ++i)
2011 {
2012 AVSubtitleRect* rect = subtitle.rects[i];
2013
2014 bool displaysub = true;
2015 if (!subs->m_buffers.empty() &&
2016 subs->m_buffers.front().end_display_time <
2017 currentFrame->m_timecode.count())
2018 {
2019 displaysub = false;
2020 }
2021
2022 if (displaysub && rect->type == SUBTITLE_BITMAP)
2023 {
2024 QRect display(rect->display_x, rect->display_y,
2025 rect->display_w, rect->display_h);
2026
2027 // XSUB and some DVD/DVB subs are based on the original video
2028 // size before the video was converted. We need to guess the
2029 // original size and allow for the difference
2030
2031 int right = rect->x + rect->w;
2032 int bottom = rect->y + rect->h;
2033 if (subs->m_fixPosition || (currentFrame->m_height < bottom) ||
2034 (currentFrame->m_width < right) ||
2035 !display.width() || !display.height())
2036 {
2037 int sd_height = 576;
2038 if ((m_player->GetFrameRate() > 26.0F ||
2039 m_player->GetFrameRate() < 24.0F) && bottom <= 480)
2040 sd_height = 480;
2041
2042 int height { 1080 };
2043 if ((currentFrame->m_height <= sd_height) &&
2044 (bottom <= sd_height))
2045 height = sd_height;
2046 else if ((currentFrame->m_height <= 720) && bottom <= 720)
2047 height = 720;
2048
2049 int width { 1920 };
2050 if ((currentFrame->m_width <= 720) && (right <= 720))
2051 width = 720;
2052 else if ((currentFrame->m_width <= 1280) &&
2053 (right <= 1280))
2054 width = 1280;
2055 display = QRect(0, 0, width, height);
2056 }
2057
2058 // split into upper/lower to allow zooming
2059 QRect bbox;
2060 int uh = (display.height() / 2) - rect->y;
2061 std::chrono::milliseconds displayuntil = currentFrame->m_timecode + displayfor;
2062 if (uh > 0)
2063 {
2064 bbox = QRect(0, 0, rect->w, uh);
2065 uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
2066 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2067 QString("avsub%1t").arg(i),
2068 displayuntil, late);
2069 }
2070 else
2071 {
2072 uh = 0;
2073 }
2074 int lh = rect->h - uh;
2075 if (lh > 0)
2076 {
2077 bbox = QRect(0, uh, rect->w, lh);
2078 DisplayScaledAVSubtitles(rect, bbox, false, display,
2079 rect->flags & AV_SUBTITLE_FLAG_FORCED,
2080 QString("avsub%1b").arg(i),
2081 displayuntil, late);
2082 }
2083 }
2084#if CONFIG_LIBASS
2085 else if (displaysub && rect->type == SUBTITLE_ASS)
2086 {
2087 InitialiseAssTrack(m_player->GetDecoder()->GetTrack(kTrackTypeSubtitle));
2088 AddAssEvent(rect->ass, subtitle.start_display_time, subtitle.end_display_time);
2089 }
2090#endif
2091 }
2093 }
2094#if CONFIG_LIBASS
2095 RenderAssTrack(currentFrame->m_timecode, assForceNext);
2096#endif
2097}
2098
2099int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
2100 QRect &bbox, bool top,
2101 QRect &display, int forced,
2102 const QString& imagename,
2103 std::chrono::milliseconds displayuntil,
2104 std::chrono::milliseconds late)
2105{
2106 // split image vertically if it spans middle of display
2107 // - split point is empty line nearest the middle
2108 // crop image to reduce scaling time
2109 bool prev_empty = false;
2110
2111 // initialize to opposite edges
2112 int xmin = bbox.right();
2113 int xmax = bbox.left();
2114 int ymin = bbox.bottom();
2115 int ymax = bbox.top();
2116 int ylast = bbox.top();
2117 int ysplit = bbox.bottom();
2118
2119 // find bounds of active image
2120 for (int y = bbox.top(); y <= bbox.bottom(); ++y)
2121 {
2122 if (y >= rect->h)
2123 {
2124 // end of image
2125 if (!prev_empty)
2126 ylast = y;
2127 break;
2128 }
2129
2130 bool empty = true;
2131 for (int x = bbox.left(); x <= bbox.right(); ++x)
2132 {
2133 const uint8_t color =
2134 rect->data[0][(y * rect->linesize[0]) + x];
2135 const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
2136 if (pixel & 0xff000000)
2137 {
2138 empty = false;
2139 xmin = std::min(x, xmin);
2140 xmax = std::max(x, xmax);
2141 }
2142 }
2143
2144 if (!empty)
2145 {
2146 ymin = std::min(y, ymin);
2147 ymax = std::max(y, ymax);
2148 }
2149 else if (!prev_empty)
2150 {
2151 // remember uppermost empty line
2152 ylast = y;
2153 }
2154 prev_empty = empty;
2155 }
2156
2157 if (ymax <= ymin)
2158 return 0;
2159
2160 if (top)
2161 {
2162 if (ylast < ymin)
2163 // no empty lines
2164 return 0;
2165
2166 if (ymax == bbox.bottom())
2167 {
2168 ymax = ylast;
2169 ysplit = ylast;
2170 }
2171 }
2172
2173 // set new bounds
2174 bbox.setLeft(xmin);
2175 bbox.setRight(xmax);
2176 bbox.setTop(ymin);
2177 bbox.setBottom(ymax);
2178
2179 // copy active region
2180 // AVSubtitleRect's image data's not guaranteed to be 4 byte
2181 // aligned.
2182
2183 QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
2184
2185 QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
2186 for (int y = 0; y < bbox.height(); ++y)
2187 {
2188 int ysrc = y + bbox.top();
2189 for (int x = 0; x < bbox.width(); ++x)
2190 {
2191 int xsrc = x + bbox.left();
2192 const uint8_t color =
2193 rect->data[0][(ysrc * rect->linesize[0]) + xsrc];
2194 const uint32_t pixel = *((uint32_t *)rect->data[1] + color);
2195 qImage.setPixel(x, y, pixel);
2196 }
2197 }
2198
2199 // translate to absolute coordinates
2200 bbox.translate(rect->x, rect->y);
2201
2202 // scale and move according to zoom factor
2203 bbox.setWidth(bbox.width() * m_textFontZoom / 100);
2204 bbox.setHeight(bbox.height() * m_textFontZoom / 100);
2205
2207 QRect scaled = videoOut->GetImageRect(bbox, &display);
2208
2209 if (scaled.size() != orig_rect.size())
2210 qImage = qImage.scaled(scaled.width(), scaled.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2211
2212 int hsize = m_safeArea.width();
2213 int vsize = m_safeArea.height();
2214
2215 scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 + m_textFontZoom * scaled.left()) / 100);
2216 if (top)
2217 {
2218 // anchor up
2219 scaled.moveTop(scaled.top() * m_textFontZoom / 100);
2220 }
2221 else
2222 {
2223 // anchor down
2224 scaled.moveTop(((100 - m_textFontZoom) * vsize + m_textFontZoom * scaled.top()) / 100);
2225 }
2226
2228 SubImage *uiimage = nullptr;
2229
2230 if (image)
2231 {
2232 image->Assign(qImage);
2233 uiimage = new SubImage(this, imagename, MythRect(scaled), displayuntil);
2234 if (uiimage)
2235 {
2236 uiimage->SetImage(image);
2237 uiimage->SetArea(MythRect(scaled));
2239 }
2240 image->DecrRef();
2241 image = nullptr;
2242 }
2243
2244 if (uiimage)
2245 {
2246 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Display %1AV sub until %2ms")
2247 .arg(forced ? "FORCED " : "").arg(displayuntil.count()));
2248 if (late > 50ms)
2249 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("AV Sub was %1ms late").arg(late.count()));
2250 }
2251
2252 return (ysplit + 1);
2253}
2254
2256{
2257 if (!m_player || !m_subreader)
2258 return;
2259
2260 std::chrono::milliseconds duration = 0ms;
2261 QStringList subs = m_subreader->GetRawTextSubtitles(duration);
2262 if (subs.empty())
2263 return;
2264
2266 if (!vo)
2267 return;
2268
2269 MythVideoFrame *currentFrame = vo->GetLastShownFrame();
2270 if (!currentFrame)
2271 return;
2272
2273 m_safeArea = vo->GetSafeRect();
2274
2275 // delete old subs that may still be on screen
2278 DrawTextSubtitles(subs, currentFrame->m_timecode, duration);
2279}
2280
2281void SubtitleScreen::DrawTextSubtitles(const QStringList &subs,
2282 std::chrono::milliseconds start,
2283 std::chrono::milliseconds duration)
2284{
2285 auto *fsub = new FormattedTextSubtitleSRT(m_family, m_safeArea, start,
2286 duration, this, subs);
2287 m_qInited.append(fsub);
2288}
2289
2291{
2292 if (!m_cc608reader)
2293 return;
2294
2295 bool changed = (m_textFontZoom != m_textFontZoomPrev);
2296
2297 if (!m_player || !m_player->GetVideoOutput())
2298 return;
2300
2301 CC608Buffer* textlist = m_cc608reader->GetOutputText(changed);
2302 if (!changed)
2303 return;
2304
2307
2308 if (!textlist)
2309 return;
2310
2311 QMutexLocker locker(&textlist->m_lock);
2312
2313 if (textlist->m_buffers.empty())
2314 return;
2315
2316 auto *fsub = new FormattedTextSubtitle608(textlist->m_buffers, m_family,
2317 m_safeArea, this/*, m_textFontZoom*/);
2318 m_qInited.append(fsub);
2319}
2320
2322{
2324 return;
2325
2326 CC708Service *cc708service = m_cc708reader->GetCurrentService();
2327 float video_aspect = m_player->GetVideoAspect();
2328 QRect oldsafe = m_safeArea;
2330 bool changed = (oldsafe != m_safeArea || m_textFontZoom != m_textFontZoomPrev);
2331 if (changed)
2332 {
2333 for (auto & window : cc708service->m_windows)
2334 window.SetChanged();
2335 }
2336
2337 uint64_t clearMask = 0;
2338 QList<FormattedTextSubtitle *> addList;
2339 for (int i = 0; i < k708MaxWindows; i++)
2340 {
2341 CC708Window &win = cc708service->m_windows[i];
2342 if (win.GetExists() && win.GetVisible() && !win.GetChanged())
2343 continue;
2344 if (!win.GetChanged())
2345 continue;
2346
2347 clearMask |= (1UL << i);
2348 win.ResetChanged();
2349 if (!win.GetExists() || !win.GetVisible())
2350 continue;
2351
2352 QMutexLocker locker(&win.m_lock);
2353 std::vector<CC708String*> list = win.GetStrings();
2354 if (!list.empty())
2355 {
2356 auto *fsub = new FormattedTextSubtitle708(win, i, list, m_family,
2357 m_safeArea, this, video_aspect);
2358 addList.append(fsub);
2359 }
2361 }
2362 if (clearMask)
2363 {
2364 Clear708Cache(clearMask);
2365 }
2366 if (!addList.empty())
2367 m_qInited.append(addList);
2368}
2369
2370void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
2371{
2373 if (!vo)
2374 return;
2375
2376 QRect scaled = vo->GetImageRect(pos);
2377 if (scaled.size() != pos.size())
2378 {
2379 img = img.scaled(scaled.width(), scaled.height(),
2380 Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
2381 }
2382
2384 if (image)
2385 {
2386 image->Assign(img);
2387 MythUIImage *uiimage = new SubImage(this, "dvd_button", MythRect(scaled), 0ms);
2388 if (uiimage)
2389 {
2390 uiimage->SetImage(image);
2391 uiimage->SetArea(MythRect(scaled));
2393 }
2394 image->DecrRef();
2395 }
2396}
2397
2398#if CONFIG_LIBASS
2399static void myth_libass_log(int level, const char *fmt, va_list vl, void */*ctx*/)
2400{
2401 uint64_t verbose_mask = VB_GENERAL;
2402 LogLevel_t verbose_level = LOG_INFO;
2403
2404 switch (level)
2405 {
2406 case 0: //MSGL_FATAL
2407 verbose_level = LOG_EMERG;
2408 break;
2409 case 1: //MSGL_ERR
2410 verbose_level = LOG_ERR;
2411 break;
2412 case 2: //MSGL_WARN
2413 verbose_level = LOG_WARNING;
2414 break;
2415 case 4: //MSGL_INFO
2416 verbose_level = LOG_INFO;
2417 break;
2418 case 6: //MSGL_V
2419 case 7: //MSGL_DBG2
2420 verbose_level = LOG_DEBUG;
2421 break;
2422 default:
2423 return;
2424 }
2425
2426 if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
2427 return;
2428
2429 static QMutex s_stringLock;
2430 s_stringLock.lock();
2431
2432 QString str = QString::vasprintf(fmt, vl);
2433 LOG(verbose_mask, verbose_level, QString("libass: %1").arg(str));
2434 s_stringLock.unlock();
2435}
2436
2437bool SubtitleScreen::InitialiseAssLibrary(void)
2438{
2439 if (m_assLibrary && m_assRenderer)
2440 return true;
2441
2442 if (!m_assLibrary)
2443 {
2444 m_assLibrary = ass_library_init();
2445 if (!m_assLibrary)
2446 return false;
2447
2448 ass_set_message_cb(m_assLibrary, myth_libass_log, nullptr);
2449 ass_set_extract_fonts(m_assLibrary, static_cast<int>(true));
2450 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
2451 }
2452
2453 LoadAssFonts();
2454
2455 if (!m_assRenderer)
2456 {
2457 m_assRenderer = ass_renderer_init(m_assLibrary);
2458 if (!m_assRenderer)
2459 return false;
2460
2461#ifdef Q_OS_ANDROID
2462 // fontconfig doesn't yet work for us on Android. For the
2463 // time being, more explicitly set a font we know should
2464 // exist. This was adapted from VLC master as of 2019-01-21.
2465 const char *psz_font = "/system/fonts/DroidSans.ttf";
2466 const char *psz_font_family = "Droid Sans";
2467#else
2468 const char *psz_font = nullptr;
2469 const char *psz_font_family = "sans-serif";
2470#endif
2471 ass_set_fonts(m_assRenderer, psz_font, psz_font_family, 1, nullptr, 1);
2472 ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
2473 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
2474 }
2475
2476 return true;
2477}
2478
2479void SubtitleScreen::LoadAssFonts(void)
2480{
2481 if (!m_assLibrary || !m_player)
2482 return;
2483
2485 if (m_assFontCount == count)
2486 return;
2487
2488 ass_clear_fonts(m_assLibrary);
2489 m_assFontCount = 0;
2490
2491 // TODO these need checking and/or reinitialising after a stream change
2492 for (uint i = 0; i < count; ++i)
2493 {
2494 QByteArray filename;
2495 QByteArray font;
2497 ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
2498 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
2499 .arg(filename.constData()));
2500 m_assFontCount++;
2501 }
2502}
2503
2504void SubtitleScreen::CleanupAssLibrary(void)
2505{
2506 CleanupAssTrack();
2507
2508 if (m_assRenderer)
2509 ass_renderer_done(m_assRenderer);
2510 m_assRenderer = nullptr;
2511
2512 if (m_assLibrary)
2513 {
2514 ass_clear_fonts(m_assLibrary);
2515 m_assFontCount = 0;
2516 ass_library_done(m_assLibrary);
2517 }
2518 m_assLibrary = nullptr;
2519}
2520
2521void SubtitleScreen::InitialiseAssTrack(int tracknum)
2522{
2523 if (!InitialiseAssLibrary() || !m_player)
2524 return;
2525
2526 if (tracknum == m_assTrackNum && m_assTrack)
2527 return;
2528
2529 LoadAssFonts();
2530 CleanupAssTrack();
2531 m_assTrack = ass_new_track(m_assLibrary);
2532 m_assTrackNum = tracknum;
2533
2534 QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
2535 if (header.isNull())
2536 {
2538 if (parser)
2539 header = parser->GetSubHeader();
2540 }
2541 if (!header.isNull())
2542 ass_process_codec_private(m_assTrack, header.data(), header.size());
2543
2545 ResizeAssRenderer();
2546}
2547
2548void SubtitleScreen::CleanupAssTrack(void)
2549{
2550 if (m_assTrack)
2551 ass_free_track(m_assTrack);
2552 m_assTrack = nullptr;
2553}
2554
2555void SubtitleScreen::AddAssEvent(char *event, uint32_t starttime, uint32_t endtime)
2556{
2557 if (m_assTrack && event)
2558 ass_process_chunk(m_assTrack, event, strlen(event), starttime, endtime-starttime);
2559}
2560
2561void SubtitleScreen::ResizeAssRenderer(void)
2562{
2563 ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
2564 ass_set_margins(m_assRenderer, 0, 0, 0, 0);
2565 ass_set_use_margins(m_assRenderer, static_cast<int>(true));
2566 ass_set_font_scale(m_assRenderer, 1.0);
2567}
2568
2569void SubtitleScreen::RenderAssTrack(std::chrono::milliseconds timecode, bool force)
2570{
2571 if (!m_player || !m_assRenderer || !m_assTrack)
2572 return;
2573
2575 if (!vo )
2576 return;
2577
2578 QRect oldscreen = m_safeArea;
2580 if (oldscreen != m_safeArea)
2581 ResizeAssRenderer();
2582
2583 int changed = 0;
2584 ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack, timecode.count(), &changed);
2585 if (!changed && !force)
2586 return;
2587
2588 int count = 0;
2591 while (images)
2592 {
2593 if (images->w == 0 || images->h == 0)
2594 {
2595 images = images->next;
2596 continue;
2597 }
2598
2599 uint8_t alpha = images->color & 0xFF;
2600 uint8_t blue = images->color >> 8 & 0xFF;
2601 uint8_t green = images->color >> 16 & 0xFF;
2602 uint8_t red = images->color >> 24 & 0xFF;
2603
2604 if (alpha == 255)
2605 {
2606 images = images->next;
2607 continue;
2608 }
2609
2610 QSize img_size(images->w, images->h);
2611 QRect img_rect(images->dst_x,images->dst_y,
2612 images->w, images->h);
2613 QImage qImage(img_size, QImage::Format_ARGB32);
2614 qImage.fill(0x00000000);
2615
2616 unsigned char *src = images->bitmap;
2617 for (int y = 0; y < images->h; ++y)
2618 {
2619 for (int x = 0; x < images->w; ++x)
2620 {
2621 uint8_t value = src[x];
2622 if (value)
2623 {
2624 uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
2625 (red << 16) | (green << 8) | blue;
2626 qImage.setPixel(x, y, pixel);
2627 }
2628 }
2629 src += images->stride;
2630 }
2631
2633 SubImage *uiimage = nullptr;
2634
2635 if (image)
2636 {
2637 image->Assign(qImage);
2638 QString name = QString("asssub%1").arg(count);
2639 uiimage = new SubImage(this, name, MythRect(img_rect), 0ms);
2640 if (uiimage)
2641 {
2642 uiimage->SetImage(image);
2643 uiimage->SetArea(MythRect(img_rect));
2645 }
2646 image->DecrRef();
2647 }
2648
2649 images = images->next;
2650 count++;
2651 }
2652}
2653#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:38
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:169
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)
void AddChild(MythUIType *child)
Add a child UIType.
Definition: mythuitype.cpp:80
virtual void SetVisible(bool visible)
virtual void SetArea(const MythRect &rect)
Definition: mythuitype.cpp:595
void SetRedraw(void)
Definition: mythuitype.cpp:298
virtual void Pulse(void)
Pulse is called 70 times a second to trigger a single frame of an animation.
Definition: mythuitype.cpp:441
void DeleteAllChildren(void)
Delete all child widgets.
Definition: mythuitype.cpp:207
MythPainter * m_painter
Definition: mythuitype.h:298
void DeleteChild(const QString &name)
Delete a named child of this UIType.
Definition: mythuitype.cpp:144
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:60
@ kTrackTypeSubtitle
Definition: decoderbase.h:31
@ kTrackTypeAttachment
Definition: decoderbase.h:37
unsigned int prevY
Definition: filters.cpp:92
static uint32_t * tmp
Definition: goom_core.cpp:28
static 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: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