718 | | vector<CC608Text*>::iterator i = textlist->buffers.begin(); |
719 | | bool teletextmode = (*i)->teletextmode; |
720 | | int xscale = teletextmode ? 40 : 36; |
721 | | int yscale = teletextmode ? 25 : 17; |
722 | | gTextSubFont->GetFace()->setPixelSize(m_safeArea.height() / (yscale * 1.2)); |
723 | | QBrush bgfill = QBrush(QColor(0, 0, 0), Qt::SolidPattern); |
724 | | |
725 | | for (; i != textlist->buffers.end(); ++i) |
726 | | { |
727 | | CC608Text *cc = (*i); |
728 | | int color = 0; |
729 | | bool isItalic = false, isUnderline = false; |
730 | | bool first = true; |
731 | | bool showedNonControl = false; |
732 | | int x = 0, width = 0; |
733 | | QString text(cc->text); |
734 | | |
735 | | for (int chunk = 0; text != QString::null; first = false, chunk++) |
736 | | { |
737 | | QString captionText = |
738 | | extract_cc608(text, cc->teletextmode, |
739 | | color, isItalic, isUnderline, |
740 | | showedNonControl); |
741 | | gTextSubFont->GetFace()->setItalic(isItalic); |
742 | | gTextSubFont->GetFace()->setUnderline(isUnderline); |
743 | | gTextSubFont->SetColor(clr[min(max(0, color), 7)]); |
744 | | QFontMetrics font(*(gTextSubFont->GetFace())); |
745 | | // XXX- could there be different heights across the same line? |
746 | | int height = font.height() * (1 + PAD_HEIGHT); |
747 | | if (first) |
748 | | { |
749 | | x = teletextmode ? cc->y : (cc->x + 3); |
750 | | x = (int)(((float)x / (float)xscale) * |
751 | | (float)m_safeArea.width()); |
752 | | } |
753 | | else |
754 | | { |
755 | | x += width; // bump x by the previous width |
756 | | } |
757 | | |
758 | | int pad_width = font.maxWidth() * PAD_WIDTH; |
759 | | width = font.width(captionText) + pad_width; |
760 | | int y = teletextmode ? cc->x : cc->y; |
761 | | y = (int)(((float)y / (float)yscale) * (float)m_safeArea.height()); |
762 | | // Sometimes a line of caption text begins with a mid-row |
763 | | // format control like italics or a color change. The |
764 | | // spec says the mid-row control also includes a space |
765 | | // character. But this looks clumsy when using a |
766 | | // background, so we suppress it after the placement is |
767 | | // calculated. |
768 | | if (!showedNonControl) |
769 | | continue; |
770 | | QRect rect(x, y, width, height); |
771 | | |
772 | | if (!teletextmode && m_useBackground) |
773 | | { |
774 | | MythUIShape *shape = new MythUIShape(this, |
775 | | QString("cc608bg%1%2%3").arg(cc->x).arg(cc->y).arg(width)); |
776 | | shape->SetFillBrush(bgfill); |
777 | | QRect bgrect(x - pad_width, y, width + pad_width, height); |
778 | | shape->SetArea(MythRect(bgrect)); |
779 | | } |
780 | | |
781 | | new MythUISimpleText(captionText, *gTextSubFont, rect, |
782 | | Qt::AlignLeft, (MythUIType*)this, |
783 | | QString("cc608txt%1%2%3%4") |
784 | | .arg(cc->x).arg(cc->y) |
785 | | .arg(width).arg(chunk)); |
786 | | |
787 | | m_refreshArea = true; |
788 | | |
789 | | LOG(VB_VBI, LOG_INFO, |
790 | | QString("x %1 y %2 uline=%4 ital=%5 " |
791 | | "color=%6 coord=%7,%8 String: '%3'") |
792 | | .arg(cc->x).arg(cc->y).arg(captionText) |
793 | | .arg(isUnderline).arg(isItalic).arg(color).arg(x).arg(y)); |
794 | | } |
795 | | } |
| 643 | FormattedTextSubtitle fsub(m_safeArea); |
| 644 | fsub.InitFromCC608(textlist->buffers); |
| 645 | fsub.Draw(this); |
| 646 | m_refreshArea = true; |
| 1043 | void FormattedTextChunk::ComputeSize(void) |
| 1044 | { |
| 1045 | QFont *font = gTextSubFont->GetFace(); |
| 1046 | font->setItalic(isItalic); |
| 1047 | font->setBold(isBold); |
| 1048 | font->setUnderline(isUnderline); |
| 1049 | QFontMetrics fm(*font); |
| 1050 | width = fm.width(text) + fm.maxWidth() * PAD_WIDTH; |
| 1051 | height = fm.height() * (1 + PAD_HEIGHT); |
| 1052 | } |
| 1053 | |
| 1054 | void FormattedTextSubtitle::InitFromCC608(vector<CC608Text*> &buffers) |
| 1055 | { |
| 1056 | static const QColor clr[8] = |
| 1057 | { |
| 1058 | Qt::white, Qt::green, Qt::blue, Qt::cyan, |
| 1059 | Qt::red, Qt::yellow, Qt::magenta, Qt::white, |
| 1060 | }; |
| 1061 | |
| 1062 | if (buffers.empty()) |
| 1063 | return; |
| 1064 | vector<CC608Text*>::iterator i = buffers.begin(); |
| 1065 | bool teletextmode = (*i)->teletextmode; |
| 1066 | m_useBackground = m_useBackground && !teletextmode; |
| 1067 | int xscale = teletextmode ? 40 : 36; |
| 1068 | int yscale = teletextmode ? 25 : 17; |
| 1069 | m_pixelSize = m_safeArea.height() / (yscale * 1.2); |
| 1070 | |
| 1071 | for (; i != buffers.end(); ++i) |
| 1072 | { |
| 1073 | CC608Text *cc = (*i); |
| 1074 | int color = 0; |
| 1075 | bool isItalic = false, isUnderline = false; |
| 1076 | const bool isBold = false; |
| 1077 | QString text(cc->text); |
| 1078 | |
| 1079 | int orig_x = teletextmode ? cc->y : (cc->x + 3); |
| 1080 | int x = (int)(((float)orig_x / (float)xscale) * (float)m_safeArea.width()); |
| 1081 | int orig_y = teletextmode ? cc->x : cc->y; |
| 1082 | int y = (int)(((float)orig_y / (float)yscale) * (float)m_safeArea.height()); |
| 1083 | FormattedTextLine line(x, y); |
| 1084 | for (int chunk = 0; text != QString::null; chunk++) |
| 1085 | { |
| 1086 | QString captionText = |
| 1087 | extract_cc608(text, cc->teletextmode, |
| 1088 | color, isItalic, isUnderline); |
| 1089 | FormattedTextChunk chunk(captionText, |
| 1090 | isItalic, isBold, isUnderline, |
| 1091 | clr[min(max(0, color), 7)], |
| 1092 | orig_x, orig_y); |
| 1093 | line.chunks += chunk; |
| 1094 | LOG(VB_VBI, LOG_INFO, |
| 1095 | QString("Adding cc608 chunk: x=%1 y=%2 " |
| 1096 | "uline=%3 ital=%4 color=%5 text='%6'") |
| 1097 | .arg(cc->x).arg(cc->y) |
| 1098 | .arg(isUnderline).arg(isItalic).arg(color).arg(captionText)); |
| 1099 | } |
| 1100 | m_lines += line; |
| 1101 | } |
| 1102 | } |
| 1103 | |
| 1104 | void FormattedTextSubtitle::InitFromSRT(QStringList &subs, int textFontZoom) |
| 1105 | { |
| 1106 | // Does a simplistic parsing of HTML tags from the strings. |
| 1107 | // Nesting is not implemented. Stray whitespace may cause |
| 1108 | // legitimate tags to be ignored. All other HTML tags are |
| 1109 | // stripped and ignored. |
| 1110 | // |
| 1111 | // <i> - enable italics |
| 1112 | // </i> - disable italics |
| 1113 | // <b> - enable boldface |
| 1114 | // </b> - disable boldface |
| 1115 | // <u> - enable underline |
| 1116 | // </u> - disable underline |
| 1117 | // <font color="#xxyyzz"> - change font color |
| 1118 | // </font> - reset font color to white |
| 1119 | |
| 1120 | m_pixelSize = (m_safeArea.height() * textFontZoom) / 1800; |
| 1121 | |
| 1122 | bool isItalic = false; |
| 1123 | bool isBold = false; |
| 1124 | bool isUnderline = false; |
| 1125 | QColor color(Qt::white); |
| 1126 | QRegExp htmlTag("</?.+>"); |
| 1127 | QString htmlPrefix("<font color=\""), htmlSuffix("\">"); |
| 1128 | htmlTag.setMinimal(true); |
| 1129 | foreach (QString subtitle, subs) |
| 1130 | { |
| 1131 | FormattedTextLine line; |
| 1132 | QString text(subtitle); |
| 1133 | while (!text.isEmpty()) |
| 1134 | { |
| 1135 | int pos = text.indexOf(htmlTag); |
| 1136 | if (pos != 0) // don't add a zero-length string |
| 1137 | { |
| 1138 | FormattedTextChunk chunk(text.left(pos), |
| 1139 | isItalic, isBold, isUnderline, |
| 1140 | color, -1, -1); |
| 1141 | line.chunks += chunk; |
| 1142 | text = (pos < 0 ? "" : text.mid(pos)); |
| 1143 | LOG(VB_VBI, LOG_INFO, QString("Adding SRT chunk '%1'").arg(chunk.text)); |
| 1144 | } |
| 1145 | if (pos >= 0) |
| 1146 | { |
| 1147 | int htmlLen = htmlTag.matchedLength(); |
| 1148 | QString html = text.left(htmlLen).toLower(); |
| 1149 | text = text.mid(htmlLen); |
| 1150 | if (html == "<i>") |
| 1151 | isItalic = true; |
| 1152 | else if (html == "</i>") |
| 1153 | isItalic = false; |
| 1154 | else if (html.startsWith(htmlPrefix) && |
| 1155 | html.endsWith(htmlSuffix)) |
| 1156 | { |
| 1157 | int colorLen = html.length() - |
| 1158 | (htmlPrefix.length() + htmlSuffix.length()); |
| 1159 | QString colorString(html.mid(htmlPrefix.length(), colorLen)); |
| 1160 | QColor newColor(colorString); |
| 1161 | if (newColor.isValid()) |
| 1162 | color = newColor; |
| 1163 | else |
| 1164 | LOG(VB_VBI, LOG_INFO, |
| 1165 | QString("Ignoring invalid SRT color specification: " |
| 1166 | "'%1'").arg(colorString)); |
| 1167 | } |
| 1168 | else if (html == "</font>") |
| 1169 | color = Qt::white; |
| 1170 | else if (html == "<b>") |
| 1171 | isBold = true; |
| 1172 | else if (html == "</b>") |
| 1173 | isBold = false; |
| 1174 | else if (html == "<u>") |
| 1175 | isUnderline = true; |
| 1176 | else if (html == "</u>") |
| 1177 | isUnderline = false; |
| 1178 | else |
| 1179 | LOG(VB_VBI, LOG_INFO, |
| 1180 | QString("Ignoring unknown SRT formatting: '%1'").arg(html)); |
| 1181 | |
| 1182 | LOG(VB_VBI, LOG_INFO, |
| 1183 | QString("SRT formatting change '%1', " |
| 1184 | "new ital=%2 bold=%3 uline=%4 color=(%5,%6,%7)") |
| 1185 | .arg(html).arg(isItalic).arg(isBold).arg(isUnderline) |
| 1186 | .arg(color.red(), 2, 16, QLatin1Char('0')) |
| 1187 | .arg(color.green(), 2, 16, QLatin1Char('0')) |
| 1188 | .arg(color.blue(), 2, 16, QLatin1Char('0'))); |
| 1189 | } |
| 1190 | } |
| 1191 | m_lines += line; |
| 1192 | } |
| 1193 | } |
| 1194 | |
| 1195 | bool FormattedTextChunk::Split(FormattedTextChunk &newChunk) |
| 1196 | { |
| 1197 | LOG(VB_VBI, LOG_INFO, |
| 1198 | QString("Attempting to split chunk '%1'").arg(text)); |
| 1199 | int lastSpace = text.lastIndexOf(' ', -2); // -2 to ignore trailing space |
| 1200 | if (lastSpace < 0) |
| 1201 | { |
| 1202 | LOG(VB_VBI, LOG_INFO, |
| 1203 | QString("Failed to split chunk '%1'").arg(text)); |
| 1204 | return false; |
| 1205 | } |
| 1206 | newChunk.isItalic = isItalic; |
| 1207 | newChunk.isBold = isBold; |
| 1208 | newChunk.isUnderline = isUnderline; |
| 1209 | newChunk.color = color; |
| 1210 | newChunk.debug_x = debug_x; |
| 1211 | newChunk.debug_y = debug_y; |
| 1212 | newChunk.text = text.mid(lastSpace + 1).trimmed() + ' '; |
| 1213 | text = text.left(lastSpace).trimmed(); |
| 1214 | newChunk.ComputeSize(); |
| 1215 | ComputeSize(); |
| 1216 | LOG(VB_VBI, LOG_INFO, |
| 1217 | QString("Split chunk into '%1' + '%2'").arg(text).arg(newChunk.text)); |
| 1218 | return true; |
| 1219 | } |
| 1220 | |
| 1221 | void FormattedTextSubtitle::WrapLongLines(void) |
| 1222 | { |
| 1223 | int maxWidth = m_safeArea.width(); |
| 1224 | QList<FormattedTextLine>::iterator i = m_lines.begin(); |
| 1225 | while (i != m_lines.end()) |
| 1226 | { |
| 1227 | int width = (*i).Width(); |
| 1228 | // Move entire chunks to the next line as necessary. Leave at |
| 1229 | // least one chunk on the current line. |
| 1230 | while (width > maxWidth && (*i).chunks.size() > 1) |
| 1231 | { |
| 1232 | width -= (*i).chunks.back().width; |
| 1233 | // Make sure there's a next line to wrap into. |
| 1234 | if (i + 1 == m_lines.end()) |
| 1235 | m_lines += FormattedTextLine((*i).x_indent, (*i).y_indent); |
| 1236 | (*(i+1)).chunks.prepend((*i).chunks.takeLast()); |
| 1237 | LOG(VB_VBI, LOG_INFO, |
| 1238 | QString("Wrapping chunk to next line: '%1'") |
| 1239 | .arg((*(i+1)).chunks[0].text)); |
| 1240 | } |
| 1241 | // Split the last chunk until width is small enough or until |
| 1242 | // no more splits are possible. |
| 1243 | bool isSplitPossible = true; |
| 1244 | while (width > maxWidth && isSplitPossible) |
| 1245 | { |
| 1246 | FormattedTextChunk newChunk; |
| 1247 | isSplitPossible = (*i).chunks.back().Split(newChunk); |
| 1248 | if (isSplitPossible) |
| 1249 | { |
| 1250 | // Make sure there's a next line to split into. |
| 1251 | if (i + 1 == m_lines.end()) |
| 1252 | m_lines += FormattedTextLine((*i).x_indent, (*i).y_indent); |
| 1253 | (*(i+1)).chunks.prepend(newChunk); |
| 1254 | width = (*i).Width(); |
| 1255 | } |
| 1256 | } |
| 1257 | ++i; |
| 1258 | } |
| 1259 | } |
| 1260 | |
| 1261 | void FormattedTextSubtitle::Draw(SubtitleScreen *parent, |
| 1262 | uint64_t start, uint64_t duration) const |
| 1263 | { |
| 1264 | bool useBackground = m_useBackground && parent->GetUseBackground(); |
| 1265 | gTextSubFont->GetFace()->setPixelSize(m_pixelSize); |
| 1266 | QFontMetrics font(*(gTextSubFont->GetFace())); |
| 1267 | int pad_width = font.maxWidth() * PAD_WIDTH; |
| 1268 | QBrush bgfill = QBrush(QColor(0, 0, 0), Qt::SolidPattern); |
| 1269 | |
| 1270 | int lineNum = 0; |
| 1271 | QList<FormattedTextLine>::const_iterator line = m_lines.constBegin(); |
| 1272 | for (; line != m_lines.constEnd(); ++line, ++lineNum) |
| 1273 | { |
| 1274 | int x = (*line).x_indent; |
| 1275 | if (x < 0) // centering |
| 1276 | x = (m_safeArea.width() - (*line).Width()) / 2; |
| 1277 | int y = (*line).y_indent; |
| 1278 | if (y < 0) // stack lines at bottom |
| 1279 | y = m_safeArea.height() - |
| 1280 | (m_lines.size() - lineNum) * (*line).Height(); |
| 1281 | |
| 1282 | if (useBackground) |
| 1283 | { |
| 1284 | QRect bgrect(x - pad_width, y, |
| 1285 | (*line).Width() + pad_width, (*line).Height()); |
| 1286 | // Special case. If the first chunk is entirely |
| 1287 | // whitespace, don't put a black background behind that |
| 1288 | // chunk. |
| 1289 | // |
| 1290 | // This often happens when a line of cc608 caption text |
| 1291 | // begins with a mid-row format control like italics or a |
| 1292 | // color change. The spec says the mid-row control |
| 1293 | // implies a space character, which needs to be preserved |
| 1294 | // so that the rest of the text is accurately laid out. A |
| 1295 | // leading space looks clumsy against the black |
| 1296 | // background, so we adjust the background accordingly. |
| 1297 | if (!(*line).chunks.isEmpty() && |
| 1298 | (*line).chunks[0].text.trimmed().isEmpty()) |
| 1299 | { |
| 1300 | bgrect.setLeft(bgrect.left() + (*line).chunks[0].width); |
| 1301 | } |
| 1302 | MythUIShape *shape = |
| 1303 | new MythUIShape(parent, QString("subbg%1_%2").arg(x).arg(y)); |
| 1304 | shape->SetFillBrush(bgfill); |
| 1305 | shape->SetArea(MythRect(bgrect)); |
| 1306 | if (duration > 0) |
| 1307 | parent->RegisterExpiration(shape, start + duration); |
| 1308 | } |
| 1309 | QList<FormattedTextChunk>::const_iterator chunk; |
| 1310 | for (chunk = (*line).chunks.constBegin(); |
| 1311 | chunk != (*line).chunks.constEnd(); |
| 1312 | ++chunk) |
| 1313 | { |
| 1314 | // If the chunk starts with whitespace, the leading |
| 1315 | // whitespace ultimately gets lost due to the |
| 1316 | // text.trimmed() operation in the MythUISimpleText |
| 1317 | // constructor. To compensate, we manually indent the |
| 1318 | // chunk accordingly. |
| 1319 | int count = 0; |
| 1320 | while (count < (*chunk).text.length() && (*chunk).text.at(count) == ' ') |
| 1321 | ++count; |
| 1322 | int x_adjust = count * font.width(" "); |
| 1323 | gTextSubFont->GetFace()->setItalic((*chunk).isItalic); |
| 1324 | gTextSubFont->GetFace()->setBold((*chunk).isBold); |
| 1325 | gTextSubFont->GetFace()->setUnderline((*chunk).isUnderline); |
| 1326 | gTextSubFont->SetColor((*chunk).color); |
| 1327 | QRect rect(x + x_adjust, y, (*chunk).width - x_adjust, (*chunk).height); |
| 1328 | |
| 1329 | MythUISimpleText *text = |
| 1330 | new MythUISimpleText((*chunk).text, *gTextSubFont, rect, |
| 1331 | Qt::AlignLeft, (MythUIType*)parent, |
| 1332 | QString("subtxt%1x%2@%3,%4") |
| 1333 | .arg((*chunk).width).arg((*chunk).height) |
| 1334 | .arg(x).arg(y)); |
| 1335 | if (duration > 0) |
| 1336 | parent->RegisterExpiration(text, start + duration); |
| 1337 | |
| 1338 | LOG(VB_VBI, LOG_INFO, |
| 1339 | QString("Drawing chunk at (%1,%2) with " |
| 1340 | "ital=%3 bold=%4 uline=%5 color=(%6,%7,%8) " |
| 1341 | "text='%9'") |
| 1342 | .arg(x).arg(y) |
| 1343 | .arg((*chunk).isItalic) |
| 1344 | .arg((*chunk).isBold) |
| 1345 | .arg((*chunk).isUnderline) |
| 1346 | .arg((*chunk).color.red(), 2, 16, QLatin1Char('0')) |
| 1347 | .arg((*chunk).color.green(), 2, 16, QLatin1Char('0')) |
| 1348 | .arg((*chunk).color.blue(), 2, 16, QLatin1Char('0')) |
| 1349 | .arg((*chunk).text)); |
| 1350 | |
| 1351 | x += (*chunk).width; |
| 1352 | } |
| 1353 | } |
| 1354 | } |
| 1355 | |