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