MythTV  0.26-pre
subtitlescreen.cpp
Go to the documentation of this file.
00001 #include <QFontMetrics>
00002 
00003 #include "mythlogging.h"
00004 #include "mythfontproperties.h"
00005 #include "mythuisimpletext.h"
00006 #include "mythuishape.h"
00007 #include "mythuiimage.h"
00008 #include "mythpainter.h"
00009 #include "subtitlescreen.h"
00010 
00011 #define LOC      QString("Subtitles: ")
00012 #define LOC_WARN QString("Subtitles Warning: ")
00013 
00014 // Class SubtitleFormat manages fonts and backgrounds for subtitles.
00015 //
00016 // Formatting is specified by the theme in the new file
00017 // osd_subtitle.xml, in the osd_subtitle window.  Subtitle types are
00018 // text, teletext, 608, 708_0, 708_1, ..., 708_7.  Each subtitle type
00019 // has a fontdef component for the text and a shape component for the
00020 // background.  By default, the attributes of a subtitle type come
00021 // from either the provider (e.g. font color, italics) or the system
00022 // default (e.g. font family).  These attributes can be overridden by
00023 // the xml file.
00024 //
00025 // The fontdef name and the background shape name are both simply the
00026 // name of the subtitle type.  The fontdef and shape should ultimately
00027 // inherit from the special value "provider", otherwise all provider
00028 // values will be ignored.
00029 //
00030 // The following example forces .srt subtitles to be rendered in
00031 // yellow text, FreeSans font, black shadow, with a translucent black
00032 // background.  The fontdef and shape names are "text" since the
00033 // subtitles come from a .srt file.  Note that in this example, color
00034 // formatting controls in the .srt file will be ignored due to the
00035 // explicit setting of yellow text.
00036 //
00037 // <?xml version="1.0" encoding="utf-8"?>
00038 // <!DOCTYPE mythuitheme SYSTEM "http://www.mythtv.org/schema/mythuitheme.dtd">
00039 // <mythuitheme>
00040 //   <window name="osd_subtitle">
00041 //     <fontdef name="text" face="FreeSans" from="provider">
00042 //       <color>#FFFF00</color>
00043 //       <shadowoffset>2,2</shadowoffset>
00044 //       <shadowcolor>#000000</shadowcolor>
00045 //     </fontdef>
00046 //     <shape name="text" from="provider">
00047 //       <fill color="#000000" alpha="100" />
00048 //     </shape>
00049 //   </window>
00050 // </mythuitheme>
00051 //
00052 // All elements/attributes of fontdef and shape can be used.  Note
00053 // however that area and position are explicitly ignored, as these are
00054 // dictated by the subtitle layout.
00055 //
00056 // This is implemented with almost no MythUI changes.  Two copies of a
00057 // "provider" object are created, with default/representative provider
00058 // attributes.  One copy is then "complemented" to have a different
00059 // value for each attribute that a provider might change.  The
00060 // osd_subtitle.xml file is loaded twice, once with respect to each
00061 // provider object, and the desired fontdef or shape is looked up.
00062 // The two fontdefs or shapes are compared attribute by attribute, and
00063 // each attribute that is different is an attribute that the provider
00064 // may modify, whereas each identical attribute represents one that is
00065 // fixed by the theme.
00066 class SubtitleFormat
00067 {
00068 public:
00069     SubtitleFormat(void) {}
00070     ~SubtitleFormat(void);
00071     MythFontProperties *GetFont(const QString &family,
00072                                 const CC708CharacterAttribute &attr,
00073                                 int pixelSize, bool isTeletext,
00074                                 int zoom, int stretch);
00075     MythUIShape *GetBackground(MythUIType *parent, const QString &name,
00076                                const QString &family,
00077                                const CC708CharacterAttribute &attr);
00078     static QString MakePrefix(const QString &family,
00079                               const CC708CharacterAttribute &attr);
00080     bool IsUnlocked(const QString &prefix, const QString &property) const
00081     {
00082         return m_changeMap[prefix].contains(property);
00083     }
00084 private:
00085     void Load(const QString &family,
00086               const CC708CharacterAttribute &attr);
00087     static void CreateProviderDefault(const QString &family,
00088                                       const CC708CharacterAttribute &attr,
00089                                       MythUIType *parent,
00090                                       bool isComplement,
00091                                       MythFontProperties **font,
00092                                       MythUIShape **bg);
00093     static void Complement(MythFontProperties *font, MythUIShape *bg);
00094     static QSet<QString> Diff(const QString &family,
00095                               const CC708CharacterAttribute &attr,
00096                               MythFontProperties *font1,
00097                               MythFontProperties *font2,
00098                               MythUIShape *bg1,
00099                               MythUIShape *bg2);
00100 
00101     QHash<QString, MythFontProperties *> m_fontMap;
00102     QHash<QString, MythUIShape *>       m_shapeMap;
00103     QHash<QString, QSet<QString> >     m_changeMap;
00104     QHash<QString, int>             m_pixelSizeMap;
00105     QVector<MythUIType *> m_cleanup;
00106 };
00107 
00108 static const QString kSubProvider("provider");
00109 static const QString kSubFileName  ("osd_subtitle.xml");
00110 static const QString kSubWindowName("osd_subtitle");
00111 static const QString kSubFamily608     ("608");
00112 static const QString kSubFamily708     ("708");
00113 static const QString kSubFamilyText    ("text");
00114 static const QString kSubFamilyAV      ("AV");
00115 static const QString kSubFamilyTeletext("teletext");
00116 
00117 static const QString kSubAttrItalics  ("italics");
00118 static const QString kSubAttrBold     ("bold");
00119 static const QString kSubAttrUnderline("underline");
00120 static const QString kSubAttrPixelsize("pixelsize");
00121 static const QString kSubAttrColor    ("color");
00122 static const QString kSubAttrBGfill   ("bgfill");
00123 static const QString kSubAttrShadow      ("shadow");
00124 static const QString kSubAttrShadowoffset("shadowoffset");
00125 static const QString kSubAttrShadowcolor ("shadowcolor");
00126 static const QString kSubAttrShadowalpha ("shadowalpha");
00127 static const QString kSubAttrOutline     ("outline");
00128 static const QString kSubAttrOutlinecolor("outlinecolor");
00129 static const QString kSubAttrOutlinesize ("outlinesize");
00130 static const QString kSubAttrOutlinealpha("outlinealpha");
00131 
00132 static QString srtColorString(QColor color);
00133 static QString fontToString(MythFontProperties *f)
00134 {
00135     QString result;
00136     result = QString("face=%1 pixelsize=%2 color=%3 "
00137                      "italics=%4 weight=%5 underline=%6")
00138         .arg(f->GetFace()->family())
00139         .arg(f->GetFace()->pixelSize())
00140         .arg(srtColorString(f->color()))
00141         .arg(f->GetFace()->italic())
00142         .arg(f->GetFace()->weight())
00143         .arg(f->GetFace()->underline());
00144     QPoint offset;
00145     QColor color;
00146     int alpha;
00147     int size;
00148     f->GetShadow(offset, color, alpha);
00149     result += QString(" shadow=%1 shadowoffset=%2 "
00150                       "shadowcolor=%3 shadowalpha=%4")
00151         .arg(f->hasShadow())
00152         .arg(QString("(%1,%2)").arg(offset.x()).arg(offset.y()))
00153         .arg(srtColorString(color))
00154         .arg(alpha);
00155     f->GetOutline(color, size, alpha);
00156     result += QString(" outline=%1 outlinecolor=%2 "
00157                       "outlinesize=%3 outlinealpha=%4")
00158         .arg(f->hasOutline())
00159         .arg(srtColorString(color))
00160         .arg(size)
00161         .arg(alpha);
00162     return result;
00163 }
00164 
00165 SubtitleFormat::~SubtitleFormat(void)
00166 {
00167     for (int i = 0; i < m_cleanup.size(); ++i)
00168     {
00169         m_cleanup[i]->DeleteAllChildren();
00170         delete m_cleanup[i];
00171         m_cleanup[i] = NULL; // just to be safe
00172     }
00173 }
00174 
00175 QString SubtitleFormat::MakePrefix(const QString &family,
00176                                    const CC708CharacterAttribute &attr)
00177 {
00178     if (family == kSubFamily708)
00179         return family + "_" + QString::number(attr.font_tag & 0x7);
00180     else
00181         return family;
00182 }
00183 
00184 void SubtitleFormat::CreateProviderDefault(const QString &family,
00185                                            const CC708CharacterAttribute &attr,
00186                                            MythUIType *parent,
00187                                            bool isComplement,
00188                                            MythFontProperties **returnFont,
00189                                            MythUIShape **returnBg)
00190 {
00191     MythFontProperties *font = new MythFontProperties();
00192     MythUIShape *bg = new MythUIShape(parent, kSubProvider);
00193     if (family == kSubFamily608)
00194     {
00195         font->GetFace()->setFamily("FreeMono");
00196         bg->SetFillBrush(QBrush(Qt::black));
00197     }
00198     else if (family == kSubFamily708)
00199     {
00200         static const char *cc708Fonts[] = {
00201             "FreeMono",        // default
00202             "FreeMono",        // mono serif
00203             "DejaVu Serif",    // prop serif
00204             "Droid Sans Mono", // mono sans
00205             "Liberation Sans", // prop sans
00206             "Purisa",          // casual
00207             "URW Chancery L",  // cursive
00208             "Impact"           // capitals
00209         };
00210         font->GetFace()->setFamily(cc708Fonts[attr.font_tag & 0x7]);
00211     }
00212     else if (family == kSubFamilyText)
00213     {
00214         font->GetFace()->setFamily("Droid Sans");
00215         bg->SetFillBrush(QBrush(Qt::black));
00216     }
00217     else if (family == kSubFamilyTeletext)
00218     {
00219         font->GetFace()->setFamily("FreeMono");
00220     }
00221     font->GetFace()->setPixelSize(10);
00222 
00223     if (isComplement)
00224         Complement(font, bg);
00225     parent->AddFont(kSubProvider, font);
00226 
00227     *returnFont = font;
00228     *returnBg = bg;
00229 }
00230 
00231 static QColor differentColor(const QColor &color)
00232 {
00233     return color == Qt::white ? Qt::black : Qt::white;
00234 }
00235 
00236 // Change everything (with respect to the == operator) that the
00237 // provider might define or override.
00238 void SubtitleFormat::Complement(MythFontProperties *font, MythUIShape *bg)
00239 {
00240     QPoint offset;
00241     QColor color;
00242     int alpha;
00243     int size;
00244     QFont *face = font->GetFace();
00245     face->setItalic(!face->italic());
00246     face->setPixelSize(face->pixelSize() + 1);
00247     face->setUnderline(!face->underline());
00248     face->setWeight((face->weight() + 1) % 32);
00249     font->SetColor(differentColor(font->color()));
00250 
00251     font->GetShadow(offset, color, alpha);
00252     offset.setX(offset.x() + 1);
00253     font->SetShadow(!font->hasShadow(), offset, differentColor(color),
00254                     255 - alpha);
00255 
00256     font->GetOutline(color, size, alpha);
00257     font->SetOutline(!font->hasOutline(), differentColor(color),
00258                      size + 1, 255 - alpha);
00259 
00260     bg->SetFillBrush(bg->m_fillBrush == Qt::NoBrush ?
00261                      Qt::SolidPattern : Qt::NoBrush);
00262 }
00263 
00264 void SubtitleFormat::Load(const QString &family,
00265                           const CC708CharacterAttribute &attr)
00266 {
00267     // Widgets for the actual values
00268     MythUIType *baseParent = new MythUIType(NULL, "base");
00269     m_cleanup += baseParent;
00270     MythFontProperties *providerBaseFont;
00271     MythUIShape *providerBaseShape;
00272     CreateProviderDefault(family, attr, baseParent, false,
00273                           &providerBaseFont, &providerBaseShape);
00274 
00275     // Widgets for the "negative" values
00276     MythUIType *negParent = new MythUIType(NULL, "base");
00277     m_cleanup += negParent;
00278     MythFontProperties *negFont;
00279     MythUIShape *negBG;
00280     CreateProviderDefault(family, attr, negParent, true, &negFont, &negBG);
00281 
00282     bool posResult =
00283         XMLParseBase::LoadWindowFromXML(kSubFileName, kSubWindowName,
00284                                         baseParent);
00285     bool negResult =
00286         XMLParseBase::LoadWindowFromXML(kSubFileName, kSubWindowName,
00287                                         negParent);
00288     if (!posResult || !negResult)
00289         LOG(VB_VBI, LOG_INFO,
00290             QString("Couldn't load theme file %1").arg(kSubFileName));
00291     QString prefix = MakePrefix(family, attr);
00292     MythFontProperties *resultFont = baseParent->GetFont(prefix);
00293     if (!resultFont)
00294         resultFont = providerBaseFont;
00295     MythUIShape *resultBG =
00296         dynamic_cast<MythUIShape *>(baseParent->GetChild(prefix));
00297     if (!resultBG)
00298         resultBG = providerBaseShape;
00299     MythFontProperties *testFont = negParent->GetFont(prefix);
00300     if (!testFont)
00301         testFont = negFont;
00302     MythUIShape *testBG =
00303         dynamic_cast<MythUIShape *>(negParent->GetChild(prefix));
00304     if (!testBG)
00305         testBG = negBG;
00306     m_fontMap[prefix] = resultFont;
00307     m_shapeMap[prefix] = resultBG;
00308     LOG(VB_VBI, LOG_DEBUG,
00309         QString("providerBaseFont = %1").arg(fontToString(providerBaseFont)));
00310     LOG(VB_VBI, LOG_DEBUG,
00311         QString("negFont = %1").arg(fontToString(negFont)));
00312     LOG(VB_VBI, LOG_DEBUG,
00313         QString("resultFont = %1").arg(fontToString(resultFont)));
00314     LOG(VB_VBI, LOG_DEBUG,
00315         QString("testFont = %1").arg(fontToString(testFont)));
00316     m_changeMap[prefix] = Diff(family, attr, resultFont, testFont,
00317                                resultBG, testBG);
00318     if (!IsUnlocked(prefix, kSubAttrPixelsize))
00319         m_pixelSizeMap[prefix] = resultFont->GetFace()->pixelSize();
00320 
00321     delete negFont;
00322 }
00323 
00324 QSet<QString> SubtitleFormat::Diff(const QString &family,
00325                                    const CC708CharacterAttribute &attr,
00326                                    MythFontProperties *font1,
00327                                    MythFontProperties *font2,
00328                                    MythUIShape *bg1,
00329                                    MythUIShape *bg2)
00330 {
00331     bool is708 = (family == kSubFamily708);
00332     QSet<QString> result;
00333     QFont *face1 = font1->GetFace();
00334     QFont *face2 = font2->GetFace();
00335     if (face1->italic() != face2->italic())
00336         result << kSubAttrItalics;
00337     if (face1->weight() != face2->weight())
00338         result << kSubAttrBold;
00339     if (face1->underline() != face2->underline())
00340         result << kSubAttrUnderline;
00341     if (face1->pixelSize() != face2->pixelSize())
00342         result << kSubAttrPixelsize;
00343     if (font1->color() != font2->color())
00344         result << kSubAttrColor;
00345     if (is708 && font1->hasShadow() != font2->hasShadow())
00346     {
00347         result << kSubAttrShadow;
00348         QPoint offset1, offset2;
00349         QColor color1, color2;
00350         int alpha1, alpha2;
00351         font1->GetShadow(offset1, color1, alpha1);
00352         font2->GetShadow(offset2, color2, alpha2);
00353         if (offset1 != offset2)
00354             result << kSubAttrShadowoffset;
00355         if (color1 != color2)
00356             result << kSubAttrShadowcolor;
00357         if (alpha1 != alpha2)
00358             result << kSubAttrShadowalpha;
00359     }
00360     if (is708 && font1->hasOutline() != font2->hasOutline())
00361     {
00362         result << kSubAttrOutline;
00363         QColor color1, color2;
00364         int size1, size2;
00365         int alpha1, alpha2;
00366         font1->GetOutline(color1, size1, alpha1);
00367         font2->GetOutline(color2, size2, alpha2);
00368         if (color1 != color2)
00369             result << kSubAttrOutlinecolor;
00370         if (size1 != size2)
00371             result << kSubAttrOutlinesize;
00372         if (alpha1 != alpha2)
00373             result << kSubAttrOutlinealpha;
00374     }
00375     if (bg1->m_fillBrush != bg2->m_fillBrush)
00376         result << kSubAttrBGfill;
00377 
00378     QString values = "";
00379     QSet<QString>::const_iterator i = result.constBegin();
00380     for (; i != result.constEnd(); ++i)
00381         values += " " + (*i);
00382     LOG(VB_VBI, LOG_INFO,
00383         QString("Subtitle family %1 allows provider to change:%2")
00384         .arg(MakePrefix(family, attr)).arg(values));
00385 
00386     return result;
00387 }
00388 
00389 MythFontProperties *
00390 SubtitleFormat::GetFont(const QString &family,
00391                         const CC708CharacterAttribute &attr,
00392                         int pixelSize, bool isTeletext,
00393                         int zoom, int stretch)
00394 {
00395     int origPixelSize = pixelSize;
00396     float scale = zoom / 100.0;
00397     if (isTeletext)
00398         scale = scale * 17 / 25;
00399     if ((attr.pen_size & 0x3) == k708AttrSizeSmall)
00400         scale = scale * 32 / 42;
00401     else if ((attr.pen_size & 0x3) == k708AttrSizeLarge)
00402         scale = scale * 42 / 32;
00403 
00404     QString prefix = MakePrefix(family, attr);
00405     if (!m_fontMap.contains(prefix))
00406         Load(family, attr);
00407     MythFontProperties *result = m_fontMap[prefix];
00408 
00409     // Apply the scaling factor to pixelSize even if the theme
00410     // explicitly sets pixelSize.
00411     if (!IsUnlocked(prefix, kSubAttrPixelsize))
00412         pixelSize = m_pixelSizeMap[prefix];
00413     pixelSize *= scale;
00414     result->GetFace()->setPixelSize(pixelSize);
00415 
00416     result->GetFace()->setStretch(stretch);
00417     if (IsUnlocked(prefix, kSubAttrItalics))
00418         result->GetFace()->setItalic(attr.italics);
00419     if (IsUnlocked(prefix, kSubAttrUnderline))
00420         result->GetFace()->setUnderline(attr.underline);
00421     if (IsUnlocked(prefix, kSubAttrBold))
00422         result->GetFace()->setBold(attr.boldface);
00423     if (IsUnlocked(prefix, kSubAttrColor))
00424         result->SetColor(attr.GetFGColor());
00425     if (IsUnlocked(prefix, kSubAttrShadow))
00426     {
00427         QPoint offset;
00428         QColor color;
00429         int alpha;
00430         bool shadow = result->hasShadow();
00431         result->GetShadow(offset, color, alpha);
00432         if (IsUnlocked(prefix, kSubAttrShadowcolor))
00433             color = attr.GetEdgeColor();
00434         if (IsUnlocked(prefix, kSubAttrShadowalpha))
00435             alpha = attr.GetFGAlpha();
00436         if (IsUnlocked(prefix, kSubAttrShadowoffset))
00437         {
00438             int off = pixelSize / 20;
00439             offset = QPoint(off, off);
00440             if (attr.edge_type == k708AttrEdgeLeftDropShadow)
00441             {
00442                 shadow = true;
00443                 offset.setX(-off);
00444             }
00445             else if (attr.edge_type == k708AttrEdgeRightDropShadow)
00446                 shadow = true;
00447             else
00448                 shadow = false;
00449         }
00450         result->SetShadow(shadow, offset, color, alpha);
00451     }
00452     if (IsUnlocked(prefix, kSubAttrOutline))
00453     {
00454         QColor color;
00455         int off;
00456         int alpha;
00457         bool outline = result->hasOutline();
00458         result->GetOutline(color, off, alpha);
00459         if (IsUnlocked(prefix, kSubAttrOutlinecolor))
00460             color = attr.GetEdgeColor();
00461         if (IsUnlocked(prefix, kSubAttrOutlinealpha))
00462             alpha = attr.GetFGAlpha();
00463         if (IsUnlocked(prefix, kSubAttrOutlinesize))
00464         {
00465             if (attr.edge_type == k708AttrEdgeUniform ||
00466                 attr.edge_type == k708AttrEdgeRaised  ||
00467                 attr.edge_type == k708AttrEdgeDepressed)
00468             {
00469                 outline = true;
00470                 off = pixelSize / 20;
00471             }
00472             else
00473                 outline = false;
00474         }
00475         result->SetOutline(outline, color, off, alpha);
00476     }
00477     LOG(VB_VBI, LOG_DEBUG,
00478         QString("GetFont(family=%1, prefix=%2, orig pixelSize=%3, "
00479                 "new pixelSize=%4 zoom=%5) = %6")
00480         .arg(family).arg(prefix).arg(origPixelSize).arg(pixelSize)
00481         .arg(zoom).arg(fontToString(result)));
00482     return result;
00483 }
00484 
00485 MythUIShape *
00486 SubtitleFormat::GetBackground(MythUIType *parent, const QString &name,
00487                               const QString &family,
00488                               const CC708CharacterAttribute &attr)
00489 {
00490     QString prefix = MakePrefix(family, attr);
00491     if (!m_shapeMap.contains(prefix))
00492         Load(family, attr);
00493     MythUIShape *result = new MythUIShape(parent, name);
00494     result->CopyFrom(m_shapeMap[prefix]);
00495     if (family == kSubFamily708)
00496     {
00497         if (IsUnlocked(prefix, kSubAttrBGfill))
00498         {
00499             result->SetFillBrush(QBrush(attr.GetBGColor()));
00500         }
00501     }
00502     else if (family == kSubFamilyTeletext)
00503     {
00504         // add code here when teletextscreen.cpp is refactored
00505     }
00506     LOG(VB_VBI, LOG_DEBUG,
00507         QString("GetBackground(prefix=%1) = "
00508                 "{type=%2 alpha=%3 brushstyle=%4 brushcolor=%5}")
00509         .arg(prefix).arg(result->m_type).arg(result->GetAlpha())
00510         .arg(result->m_fillBrush.style())
00511         .arg(srtColorString(result->m_fillBrush.color())));
00512     return result;
00513 }
00514 
00516 
00517 static const float PAD_WIDTH  = 0.5;
00518 static const float PAD_HEIGHT = 0.04;
00519 
00520 // Normal font height is designed to be 1/20 of safe area height, with
00521 // extra blank space between lines to make 17 lines within the safe
00522 // area.
00523 static const float LINE_SPACING = (20.0 / 17.0);
00524 
00525 SubtitleScreen::SubtitleScreen(MythPlayer *player, const char * name,
00526                                int fontStretch) :
00527     MythScreenType((MythScreenType*)NULL, name),
00528     m_player(player),  m_subreader(NULL),   m_608reader(NULL),
00529     m_708reader(NULL), m_safeArea(QRect()),
00530     m_removeHTML(QRegExp("</?.+>")),        m_subtitleType(kDisplayNone),
00531     m_textFontZoom(100), m_textFontZoomPrev(100), m_refreshArea(false),
00532     m_fontStretch(fontStretch),
00533     m_format(new SubtitleFormat)
00534 {
00535     m_removeHTML.setMinimal(true);
00536 
00537 #ifdef USING_LIBASS
00538     m_assLibrary   = NULL;
00539     m_assRenderer  = NULL;
00540     m_assTrackNum  = -1;
00541     m_assTrack     = NULL;
00542     m_assFontCount = 0;
00543 #endif
00544 }
00545 
00546 SubtitleScreen::~SubtitleScreen(void)
00547 {
00548     ClearAllSubtitles();
00549     delete m_format;
00550 #ifdef USING_LIBASS
00551     CleanupAssLibrary();
00552 #endif
00553 }
00554 
00555 void SubtitleScreen::EnableSubtitles(int type, bool forced_only)
00556 {
00557     if (forced_only)
00558     {
00559         SetVisible(true);
00560         SetArea(MythRect());
00561         return;
00562     }
00563 
00564     m_subtitleType = type;
00565     if (m_subreader)
00566     {
00567         m_subreader->EnableAVSubtitles(kDisplayAVSubtitle == m_subtitleType);
00568         m_subreader->EnableTextSubtitles(kDisplayTextSubtitle == m_subtitleType);
00569         m_subreader->EnableRawTextSubtitles(kDisplayRawTextSubtitle == m_subtitleType);
00570     }
00571     if (m_608reader)
00572         m_608reader->SetEnabled(kDisplayCC608 == m_subtitleType);
00573     if (m_708reader)
00574         m_708reader->SetEnabled(kDisplayCC708 == m_subtitleType);
00575     ClearAllSubtitles();
00576     SetVisible(m_subtitleType != kDisplayNone);
00577     SetArea(MythRect());
00578     m_textFontZoom  = gCoreContext->GetNumSetting("OSDCC708TextZoom", 100);
00579     switch (m_subtitleType)
00580     {
00581     case kDisplayTextSubtitle:
00582     case kDisplayRawTextSubtitle:
00583         m_family = kSubFamilyText;
00584         break;
00585     case kDisplayCC608:
00586         m_family = kSubFamily608;
00587         break;
00588     case kDisplayCC708:
00589         m_family = kSubFamily708;
00590         break;
00591     case kDisplayAVSubtitle:
00592         m_family = kSubFamilyAV;
00593         m_textFontZoom = gCoreContext->GetNumSetting("OSDAVSubZoom", 100);
00594         break;
00595     }
00596     m_textFontZoomPrev = m_textFontZoom;
00597 }
00598 
00599 void SubtitleScreen::DisableForcedSubtitles(void)
00600 {
00601     if (kDisplayNone != m_subtitleType)
00602         return;
00603     ClearAllSubtitles();
00604     SetVisible(false);
00605     SetArea(MythRect());
00606 }
00607 
00608 bool SubtitleScreen::Create(void)
00609 {
00610     if (!m_player)
00611         return false;
00612 
00613     m_subreader = m_player->GetSubReader();
00614     m_608reader = m_player->GetCC608Reader();
00615     m_708reader = m_player->GetCC708Reader();
00616     if (!m_subreader)
00617         LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get subtitle reader.");
00618     if (!m_608reader)
00619         LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-608 reader.");
00620     if (!m_708reader)
00621         LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to get CEA-708 reader.");
00622 
00623     return true;
00624 }
00625 
00626 void SubtitleScreen::Pulse(void)
00627 {
00628     ExpireSubtitles();
00629 
00630     DisplayAVSubtitles(); // allow forced subtitles to work
00631 
00632     if (kDisplayTextSubtitle == m_subtitleType)
00633         DisplayTextSubtitles();
00634     else if (kDisplayCC608 == m_subtitleType)
00635         DisplayCC608Subtitles();
00636     else if (kDisplayCC708 == m_subtitleType)
00637         DisplayCC708Subtitles();
00638     else if (kDisplayRawTextSubtitle == m_subtitleType)
00639         DisplayRawTextSubtitles();
00640 
00641     OptimiseDisplayedArea();
00642     MythScreenType::Pulse();
00643     m_refreshArea = false;
00644     m_textFontZoomPrev = m_textFontZoom;
00645 }
00646 
00647 void SubtitleScreen::ClearAllSubtitles(void)
00648 {
00649     ClearNonDisplayedSubtitles();
00650     ClearDisplayedSubtitles();
00651 #ifdef USING_LIBASS
00652     if (m_assTrack)
00653         ass_flush_events(m_assTrack);
00654 #endif
00655 }
00656 
00657 void SubtitleScreen::ClearNonDisplayedSubtitles(void)
00658 {
00659     if (m_subreader && (kDisplayAVSubtitle == m_subtitleType))
00660         m_subreader->ClearAVSubtitles();
00661     if (m_subreader && (kDisplayRawTextSubtitle == m_subtitleType))
00662         m_subreader->ClearRawTextSubtitles();
00663     if (m_608reader && (kDisplayCC608 == m_subtitleType))
00664         m_608reader->ClearBuffers(true, true);
00665     if (m_708reader && (kDisplayCC708 == m_subtitleType))
00666         m_708reader->ClearBuffers();
00667 }
00668 
00669 void SubtitleScreen::ClearDisplayedSubtitles(void)
00670 {
00671     for (int i = 0; i < 8; i++)
00672         Clear708Cache(i);
00673     DeleteAllChildren();
00674     m_expireTimes.clear();
00675     m_avsubCache.clear();
00676     SetRedraw();
00677 }
00678 
00679 void SubtitleScreen::ExpireSubtitles(void)
00680 {
00681     VideoOutput    *videoOut = m_player->GetVideoOutput();
00682     VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : NULL;
00683     long long now = currentFrame ? currentFrame->timecode : LLONG_MAX;
00684     QMutableHashIterator<MythUIType*, long long> it(m_expireTimes);
00685     while (it.hasNext())
00686     {
00687         it.next();
00688         if (it.value() < now)
00689         {
00690             m_avsubCache.remove(it.key());
00691             DeleteChild(it.key());
00692             it.remove();
00693             SetRedraw();
00694         }
00695     }
00696 }
00697 
00698 void SubtitleScreen::OptimiseDisplayedArea(void)
00699 {
00700     if (!m_refreshArea)
00701         return;
00702 
00703     QRegion visible;
00704     QListIterator<MythUIType *> i(m_ChildrenList);
00705     while (i.hasNext())
00706     {
00707         MythUIType *img = i.next();
00708         visible = visible.united(img->GetArea());
00709     }
00710 
00711     if (visible.isEmpty())
00712         return;
00713 
00714     QRect bounding  = visible.boundingRect();
00715     bounding = bounding.translated(m_safeArea.topLeft());
00716     bounding = m_safeArea.intersected(bounding);
00717     int left = m_safeArea.left() - bounding.left();
00718     int top  = m_safeArea.top()  - bounding.top();
00719     SetArea(MythRect(bounding));
00720 
00721     i.toFront();
00722     while (i.hasNext())
00723     {
00724         MythUIType *img = i.next();
00725         img->SetArea(img->GetArea().translated(left, top));
00726     }
00727 }
00728 
00729 void SubtitleScreen::DisplayAVSubtitles(void)
00730 {
00731     if (!m_player || !m_subreader)
00732         return;
00733 
00734     // Resize subtitles if the zoom factor has changed since the last
00735     // update.
00736     if (m_textFontZoom != m_textFontZoomPrev)
00737     {
00738         double factor = m_textFontZoom / (double)m_textFontZoomPrev;
00739         QHash<MythUIType*, MythImage*>::iterator it;
00740         for (it = m_avsubCache.begin(); it != m_avsubCache.end(); ++it)
00741         {
00742             MythUIImage *image = dynamic_cast<MythUIImage *>(it.key());
00743             if (image)
00744             {
00745                 QSize size = it.value()->size();
00746                 size *= factor;
00747                 it.value()->Resize(size);
00748             }
00749         }
00750         m_refreshArea = true;
00751     }
00752 
00753     AVSubtitles* subs = m_subreader->GetAVSubtitles();
00754     QMutexLocker lock(&(subs->lock));
00755     if (subs->buffers.empty() && (kDisplayAVSubtitle != m_subtitleType))
00756         return;
00757 
00758     VideoOutput    *videoOut = m_player->GetVideoOutput();
00759     VideoFrame *currentFrame = videoOut ? videoOut->GetLastShownFrame() : NULL;
00760 
00761     if (!currentFrame || !videoOut)
00762         return;
00763 
00764     float tmp = 0.0;
00765     QRect dummy;
00766     videoOut->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
00767 
00768     while (!subs->buffers.empty())
00769     {
00770         const AVSubtitle subtitle = subs->buffers.front();
00771         if (subtitle.start_display_time > currentFrame->timecode)
00772             break;
00773 
00774         long long displayfor = subtitle.end_display_time -
00775                                subtitle.start_display_time;
00776         if (displayfor == 0)
00777             displayfor = 60000;
00778         displayfor = (displayfor < 50) ? 50 : displayfor;
00779         long long late = currentFrame->timecode -
00780                          subtitle.start_display_time;
00781 
00782         ClearDisplayedSubtitles();
00783         subs->buffers.pop_front();
00784         for (std::size_t i = 0; i < subtitle.num_rects; ++i)
00785         {
00786             AVSubtitleRect* rect = subtitle.rects[i];
00787 
00788             bool displaysub = true;
00789             if (subs->buffers.size() > 0 &&
00790                 subs->buffers.front().end_display_time <
00791                 currentFrame->timecode)
00792             {
00793                 displaysub = false;
00794             }
00795 
00796             if (displaysub && rect->type == SUBTITLE_BITMAP)
00797             {
00798                 QRect display(rect->display_x, rect->display_y,
00799                               rect->display_w, rect->display_h);
00800 
00801                 // XSUB and some DVD/DVB subs are based on the original video
00802                 // size before the video was converted. We need to guess the
00803                 // original size and allow for the difference
00804 
00805                 int right  = rect->x + rect->w;
00806                 int bottom = rect->y + rect->h;
00807                 if (subs->fixPosition || (currentFrame->height < bottom) ||
00808                     (currentFrame->width  < right) ||
00809                     !display.width() || !display.height())
00810                 {
00811                     int sd_height = 576;
00812                     if ((m_player->GetFrameRate() > 26.0f ||
00813                          m_player->GetFrameRate() < 24.0f) && bottom <= 480)
00814                         sd_height = 480;
00815                     int height = ((currentFrame->height <= sd_height) &&
00816                                   (bottom <= sd_height)) ? sd_height :
00817                                  ((currentFrame->height <= 720) && bottom <= 720)
00818                                    ? 720 : 1080;
00819                     int width  = ((currentFrame->width  <= 720) &&
00820                                   (right <= 720)) ? 720 :
00821                                  ((currentFrame->width  <= 1280) &&
00822                                   (right <= 1280)) ? 1280 : 1920;
00823                     display = QRect(0, 0, width, height);
00824                 }
00825 
00826                 // split into upper/lower to allow zooming
00827                 QRect bbox;
00828                 int uh = display.height() / 2 - rect->y;
00829                 int lh;
00830                 long long displayuntil = currentFrame->timecode + displayfor;
00831                 if (uh > 0)
00832                 {
00833                     bbox = QRect(0, 0, rect->w, uh);
00834                     uh = DisplayScaledAVSubtitles(rect, bbox, true, display,
00835                                                   subtitle.forced,
00836                                                   QString("avsub%1t").arg(i),
00837                                                   displayuntil, late);
00838                 }
00839                 else
00840                     uh = 0;
00841                 lh = rect->h - uh;
00842                 if (lh > 0)
00843                 {
00844                     bbox = QRect(0, uh, rect->w, lh);
00845                     lh = DisplayScaledAVSubtitles(rect, bbox, false, display,
00846                                                   subtitle.forced,
00847                                                   QString("avsub%1b").arg(i),
00848                                                   displayuntil, late);
00849                 }
00850             }
00851 #ifdef USING_LIBASS
00852             else if (displaysub && rect->type == SUBTITLE_ASS)
00853             {
00854                 InitialiseAssTrack(m_player->GetDecoder()->GetTrack(kTrackTypeSubtitle));
00855                 AddAssEvent(rect->ass);
00856             }
00857 #endif
00858         }
00859         m_subreader->FreeAVSubtitle(subtitle);
00860     }
00861 #ifdef USING_LIBASS
00862     RenderAssTrack(currentFrame->timecode);
00863 #endif
00864 }
00865 
00866 int SubtitleScreen::DisplayScaledAVSubtitles(const AVSubtitleRect *rect,
00867                                              QRect &bbox, bool top,
00868                                              QRect &display, int forced,
00869                                              QString imagename,
00870                                              long long displayuntil,
00871                                              long long late)
00872 {
00873     // split image vertically if it spans middle of display
00874     // - split point is empty line nearest the middle
00875     // crop image to reduce scaling time
00876     int xmin, xmax, ymin, ymax;
00877     int ylast, ysplit;
00878     bool prev_empty = false;
00879 
00880     // initialize to opposite edges
00881     xmin = bbox.right();
00882     xmax = bbox.left();
00883     ymin = bbox.bottom();
00884     ymax = bbox.top();
00885     ylast = bbox.top();
00886     ysplit = bbox.bottom();
00887 
00888     // find bounds of active image
00889     for (int y = bbox.top(); y <= bbox.bottom(); ++y)
00890     {
00891         if (y >= rect->h)
00892         {
00893             // end of image
00894             if (!prev_empty)
00895                 ylast = y;
00896             break;
00897         }
00898 
00899         bool empty = true;
00900         for (int x = bbox.left(); x <= bbox.right(); ++x)
00901         {
00902             const uint8_t color =
00903                 rect->pict.data[0][y * rect->pict.linesize[0] + x];
00904             const uint32_t pixel = *((uint32_t *)rect->pict.data[1] + color);
00905             if (pixel & 0xff000000)
00906             {
00907                 empty = false;
00908                 if (x < xmin)
00909                     xmin = x;
00910                 if (x > xmax)
00911                     xmax = x;
00912             }
00913         }
00914 
00915         if (!empty)
00916         {
00917             if (y < ymin)
00918                 ymin = y;
00919             if (y > ymax)
00920                 ymax = y;
00921         }
00922         else if (!prev_empty)
00923         {
00924             // remember uppermost empty line
00925             ylast = y;
00926         }
00927         prev_empty = empty;
00928     }
00929 
00930     if (ymax <= ymin)
00931         return 0;
00932 
00933     if (top)
00934     {
00935         if (ylast < ymin)
00936             // no empty lines
00937             return 0;
00938 
00939         if (ymax == bbox.bottom())
00940         {
00941             ymax = ylast;
00942             ysplit = ylast;
00943         }
00944     }
00945 
00946     // set new bounds
00947     bbox.setLeft(xmin);
00948     bbox.setRight(xmax);
00949     bbox.setTop(ymin);
00950     bbox.setBottom(ymax);
00951 
00952     // copy active region
00953     // AVSubtitleRect's image data's not guaranteed to be 4 byte
00954     // aligned.
00955 
00956     QRect orig_rect(bbox.left(), bbox.top(), bbox.width(), bbox.height());
00957 
00958     QImage qImage(bbox.width(), bbox.height(), QImage::Format_ARGB32);
00959     for (int y = 0; y < bbox.height(); ++y)
00960     {
00961         int ysrc = y + bbox.top();
00962         for (int x = 0; x < bbox.width(); ++x)
00963         {
00964             int xsrc = x + bbox.left();
00965             const uint8_t color =
00966                 rect->pict.data[0][ysrc * rect->pict.linesize[0] + xsrc];
00967             const uint32_t pixel = *((uint32_t *)rect->pict.data[1] + color);
00968             qImage.setPixel(x, y, pixel);
00969         }
00970     }
00971 
00972     // translate to absolute coordinates
00973     bbox.translate(rect->x, rect->y);
00974 
00975     // scale and move according to zoom factor
00976     bbox.setWidth(bbox.width() * m_textFontZoom / 100);
00977     bbox.setHeight(bbox.height() * m_textFontZoom / 100);
00978 
00979     VideoOutput *videoOut = m_player->GetVideoOutput();
00980     QRect scaled = videoOut->GetImageRect(bbox, &display);
00981 
00982     if (scaled.size() != orig_rect.size())
00983         qImage = qImage.scaled(scaled.width(), scaled.height(),
00984                                Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
00985 
00986     int hsize = m_safeArea.width();
00987     int vsize = m_safeArea.height();
00988 
00989     scaled.moveLeft(((100 - m_textFontZoom) * hsize / 2 +
00990                      m_textFontZoom * scaled.left()) /
00991                     100);
00992     if (top)
00993         // anchor up
00994         scaled.moveTop(scaled.top() * m_textFontZoom / 100);
00995     else
00996         // anchor down
00997         scaled.moveTop(((100 - m_textFontZoom) * vsize +
00998                         m_textFontZoom * scaled.top()) /
00999                        100);
01000 
01001 
01002     MythPainter *osd_painter = videoOut->GetOSDPainter();
01003     MythImage *image = NULL;
01004     if (osd_painter)
01005        image = osd_painter->GetFormatImage();
01006 
01007     MythUIImage *uiimage = NULL;
01008     if (image)
01009     {
01010         image->Assign(qImage);
01011         uiimage = new MythUIImage(this, imagename);
01012         if (uiimage)
01013         {
01014             m_refreshArea = true;
01015             uiimage->SetImage(image);
01016             uiimage->SetArea(MythRect(scaled));
01017             m_expireTimes.insert(uiimage, displayuntil);
01018             m_avsubCache.insert(uiimage, image);
01019         }
01020     }
01021     if (uiimage)
01022     {
01023         LOG(VB_PLAYBACK, LOG_INFO, LOC +
01024             QString("Display %1AV sub until %2ms")
01025            .arg(forced ? "FORCED " : "")
01026            .arg(displayuntil));
01027         if (late > 50)
01028             LOG(VB_PLAYBACK, LOG_INFO, LOC +
01029                 QString("AV Sub was %1ms late").arg(late));
01030     }
01031 
01032     return (ysplit + 1);
01033 }
01034 
01035 void SubtitleScreen::DisplayTextSubtitles(void)
01036 {
01037     if (!m_player || !m_subreader)
01038         return;
01039 
01040     bool changed = (m_textFontZoom != m_textFontZoomPrev);
01041     VideoOutput *vo = m_player->GetVideoOutput();
01042     if (!vo)
01043         return;
01044     m_safeArea = vo->GetSafeRect();
01045 
01046     VideoFrame *currentFrame = vo->GetLastShownFrame();
01047     if (!currentFrame)
01048         return;
01049 
01050     TextSubtitles *subs = m_subreader->GetTextSubtitles();
01051     subs->Lock();
01052     uint64_t playPos = 0;
01053     if (subs->IsFrameBasedTiming())
01054     {
01055         // frame based subtitles get out of synch after running mythcommflag
01056         // for the file, i.e., the following number is wrong and does not
01057         // match the subtitle frame numbers:
01058         playPos = currentFrame->frameNumber;
01059     }
01060     else
01061     {
01062         // Use timecodes for time based SRT subtitles. Feeding this into
01063         // NormalizeVideoTimecode() should adjust for non-zero start times
01064         // and wraps. For MPEG, wraps will occur just once every 26.5 hours
01065         // and other formats less frequently so this should be sufficient.
01066         // Note: timecodes should now always be valid even in the case
01067         // when a frame doesn't have a valid timestamp. If an exception is
01068         // found where this is not true then we need to use the frameNumber
01069         // when timecode is not defined by uncommenting the following lines.
01070         //if (currentFrame->timecode == 0)
01071         //    playPos = (uint64_t)
01072         //        ((currentFrame->frameNumber / video_frame_rate) * 1000);
01073         //else
01074         playPos = m_player->GetDecoder()->NormalizeVideoTimecode(currentFrame->timecode);
01075     }
01076     if (playPos != 0)
01077         changed |= subs->HasSubtitleChanged(playPos);
01078     if (!changed)
01079     {
01080         subs->Unlock();
01081         return;
01082     }
01083 
01084     DeleteAllChildren();
01085     SetRedraw();
01086     if (playPos == 0)
01087     {
01088         subs->Unlock();
01089         return;
01090     }
01091 
01092     QStringList rawsubs = subs->GetSubtitles(playPos);
01093     if (rawsubs.empty())
01094     {
01095         subs->Unlock();
01096         return;
01097     }
01098 
01099     subs->Unlock();
01100     DrawTextSubtitles(rawsubs, 0, 0);
01101 }
01102 
01103 void SubtitleScreen::DisplayRawTextSubtitles(void)
01104 {
01105     if (!m_player || !m_subreader)
01106         return;
01107 
01108     uint64_t duration;
01109     QStringList subs = m_subreader->GetRawTextSubtitles(duration);
01110     if (subs.empty())
01111         return;
01112 
01113     VideoOutput *vo = m_player->GetVideoOutput();
01114     if (!vo)
01115         return;
01116 
01117     VideoFrame *currentFrame = vo->GetLastShownFrame();
01118     if (!currentFrame)
01119         return;
01120 
01121     m_safeArea = vo->GetSafeRect();
01122 
01123     // delete old subs that may still be on screen
01124     DeleteAllChildren();
01125     DrawTextSubtitles(subs, currentFrame->timecode, duration);
01126 }
01127 
01128 void SubtitleScreen::DrawTextSubtitles(QStringList &wrappedsubs,
01129                                        uint64_t start, uint64_t duration)
01130 {
01131     FormattedTextSubtitle fsub(m_safeArea, this);
01132     fsub.InitFromSRT(wrappedsubs, m_textFontZoom);
01133     fsub.WrapLongLines();
01134     fsub.Layout();
01135     m_refreshArea = fsub.Draw(m_family, NULL, start, duration) || m_refreshArea;
01136 }
01137 
01138 void SubtitleScreen::DisplayDVDButton(AVSubtitle* dvdButton, QRect &buttonPos)
01139 {
01140     if (!dvdButton || !m_player)
01141         return;
01142 
01143     VideoOutput *vo = m_player->GetVideoOutput();
01144     if (!vo)
01145         return;
01146 
01147     DeleteAllChildren();
01148     SetRedraw();
01149 
01150     float tmp = 0.0;
01151     QRect dummy;
01152     vo->GetOSDBounds(dummy, m_safeArea, tmp, tmp, tmp);
01153 
01154     AVSubtitleRect *hl_button = dvdButton->rects[0];
01155     uint h = hl_button->h;
01156     uint w = hl_button->w;
01157     QRect rect = QRect(hl_button->x, hl_button->y, w, h);
01158     QImage bg_image(hl_button->pict.data[0], w, h, w, QImage::Format_Indexed8);
01159     uint32_t *bgpalette = (uint32_t *)(hl_button->pict.data[1]);
01160 
01161     QVector<uint32_t> bg_palette(4);
01162     for (int i = 0; i < 4; i++)
01163         bg_palette[i] = bgpalette[i];
01164     bg_image.setColorTable(bg_palette);
01165 
01166     // copy button region of background image
01167     const QRect fg_rect(buttonPos.translated(-hl_button->x, -hl_button->y));
01168     QImage fg_image = bg_image.copy(fg_rect);
01169     QVector<uint32_t> fg_palette(4);
01170     uint32_t *fgpalette = (uint32_t *)(dvdButton->rects[1]->pict.data[1]);
01171     if (fgpalette)
01172     {
01173         for (int i = 0; i < 4; i++)
01174             fg_palette[i] = fgpalette[i];
01175         fg_image.setColorTable(fg_palette);
01176     }
01177 
01178     bg_image = bg_image.convertToFormat(QImage::Format_ARGB32);
01179     fg_image = fg_image.convertToFormat(QImage::Format_ARGB32);
01180 
01181     // set pixel of highlight area to highlight color
01182     for (int x=fg_rect.x(); x < fg_rect.x()+fg_rect.width(); ++x)
01183     {
01184         if ((x < 0) || (x > hl_button->w))
01185             continue;
01186         for (int y=fg_rect.y(); y < fg_rect.y()+fg_rect.height(); ++y)
01187         {
01188             if ((y < 0) || (y > hl_button->h))
01189                 continue;
01190             bg_image.setPixel(x, y, fg_image.pixel(x-fg_rect.x(),y-fg_rect.y()));
01191         }
01192     }
01193 
01194     AddScaledImage(bg_image, rect);
01195 }
01196 
01202 static QString extract_cc608(
01203     QString &text, bool teletextmode, int &color,
01204     bool &isItalic, bool &isUnderline)
01205 {
01206     QString result;
01207     QString orig(text);
01208 
01209     if (teletextmode)
01210     {
01211         result = text;
01212         text = QString::null;
01213         return result;
01214     }
01215 
01216     // Handle an initial control sequence.
01217     if (text.length() >= 1 && text[0] >= 0x7000)
01218     {
01219         int op = text[0].unicode() - 0x7000;
01220         isUnderline = (op & 0x1);
01221         switch (op & ~1)
01222         {
01223         case 0x0e:
01224             // color unchanged
01225             isItalic = true;
01226             break;
01227         case 0x1e:
01228             color = op >> 1;
01229             isItalic = true;
01230             break;
01231         case 0x20:
01232             // color unchanged
01233             // italics unchanged
01234             break;
01235         default:
01236             color = (op & 0xf) >> 1;
01237             isItalic = false;
01238             break;
01239         }
01240         text = text.mid(1);
01241     }
01242 
01243     // Copy the string into the result, up to the next control
01244     // character.
01245     int nextControl = text.indexOf(QRegExp("[\\x7000-\\x7fff]"));
01246     if (nextControl < 0)
01247     {
01248         result = text;
01249         text = QString::null;
01250     }
01251     else
01252     {
01253         result = text.left(nextControl);
01254         // Print the space character before handling the next control
01255         // character, otherwise the space character will be lost due
01256         // to the text.trimmed() operation in the MythUISimpleText
01257         // constructor, combined with the left-justification of
01258         // captions.
01259         if (text[nextControl] < (0x7000 + 0x10))
01260             result += " ";
01261         text = text.mid(nextControl);
01262     }
01263 
01264     return result;
01265 }
01266 
01267 void SubtitleScreen::DisplayCC608Subtitles(void)
01268 {
01269     if (!m_608reader)
01270         return;
01271 
01272     bool changed = (m_textFontZoom != m_textFontZoomPrev);
01273 
01274     if (!m_player || !m_player->GetVideoOutput())
01275         return;
01276     m_safeArea = m_player->GetVideoOutput()->GetSafeRect();
01277 
01278     CC608Buffer* textlist = m_608reader->GetOutputText(changed);
01279     if (!changed)
01280         return;
01281 
01282     if (textlist)
01283         textlist->lock.lock();
01284 
01285     DeleteAllChildren();
01286 
01287     if (!textlist)
01288         return;
01289 
01290     if (textlist->buffers.empty())
01291     {
01292         SetRedraw();
01293         textlist->lock.unlock();
01294         return;
01295     }
01296 
01297     FormattedTextSubtitle fsub(m_safeArea, this);
01298     fsub.InitFromCC608(textlist->buffers, m_textFontZoom);
01299     fsub.Layout608();
01300     fsub.Layout();
01301     m_refreshArea = fsub.Draw(m_family) || m_refreshArea;
01302     textlist->lock.unlock();
01303 }
01304 
01305 void SubtitleScreen::DisplayCC708Subtitles(void)
01306 {
01307     if (!m_708reader)
01308         return;
01309 
01310     CC708Service *cc708service = m_708reader->GetCurrentService();
01311     float video_aspect = 1.77777f;
01312     bool changed = false;
01313     if (m_player && m_player->GetVideoOutput())
01314     {
01315         video_aspect = m_player->GetVideoAspect();
01316         QRect oldsafe = m_safeArea;
01317         m_safeArea = m_player->GetVideoOutput()->GetSafeRect();
01318         changed = (oldsafe != m_safeArea ||
01319                    m_textFontZoom != m_textFontZoomPrev);
01320         if (changed)
01321         {
01322             for (uint i = 0; i < 8; i++)
01323                 cc708service->windows[i].changed = true;
01324         }
01325     }
01326     else
01327     {
01328         return;
01329     }
01330 
01331     for (uint i = 0; i < 8; i++)
01332     {
01333         CC708Window &win = cc708service->windows[i];
01334         if (win.exists && win.visible && !win.changed)
01335             continue;
01336 
01337         Clear708Cache(i);
01338         if (!win.exists || !win.visible)
01339             continue;
01340 
01341         QMutexLocker locker(&win.lock);
01342         vector<CC708String*> list = win.GetStrings();
01343         if (!list.empty())
01344         {
01345             FormattedTextSubtitle fsub(m_safeArea, this);
01346             fsub.InitFromCC708(win, i, list, video_aspect, m_textFontZoom);
01347             fsub.Layout();
01348             // Draw the window background after calculating bounding
01349             // rectangle in Layout()
01350             if (win.GetFillAlpha()) // TODO border?
01351             {
01352                 QBrush fill(win.GetFillColor(), Qt::SolidPattern);
01353                 MythUIShape *shape =
01354                     new MythUIShape(this, QString("cc708bg%1").arg(i));
01355                 shape->SetFillBrush(fill);
01356                 shape->SetArea(MythRect(fsub.m_bounds));
01357                 m_708imageCache[i].append(shape);
01358                 m_refreshArea = true;
01359             }
01360             m_refreshArea =
01361                 fsub.Draw(m_family, &m_708imageCache[i]) || m_refreshArea;
01362         }
01363         for (uint j = 0; j < list.size(); j++)
01364             delete list[j];
01365         win.changed = false;
01366     }
01367 }
01368 
01369 void SubtitleScreen::Clear708Cache(int num)
01370 {
01371     if (!m_708imageCache[num].isEmpty())
01372     {
01373         foreach(MythUIType* image, m_708imageCache[num])
01374             DeleteChild(image);
01375         m_708imageCache[num].clear();
01376     }
01377 }
01378 
01379 void SubtitleScreen::AddScaledImage(QImage &img, QRect &pos)
01380 {
01381     VideoOutput *vo = m_player->GetVideoOutput();
01382     if (!vo)
01383         return;
01384 
01385     QRect scaled = vo->GetImageRect(pos);
01386     if (scaled.size() != pos.size())
01387     {
01388         img = img.scaled(scaled.width(), scaled.height(),
01389                          Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
01390     }
01391 
01392     MythPainter *osd_painter = vo->GetOSDPainter();
01393     MythImage* image = NULL;
01394     if (osd_painter)
01395          image = osd_painter->GetFormatImage();
01396 
01397     if (image)
01398     {
01399         image->Assign(img);
01400         MythUIImage *uiimage = new MythUIImage(this, "dvd_button");
01401         if (uiimage)
01402         {
01403             m_refreshArea = true;
01404             uiimage->SetImage(image);
01405             uiimage->SetArea(MythRect(scaled));
01406         }
01407     }
01408 }
01409 
01410 MythFontProperties* SubtitleScreen::GetFont(CC708CharacterAttribute attr,
01411                                             bool teletext) const
01412 {
01413     return m_format->GetFont(m_family, attr, m_fontSize,
01414                              teletext, m_textFontZoom, m_fontStretch);
01415 }
01416 
01417 void SubtitleScreen::SetZoom(int percent)
01418 {
01419     m_textFontZoom = percent;
01420     if (m_family == kSubFamilyAV)
01421         gCoreContext->SaveSetting("OSDAVSubZoom", percent);
01422     else
01423         gCoreContext->SaveSetting("OSDCC708TextZoom", percent);
01424 }
01425 
01426 int SubtitleScreen::GetZoom(void)
01427 {
01428     return m_textFontZoom;
01429 }
01430 
01431 static QString srtColorString(QColor color)
01432 {
01433     return QString("#%1%2%3")
01434         .arg(color.red(),   2, 16, QLatin1Char('0'))
01435         .arg(color.green(), 2, 16, QLatin1Char('0'))
01436         .arg(color.blue(),  2, 16, QLatin1Char('0'));
01437 }
01438 
01439 void FormattedTextSubtitle::InitFromCC608(vector<CC608Text*> &buffers,
01440                                           int textFontZoom)
01441 {
01442     static const QColor clr[8] =
01443     {
01444         Qt::white,   Qt::green,   Qt::blue,    Qt::cyan,
01445         Qt::red,     Qt::yellow,  Qt::magenta, Qt::white,
01446     };
01447 
01448     if (buffers.empty())
01449         return;
01450     vector<CC608Text*>::iterator i = buffers.begin();
01451     bool teletextmode = (*i)->teletextmode;
01452 
01453     int xscale = teletextmode ? 40 : 36;
01454     int yscale = teletextmode ? 25 : 17;
01455     // For the purpose of pixel size calculation, use a normalized
01456     // size based on 17 non-teletext rows rather than 25.  The
01457     // shrinking is done in GetFont so that the theme can supply a
01458     // uniform normalized pixelsize value.
01459     int pixelSize = m_safeArea.height() / (/*yscale*/17 * LINE_SPACING);
01460     int fontwidth = 0;
01461     int xmid = 0;
01462     if (parent)
01463     {
01464         parent->SetFontSize(pixelSize);
01465         CC708CharacterAttribute def_attr(false, false, false, clr[0]);
01466         QFont *font = parent->GetFont(def_attr, teletextmode)->GetFace();
01467         QFontMetrics fm(*font);
01468         fontwidth = fm.averageCharWidth();
01469         xmid = m_safeArea.width() / 2;
01470         // Disable centering for zoom factor >= 100%
01471         if (textFontZoom >= 100)
01472             xscale = m_safeArea.width() / fontwidth;
01473     }
01474 
01475     for (; i != buffers.end(); ++i)
01476     {
01477         CC608Text *cc = (*i);
01478         int color = 0;
01479         bool isItalic = false, isUnderline = false;
01480         const bool isBold = false;
01481         QString text(cc->text);
01482 
01483         int orig_x = teletextmode ? cc->y : cc->x;
01484         // position as if we use a fixed size font
01485         // - font size already has zoom factor applied
01486 
01487         int x;
01488         if (xmid)
01489             // center horizontally
01490             x = xmid + (orig_x - xscale / 2) * fontwidth;
01491         else
01492             // fallback
01493             x = (orig_x + 3) * m_safeArea.width() / xscale;
01494 
01495         int orig_y = teletextmode ? cc->x : cc->y;
01496         int y;
01497         if (orig_y < yscale / 2)
01498             // top half -- anchor up
01499             y = (orig_y * m_safeArea.height() * textFontZoom / (yscale * 100));
01500         else
01501             // bottom half -- anchor down
01502             y = m_safeArea.height() -
01503                 ((yscale - orig_y - 0.5) * m_safeArea.height() * textFontZoom /
01504                  (yscale * 100));
01505 
01506         FormattedTextLine line(x, y, orig_x, orig_y);
01507         while (!text.isNull())
01508         {
01509             QString captionText =
01510                 extract_cc608(text, cc->teletextmode,
01511                               color, isItalic, isUnderline);
01512             CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
01513                                          clr[min(max(0, color), 7)]);
01514             FormattedTextChunk chunk(captionText, attr, parent,
01515                                      cc->teletextmode);
01516             line.chunks += chunk;
01517             LOG(VB_VBI, LOG_INFO,
01518                 QString("Adding cc608 chunk (%1,%2): %3")
01519                 .arg(cc->x).arg(cc->y).arg(chunk.ToLogString()));
01520         }
01521         m_lines += line;
01522     }
01523 }
01524 
01525 void FormattedTextSubtitle::InitFromCC708(const CC708Window &win, int num,
01526                                           const vector<CC708String*> &list,
01527                                           float aspect,
01528                                           int textFontZoom)
01529 {
01530     LOG(VB_VBI, LOG_INFO,LOC +
01531         QString("Display Win %1, Anchor_id %2, x_anch %3, y_anch %4, "
01532                 "relative %5")
01533             .arg(num).arg(win.anchor_point).arg(win.anchor_horizontal)
01534             .arg(win.anchor_vertical).arg(win.relative_pos));
01535     int pixelSize = m_safeArea.height() / 20;
01536     if (parent)
01537         parent->SetFontSize(pixelSize);
01538 
01539     float xrange  = win.relative_pos ? 100.0f :
01540                     (aspect > 1.4f) ? 210.0f : 160.0f;
01541     float yrange  = win.relative_pos ? 100.0f : 75.0f;
01542     float xmult   = (float)m_safeArea.width() / xrange;
01543     float ymult   = (float)m_safeArea.height() / yrange;
01544     uint anchor_x = (uint)(xmult * (float)win.anchor_horizontal);
01545     uint anchor_y = (uint)(ymult * (float)win.anchor_vertical);
01546     m_xAnchorPoint = win.anchor_point % 3;
01547     m_yAnchorPoint = win.anchor_point / 3;
01548     m_xAnchor = anchor_x;
01549     m_yAnchor = anchor_y;
01550 
01551     for (uint i = 0; i < list.size(); i++)
01552     {
01553         if (list[i]->y >= (uint)m_lines.size())
01554             m_lines.resize(list[i]->y + 1);
01555         FormattedTextChunk chunk(list[i]->str, list[i]->attr, parent);
01556         m_lines[list[i]->y].chunks += chunk;
01557         LOG(VB_VBI, LOG_INFO, QString("Adding cc708 chunk: win %1 row %2: %3")
01558             .arg(num).arg(i).arg(chunk.ToLogString()));
01559     }
01560 }
01561 
01562 void FormattedTextSubtitle::InitFromSRT(QStringList &subs, int textFontZoom)
01563 {
01564     // Does a simplistic parsing of HTML tags from the strings.
01565     // Nesting is not implemented.  Stray whitespace may cause
01566     // legitimate tags to be ignored.  All other HTML tags are
01567     // stripped and ignored.
01568     //
01569     // <i> - enable italics
01570     // </i> - disable italics
01571     // <b> - enable boldface
01572     // </b> - disable boldface
01573     // <u> - enable underline
01574     // </u> - disable underline
01575     // <font color="#xxyyzz"> - change font color
01576     // </font> - reset font color to white
01577 
01578     int pixelSize = m_safeArea.height() / 20;
01579     if (parent)
01580         parent->SetFontSize(pixelSize);
01581     m_xAnchorPoint = 1; // center
01582     m_yAnchorPoint = 2; // bottom
01583     m_xAnchor = m_safeArea.width() / 2;
01584     m_yAnchor = m_safeArea.height();
01585 
01586     bool isItalic = false;
01587     bool isBold = false;
01588     bool isUnderline = false;
01589     QColor color(Qt::white);
01590     QRegExp htmlTag("</?.+>");
01591     QString htmlPrefix("<font color=\""), htmlSuffix("\">");
01592     htmlTag.setMinimal(true);
01593     foreach (QString subtitle, subs)
01594     {
01595         FormattedTextLine line;
01596         QString text(subtitle);
01597         while (!text.isEmpty())
01598         {
01599             int pos = text.indexOf(htmlTag);
01600             if (pos != 0) // don't add a zero-length string
01601             {
01602                 CC708CharacterAttribute attr(isItalic, isBold, isUnderline,
01603                                              color);
01604                 FormattedTextChunk chunk(text.left(pos), attr, parent);
01605                 line.chunks += chunk;
01606                 text = (pos < 0 ? "" : text.mid(pos));
01607                 LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk: %1")
01608                     .arg(chunk.ToLogString()));
01609             }
01610             if (pos >= 0)
01611             {
01612                 int htmlLen = htmlTag.matchedLength();
01613                 QString html = text.left(htmlLen).toLower();
01614                 text = text.mid(htmlLen);
01615                 if (html == "<i>")
01616                     isItalic = true;
01617                 else if (html == "</i>")
01618                     isItalic = false;
01619                 else if (html.startsWith(htmlPrefix) &&
01620                          html.endsWith(htmlSuffix))
01621                 {
01622                     int colorLen = html.length() -
01623                         (htmlPrefix.length() + htmlSuffix.length());
01624                     QString colorString(
01625                         html.mid(htmlPrefix.length(), colorLen));
01626                     QColor newColor(colorString);
01627                     if (newColor.isValid())
01628                     {
01629                         color = newColor;
01630                     }
01631                     else
01632                     {
01633                         LOG(VB_VBI, LOG_INFO,
01634                             QString("Ignoring invalid SRT color specification: "
01635                                     "'%1'").arg(colorString));
01636                     }
01637                 }
01638                 else if (html == "</font>")
01639                     color = Qt::white;
01640                 else if (html == "<b>")
01641                     isBold = true;
01642                 else if (html == "</b>")
01643                     isBold = false;
01644                 else if (html == "<u>")
01645                     isUnderline = true;
01646                 else if (html == "</u>")
01647                     isUnderline = false;
01648                 else
01649                 {
01650                     LOG(VB_VBI, LOG_INFO,
01651                         QString("Ignoring unknown SRT formatting: '%1'")
01652                         .arg(html));
01653                 }
01654 
01655                 LOG(VB_VBI, LOG_INFO,
01656                     QString("SRT formatting change '%1', "
01657                             "new ital=%2 bold=%3 uline=%4 color=%5)")
01658                     .arg(html).arg(isItalic).arg(isBold).arg(isUnderline)
01659                     .arg(srtColorString(color)));
01660             }
01661         }
01662         m_lines += line;
01663     }
01664 }
01665 
01666 bool FormattedTextChunk::Split(FormattedTextChunk &newChunk)
01667 {
01668     LOG(VB_VBI, LOG_INFO,
01669         QString("Attempting to split chunk '%1'").arg(text));
01670     int lastSpace = text.lastIndexOf(' ', -2); // -2 to ignore trailing space
01671     if (lastSpace < 0)
01672     {
01673         LOG(VB_VBI, LOG_INFO,
01674             QString("Failed to split chunk '%1'").arg(text));
01675         return false;
01676     }
01677     newChunk.isTeletext = isTeletext;
01678     newChunk.parent = parent;
01679     newChunk.format = format;
01680     newChunk.text = text.mid(lastSpace + 1).trimmed() + ' ';
01681     text = text.left(lastSpace).trimmed();
01682     LOG(VB_VBI, LOG_INFO,
01683         QString("Split chunk into '%1' + '%2'").arg(text).arg(newChunk.text));
01684     return true;
01685 }
01686 
01687 QString FormattedTextChunk::ToLogString(void) const
01688 {
01689     QString str("");
01690     str += QString("fg=%1.%2 ")
01691         .arg(srtColorString(format.GetFGColor()))
01692         .arg(format.GetFGAlpha());
01693     str += QString("bg=%1.%2 ")
01694         .arg(srtColorString(format.GetBGColor()))
01695         .arg(format.GetBGAlpha());
01696     str += QString("edge=%1.%2 ")
01697         .arg(srtColorString(format.GetEdgeColor()))
01698         .arg(format.edge_type);
01699     str += QString("off=%1 pensize=%2 ")
01700         .arg(format.offset)
01701         .arg(format.pen_size);
01702     str += QString("it=%1 ul=%2 bf=%3 ")
01703         .arg(format.italics)
01704         .arg(format.underline)
01705         .arg(format.boldface);
01706     str += QString("font=%1 ").arg(format.font_tag);
01707     str += QString(" text='%1'").arg(text);
01708     return str;
01709 }
01710 
01711 void FormattedTextSubtitle::WrapLongLines(void)
01712 {
01713     int maxWidth = m_safeArea.width();
01714     for (int i = 0; i < m_lines.size(); i++)
01715     {
01716         int width = m_lines[i].CalcSize().width();
01717         // Move entire chunks to the next line as necessary.  Leave at
01718         // least one chunk on the current line.
01719         while (width > maxWidth && m_lines[i].chunks.size() > 1)
01720         {
01721             width -= m_lines[i].chunks.back().CalcSize().width();
01722             // Make sure there's a next line to wrap into.
01723             if (m_lines.size() == i + 1)
01724                 m_lines += FormattedTextLine(m_lines[i].x_indent,
01725                                              m_lines[i].y_indent);
01726             m_lines[i+1].chunks.prepend(m_lines[i].chunks.takeLast());
01727             LOG(VB_VBI, LOG_INFO,
01728                 QString("Wrapping chunk to next line: '%1'")
01729                 .arg(m_lines[i+1].chunks[0].text));
01730         }
01731         // Split the last chunk until width is small enough or until
01732         // no more splits are possible.
01733         bool isSplitPossible = true;
01734         while (width > maxWidth && isSplitPossible)
01735         {
01736             FormattedTextChunk newChunk;
01737             isSplitPossible = m_lines[i].chunks.back().Split(newChunk);
01738             if (isSplitPossible)
01739             {
01740                 // Make sure there's a next line to split into.
01741                 if (m_lines.size() == i + 1)
01742                     m_lines += FormattedTextLine(m_lines[i].x_indent,
01743                                                  m_lines[i].y_indent);
01744                 m_lines[i+1].chunks.prepend(newChunk);
01745                 width = m_lines[i].CalcSize().width();
01746             }
01747         }
01748     }
01749 }
01750 
01751 // Adjusts the Y coordinates to avoid overlap, which could happen as a
01752 // result of a large text zoom factor.  Then, if the total height
01753 // exceeds the safe area, compresses each piece of vertical blank
01754 // space proportionally to make it fit.
01755 void FormattedTextSubtitle::Layout608(void)
01756 {
01757     int i;
01758     int totalHeight = 0;
01759     int totalSpace = 0;
01760     int firstY = 0;
01761     int prevY = 0;
01762     QVector<int> heights(m_lines.size());
01763     QVector<int> spaceBefore(m_lines.size());
01764     // Calculate totalHeight and totalSpace
01765     for (i = 0; i < m_lines.size(); i++)
01766     {
01767         m_lines[i].y_indent = max(m_lines[i].y_indent, prevY); // avoid overlap
01768         int y = m_lines[i].y_indent;
01769         if (i == 0)
01770             firstY = prevY = y;
01771         int height = m_lines[i].CalcSize().height();
01772         heights[i] = height;
01773         spaceBefore[i] = y - prevY;
01774         totalSpace += (y - prevY);
01775         prevY = y + height;
01776         totalHeight += height;
01777     }
01778     int safeHeight = m_safeArea.height();
01779     int overage = min(totalHeight - safeHeight, totalSpace);
01780 
01781     // Recalculate Y coordinates, applying the shrink factor to space
01782     // between each line.
01783     if (overage > 0 && totalSpace > 0)
01784     {
01785         float shrink = (totalSpace - overage) / (float)totalSpace;
01786         prevY = firstY;
01787         for (i = 0; i < m_lines.size(); i++)
01788         {
01789             m_lines[i].y_indent = prevY + spaceBefore[i] * shrink;
01790             prevY = m_lines[i].y_indent + heights[i];
01791         }
01792     }
01793 
01794     // Shift Y coordinates back up into the safe area.
01795     int shift = min(firstY, max(0, prevY - safeHeight));
01796     for (i = 0; i < m_lines.size(); i++)
01797         m_lines[i].y_indent -= shift;
01798 }
01799 
01800 // Resolves any TBD x_indent and y_indent values in FormattedTextLine
01801 // objects.  Calculates m_bounds.  Prunes most leading and all
01802 // trailing whitespace from each line so that displaying with a black
01803 // background doesn't look clumsy.
01804 void FormattedTextSubtitle::Layout(void)
01805 {
01806     // Calculate dimensions of bounding rectangle
01807     int anchor_width = 0;
01808     int anchor_height = 0;
01809     for (int i = 0; i < m_lines.size(); i++)
01810     {
01811         QSize sz = m_lines[i].CalcSize(LINE_SPACING);
01812         anchor_width = max(anchor_width, sz.width());
01813         anchor_height += sz.height();
01814     }
01815 
01816     // Adjust the anchor point according to actual width and height
01817     int anchor_x = m_xAnchor;
01818     int anchor_y = m_yAnchor;
01819     if (m_xAnchorPoint == 1)
01820         anchor_x -= anchor_width / 2;
01821     else if (m_xAnchorPoint == 2)
01822         anchor_x -= anchor_width;
01823     if (m_yAnchorPoint == 1)
01824         anchor_y -= anchor_height / 2;
01825     else if (m_yAnchorPoint == 2)
01826         anchor_y -= anchor_height;
01827 
01828     // Shift the anchor point back into the safe area if necessary/possible.
01829     anchor_y = max(0, min(anchor_y, m_safeArea.height() - anchor_height));
01830     anchor_x = max(0, min(anchor_x, m_safeArea.width() - anchor_width));
01831 
01832     m_bounds = QRect(anchor_x, anchor_y, anchor_width, anchor_height);
01833 
01834     // Fill in missing coordinates
01835     int y = anchor_y;
01836     for (int i = 0; i < m_lines.size(); i++)
01837     {
01838         if (m_lines[i].x_indent < 0)
01839             m_lines[i].x_indent = anchor_x;
01840         if (m_lines[i].y_indent < 0)
01841             m_lines[i].y_indent = y;
01842         y += m_lines[i].CalcSize(LINE_SPACING).height();
01843         // Prune leading all-whitespace chunks.
01844         while (!m_lines[i].chunks.isEmpty() &&
01845                m_lines[i].chunks.first().text.trimmed().isEmpty())
01846         {
01847             m_lines[i].x_indent +=
01848                 m_lines[i].chunks.first().CalcSize().width();
01849             m_lines[i].chunks.removeFirst();
01850         }
01851         // Prune trailing all-whitespace chunks.
01852         while (!m_lines[i].chunks.isEmpty() &&
01853                m_lines[i].chunks.last().text.trimmed().isEmpty())
01854         {
01855             m_lines[i].chunks.removeLast();
01856         }
01857         // Trim trailing whitespace from last chunk.  (Trimming
01858         // leading whitespace from all chunks is handled in the Draw()
01859         // routine.)
01860         if (!m_lines[i].chunks.isEmpty())
01861         {
01862             QString *str = &m_lines[i].chunks.last().text;
01863             int idx = str->length() - 1;
01864             while (idx >= 0 && str->at(idx) == ' ')
01865                 --idx;
01866             str->truncate(idx + 1);
01867         }
01868     }
01869 }
01870 
01871 // Returns true if anything new was drawn, false if not.  The caller
01872 // should call SubtitleScreen::OptimiseDisplayedArea() if true is
01873 // returned.
01874 bool FormattedTextSubtitle::Draw(const QString &base,
01875                                  QList<MythUIType*> *imageCache,
01876                                  uint64_t start, uint64_t duration) const
01877 {
01878     bool result = false;
01879     QVector<MythUISimpleText *> bringToFront;
01880 
01881     for (int i = 0; i < m_lines.size(); i++)
01882     {
01883         int x = m_lines[i].x_indent;
01884         int y = m_lines[i].y_indent;
01885         int height = m_lines[i].CalcSize().height();
01886         QList<FormattedTextChunk>::const_iterator chunk;
01887         bool first = true;
01888         for (chunk = m_lines[i].chunks.constBegin();
01889              chunk != m_lines[i].chunks.constEnd();
01890              ++chunk)
01891         {
01892             MythFontProperties *mythfont =
01893                 parent->GetFont((*chunk).format, (*chunk).isTeletext);
01894             if (!mythfont)
01895                 continue;
01896             QFontMetrics font(*(mythfont->GetFace()));
01897             // If the chunk starts with whitespace, the leading
01898             // whitespace ultimately gets lost due to the
01899             // text.trimmed() operation in the MythUISimpleText
01900             // constructor.  To compensate, we manually indent the
01901             // chunk accordingly.
01902             int count = 0;
01903             while (count < (*chunk).text.length() &&
01904                    (*chunk).text.at(count) == ' ')
01905             {
01906                 ++count;
01907             }
01908             int x_adjust = count * font.width(" ");
01909             int leftPadding = (*chunk).CalcPadding(true);
01910             int rightPadding = (*chunk).CalcPadding(false);
01911             // Account for extra padding before the first chunk.
01912             if (first)
01913                 x += leftPadding;
01914             QSize chunk_sz = (*chunk).CalcSize();
01915             QRect bgrect(x - leftPadding, y,
01916                          chunk_sz.width() + leftPadding + rightPadding,
01917                          height);
01918             // Don't draw a background behind leading spaces.
01919             if (first)
01920                 bgrect.setLeft(bgrect.left() + x_adjust);
01921             MythUIShape *bgshape =
01922                 parent->m_format->
01923                 GetBackground(parent,
01924                               QString("subbg%1x%2@%3,%4")
01925                               .arg(chunk_sz.width()).arg(height)
01926                               .arg(x).arg(y),
01927                               base, (*chunk).format);
01928             bgshape->SetArea(MythRect(bgrect));
01929             if (imageCache)
01930                 imageCache->append(bgshape);
01931             if (duration > 0)
01932                 parent->RegisterExpiration(bgshape, start + duration);
01933             result = true;
01934             // Shift to the right to account for leading spaces that
01935             // are removed by the MythUISimpleText constructor.  Also
01936             // add in padding at the end to avoid clipping.
01937             QRect rect(x + x_adjust, y,
01938                        chunk_sz.width() - x_adjust + rightPadding, height);
01939 
01940             MythUISimpleText *text =
01941                 new MythUISimpleText((*chunk).text, *mythfont, rect,
01942                                      Qt::AlignLeft, (MythUIType*)parent,
01943                                      QString("subtxt%1x%2@%3,%4")
01944                                      .arg(chunk_sz.width())
01945                                      .arg(height)
01946                                      .arg(x).arg(y));
01947             bringToFront.append(text);
01948             if (imageCache)
01949                 imageCache->append(text);
01950             if (duration > 0)
01951                 parent->RegisterExpiration(text, start + duration);
01952             result = true;
01953 
01954             LOG(VB_VBI, LOG_INFO,
01955                 QString("Drawing chunk at (%1,%2): %3")
01956                 .arg(x).arg(y).arg((*chunk).ToLogString()));
01957 
01958             x += chunk_sz.width();
01959             first = false;
01960         }
01961     }
01962     // Move each chunk of text to the front so that it isn't clipped
01963     // by the preceding chunk's background.
01964     for (int i = 0; i < bringToFront.size(); i++)
01965         bringToFront.at(i)->MoveToTop();
01966     return result;
01967 }
01968 
01969 QStringList FormattedTextSubtitle::ToSRT(void) const
01970 {
01971     QStringList result;
01972     for (int i = 0; i < m_lines.size(); i++)
01973     {
01974         QString line;
01975         if (m_lines[i].orig_x > 0)
01976             line.fill(' ', m_lines[i].orig_x);
01977         QList<FormattedTextChunk>::const_iterator chunk;
01978         for (chunk = m_lines[i].chunks.constBegin();
01979              chunk != m_lines[i].chunks.constEnd();
01980              ++chunk)
01981         {
01982             const QString &text = (*chunk).text;
01983             const CC708CharacterAttribute &attr = (*chunk).format;
01984             bool isBlank = !attr.underline && text.trimmed().isEmpty();
01985             if (!isBlank)
01986             {
01987                 if (attr.boldface)
01988                     line += "<b>";
01989                 if (attr.italics)
01990                     line += "<i>";
01991                 if (attr.underline)
01992                     line += "<u>";
01993                 if (attr.GetFGColor() != Qt::white)
01994                     line += QString("<font color=\"%1\">")
01995                         .arg(srtColorString(attr.GetFGColor()));
01996             }
01997             line += text;
01998             if (!isBlank)
01999             {
02000                 if (attr.GetFGColor() != Qt::white)
02001                     line += QString("</font>");
02002                 if (attr.underline)
02003                     line += "</u>";
02004                 if (attr.italics)
02005                     line += "</i>";
02006                 if (attr.boldface)
02007                     line += "</b>";
02008             }
02009         }
02010         if (!line.trimmed().isEmpty())
02011             result += line;
02012     }
02013     return result;
02014 }
02015 
02016 // The QFontMetrics class does not account for the MythFontProperties
02017 // shadow and offset properties.  This method calculates the
02018 // additional padding to the right and below that is needed for proper
02019 // bounding box computation.
02020 static QSize CalcShadowOffsetPadding(MythFontProperties *mythfont)
02021 {
02022     QColor color;
02023     int alpha;
02024     int outlineSize = 0;
02025     int shadowWidth = 0, shadowHeight = 0;
02026     if (mythfont->hasOutline())
02027     {
02028         mythfont->GetOutline(color, outlineSize, alpha);
02029     }
02030     if (mythfont->hasShadow())
02031     {
02032         QPoint shadowOffset;
02033         mythfont->GetShadow(shadowOffset, color, alpha);
02034         shadowWidth = abs(shadowOffset.x());
02035         shadowHeight = abs(shadowOffset.y());
02036         // Shadow and outline overlap, so don't just add them.
02037         shadowWidth = max(shadowWidth, outlineSize);
02038         shadowHeight = max(shadowHeight, outlineSize);
02039     }
02040     return QSize(shadowWidth + outlineSize, shadowHeight + outlineSize);
02041 }
02042 
02043 QSize SubtitleScreen::CalcTextSize(const QString &text,
02044                                    const CC708CharacterAttribute &format,
02045                                    bool teletext,
02046                                    float layoutSpacing) const
02047 {
02048     MythFontProperties *mythfont = GetFont(format, teletext);
02049     QFont *font = mythfont->GetFace();
02050     QFontMetrics fm(*font);
02051     int width = fm.width(text);
02052     int height = fm.height() * (1 + PAD_HEIGHT);
02053     if (layoutSpacing > 0 && !text.trimmed().isEmpty())
02054         height = max(height, (int)(font->pixelSize() * layoutSpacing));
02055     height += CalcShadowOffsetPadding(mythfont).height();
02056     return QSize(width, height);
02057 }
02058 
02059 // Padding calculation is different depending on whether the padding
02060 // is on the left side or the right side of the text.  Padding on the
02061 // right needs to add the shadow and offset padding.
02062 int SubtitleScreen::CalcPadding(const CC708CharacterAttribute &format,
02063                                 bool teletext, bool isLeft) const
02064 {
02065     MythFontProperties *mythfont = GetFont(format, teletext);
02066     QFont *font = mythfont->GetFace();
02067     QFontMetrics fm(*font);
02068     int result = fm.maxWidth() * PAD_WIDTH;
02069     if (!isLeft)
02070         result += CalcShadowOffsetPadding(mythfont).width();
02071     return result;
02072 }
02073 
02074 QString SubtitleScreen::GetTeletextFontName(void)
02075 {
02076     SubtitleFormat format;
02077     CC708CharacterAttribute attr(false, false, false, Qt::white);
02078     MythFontProperties *mythfont =
02079         format.GetFont(kSubFamilyTeletext, attr, 20, false, 100, 100);
02080     return mythfont->face().family();
02081 }
02082 
02083 #ifdef USING_LIBASS
02084 static void myth_libass_log(int level, const char *fmt, va_list vl, void *ctx)
02085 {
02086     static QString full_line("libass:");
02087     static const int msg_len = 255;
02088     static QMutex string_lock;
02089     uint64_t verbose_mask = VB_GENERAL;
02090     LogLevel_t verbose_level = LOG_INFO;
02091 
02092     switch (level)
02093     {
02094         case 0: //MSGL_FATAL
02095             verbose_level = LOG_EMERG;
02096             break;
02097         case 1: //MSGL_ERR
02098             verbose_level = LOG_ERR;
02099             break;
02100         case 2: //MSGL_WARN
02101             verbose_level = LOG_WARNING;
02102             break;
02103         case 4: //MSGL_INFO
02104             verbose_level = LOG_INFO;
02105             break;
02106         case 6: //MSGL_V
02107         case 7: //MSGL_DBG2
02108             verbose_level = LOG_DEBUG;
02109             break;
02110         default:
02111             return;
02112     }
02113 
02114     if (!VERBOSE_LEVEL_CHECK(verbose_mask, verbose_level))
02115         return;
02116 
02117     string_lock.lock();
02118 
02119     char str[msg_len+1];
02120     int bytes = vsnprintf(str, msg_len+1, fmt, vl);
02121     // check for truncated messages and fix them
02122     if (bytes > msg_len)
02123     {
02124         LOG(VB_GENERAL, LOG_ERR,
02125             QString("libASS log output truncated %1 of %2 bytes written")
02126                 .arg(msg_len).arg(bytes));
02127         str[msg_len-1] = '\n';
02128     }
02129 
02130     full_line += QString(str);
02131     if (full_line.endsWith("\n"))
02132     {
02133         LOG(verbose_mask, verbose_level, full_line.trimmed());
02134         full_line.truncate(0);
02135     }
02136     string_lock.unlock();
02137 }
02138 
02139 bool SubtitleScreen::InitialiseAssLibrary(void)
02140 {
02141     if (m_assLibrary && m_assRenderer)
02142         return true;
02143 
02144     if (!m_assLibrary)
02145     {
02146         m_assLibrary = ass_library_init();
02147         if (!m_assLibrary)
02148             return false;
02149 
02150         ass_set_message_cb(m_assLibrary, myth_libass_log, NULL);
02151         ass_set_extract_fonts(m_assLibrary, true);
02152         LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass object.");
02153     }
02154 
02155     LoadAssFonts();
02156 
02157     if (!m_assRenderer)
02158     {
02159         m_assRenderer = ass_renderer_init(m_assLibrary);
02160         if (!m_assRenderer)
02161             return false;
02162 
02163         ass_set_fonts(m_assRenderer, NULL, "sans-serif", 1, NULL, 1);
02164         ass_set_hinting(m_assRenderer, ASS_HINTING_LIGHT);
02165         LOG(VB_PLAYBACK, LOG_INFO, LOC + "Initialised libass renderer.");
02166     }
02167 
02168     return true;
02169 }
02170 
02171 void SubtitleScreen::LoadAssFonts(void)
02172 {
02173     if (!m_assLibrary || !m_player)
02174         return;
02175 
02176     uint count = m_player->GetDecoder()->GetTrackCount(kTrackTypeAttachment);
02177     if (m_assFontCount == count)
02178         return;
02179 
02180     ass_clear_fonts(m_assLibrary);
02181     m_assFontCount = 0;
02182 
02183     // TODO these need checking and/or reinitialising after a stream change
02184     for (uint i = 0; i < count; ++i)
02185     {
02186         QByteArray filename;
02187         QByteArray font;
02188         m_player->GetDecoder()->GetAttachmentData(i, filename, font);
02189         ass_add_font(m_assLibrary, filename.data(), font.data(), font.size());
02190         LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Retrieved font '%1'")
02191             .arg(filename.constData()));
02192         m_assFontCount++;
02193     }
02194 }
02195 
02196 void SubtitleScreen::CleanupAssLibrary(void)
02197 {
02198     CleanupAssTrack();
02199 
02200     if (m_assRenderer)
02201         ass_renderer_done(m_assRenderer);
02202     m_assRenderer = NULL;
02203 
02204     if (m_assLibrary)
02205     {
02206         ass_clear_fonts(m_assLibrary);
02207         m_assFontCount = 0;
02208         ass_library_done(m_assLibrary);
02209     }
02210     m_assLibrary = NULL;
02211 }
02212 
02213 void SubtitleScreen::InitialiseAssTrack(int tracknum)
02214 {
02215     if (!InitialiseAssLibrary() || !m_player)
02216         return;
02217 
02218     if (tracknum == m_assTrackNum && m_assTrack)
02219         return;
02220 
02221     LoadAssFonts();
02222     CleanupAssTrack();
02223     m_assTrack = ass_new_track(m_assLibrary);
02224     m_assTrackNum = tracknum;
02225 
02226     QByteArray header = m_player->GetDecoder()->GetSubHeader(tracknum);
02227     if (!header.isNull())
02228         ass_process_codec_private(m_assTrack, header.data(), header.size());
02229 
02230     m_safeArea = m_player->GetVideoOutput()->GetMHEGBounds();
02231     ResizeAssRenderer();
02232 }
02233 
02234 void SubtitleScreen::CleanupAssTrack(void)
02235 {
02236     if (m_assTrack)
02237         ass_free_track(m_assTrack);
02238     m_assTrack = NULL;
02239 }
02240 
02241 void SubtitleScreen::AddAssEvent(char *event)
02242 {
02243     if (m_assTrack && event)
02244         ass_process_data(m_assTrack, event, strlen(event));
02245 }
02246 
02247 void SubtitleScreen::ResizeAssRenderer(void)
02248 {
02249     // TODO this probably won't work properly for anamorphic content and XVideo
02250     ass_set_frame_size(m_assRenderer, m_safeArea.width(), m_safeArea.height());
02251     ass_set_margins(m_assRenderer, 0, 0, 0, 0);
02252     ass_set_use_margins(m_assRenderer, true);
02253     ass_set_font_scale(m_assRenderer, 1.0);
02254 }
02255 
02256 void SubtitleScreen::RenderAssTrack(uint64_t timecode)
02257 {
02258     if (!m_player || !m_assRenderer || !m_assTrack)
02259         return;
02260 
02261     VideoOutput *vo = m_player->GetVideoOutput();
02262     if (!vo )
02263         return;
02264 
02265     QRect oldscreen = m_safeArea;
02266     m_safeArea = vo->GetMHEGBounds();
02267     if (oldscreen != m_safeArea)
02268         ResizeAssRenderer();
02269 
02270     int changed = 0;
02271     ASS_Image *images = ass_render_frame(m_assRenderer, m_assTrack,
02272                                          timecode, &changed);
02273     if (!changed)
02274         return;
02275 
02276     MythPainter *osd_painter = vo->GetOSDPainter();
02277     if (!osd_painter)
02278         return;
02279 
02280     int count = 0;
02281     DeleteAllChildren();
02282     SetRedraw();
02283     while (images)
02284     {
02285         if (images->w == 0 || images->h == 0)
02286         {
02287             images = images->next;
02288             continue;
02289         }
02290 
02291         uint8_t alpha = images->color & 0xFF;
02292         uint8_t blue = images->color >> 8 & 0xFF;
02293         uint8_t green = images->color >> 16 & 0xFF;
02294         uint8_t red = images->color >> 24 & 0xFF;
02295 
02296         if (alpha == 255)
02297         {
02298             images = images->next;
02299             continue;
02300         }
02301 
02302         QSize img_size(images->w, images->h);
02303         QRect img_rect(images->dst_x,images->dst_y,
02304                        images->w, images->h);
02305         QImage qImage(img_size, QImage::Format_ARGB32);
02306         qImage.fill(0x00000000);
02307 
02308         unsigned char *src = images->bitmap;
02309         for (int y = 0; y < images->h; ++y)
02310         {
02311             for (int x = 0; x < images->w; ++x)
02312             {
02313                 uint8_t value = src[x];
02314                 if (value)
02315                 {
02316                     uint32_t pixel = (value * (255 - alpha) / 255 << 24) |
02317                                      (red << 16) | (green << 8) | blue;
02318                     qImage.setPixel(x, y, pixel);
02319                 }
02320             }
02321             src += images->stride;
02322         }
02323 
02324         MythImage* image = NULL;
02325         MythUIImage *uiimage = NULL;
02326 
02327         if (osd_painter)
02328             image = osd_painter->GetFormatImage();
02329 
02330         if (image)
02331         {
02332             image->Assign(qImage);
02333             QString name = QString("asssub%1").arg(count);
02334             uiimage = new MythUIImage(this, name);
02335             if (uiimage)
02336             {
02337                 m_refreshArea = true;
02338                 uiimage->SetImage(image);
02339                 uiimage->SetArea(MythRect(img_rect));
02340             }
02341         }
02342         images = images->next;
02343         count++;
02344     }
02345 }
02346 #endif // USING_LIBASS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends