bool CKaraokeLyricsText::InitGraphics() { if ( m_lyrics.empty() ) return false; CStdString fontPath = "special://xbmc/media/Fonts/" + g_guiSettings.GetString("karaoke.font"); m_karaokeFont = g_fontManager.LoadTTF("__karaoke__", fontPath, m_colorLyrics, 0, g_guiSettings.GetInt("karaoke.fontheight"), FONT_STYLE_BOLD ); CGUIFont *karaokeBorder = g_fontManager.LoadTTF("__karaokeborder__", fontPath, m_colorLyrics, 0, g_guiSettings.GetInt("karaoke.fontheight"), FONT_STYLE_BOLD, true ); if ( !m_karaokeFont ) { CLog::Log(LOGERROR, "CKaraokeLyricsText::PrepareGraphicsData - Unable to load subtitle font"); return false; } m_karaokeLayout = new CGUITextLayout( m_karaokeFont, true, 0, karaokeBorder ); m_preambleLayout = new CGUITextLayout( m_karaokeFont, true, 0, karaokeBorder ); if ( !m_karaokeLayout || !m_preambleLayout ) { delete m_preambleLayout; delete m_karaokeLayout; m_karaokeLayout = m_preambleLayout = 0; CLog::Log(LOGERROR, "CKaraokeLyricsText::PrepareGraphicsData - cannot create layout"); return false; } rescanLyrics(); m_indexNextPara = 0; // Generate next paragraph nextParagraph(); m_lyricsState = STATE_WAITING; return true; }
bool RstParagraphIterator::nextParagraph() { begin_ = nextBegin_; type_ = eParagraphType_Normal; breakSize_ = nextBreakSize_; // Skip leading newlines (includes those separating paragraphs). while (begin_ < text_.length() && text_[begin_] == '\n') { ++begin_; } if (begin_ == text_.length()) { end_ = begin_; breakSize_ = 0; nextBegin_ = begin_; return false; } if (literalIndent_ >= 0) { type_ = eParagraphType_Literal; } // Loop over lines in input until the end of the current paragraph. size_t i = begin_; int lineCount = 0; while (true) { const bool bFirstLine = (lineCount == 0); const size_t lineStart = i; const size_t lineEnd = std::min(text_.find('\n', i), text_.length()); const int lineIndent = countLeadingSpace(text_, lineStart, lineEnd); const size_t textStart = lineStart + lineIndent; const bool bListItem = startsListItem(text_, textStart); // Return each list item as a separate paragraph to make the behavior // the same always; the item text could even contain multiple // paragraphs, that would anyways produce breaks. if (bListItem && !bFirstLine) { // Since there was no empty line in input, do not produce one in // the output, either. nextBreakSize_ = 1; // end_ is not updated to break the paragraph before the current line. break; } // Now we will actually use this line as part of this paragraph. end_ = lineEnd; ++lineCount; // Update indentation. if (bFirstLine) { firstLineIndent_ = indent_ = lineIndent; if (bListItem) { // Find the indentation of the actual text after the // bullet/number. int prefixLength = 0; while (!std::isspace(text_[textStart + prefixLength])) { ++prefixLength; } while (textStart + prefixLength < text_.length() && std::isspace(text_[textStart + prefixLength])) { ++prefixLength; } indent_ += prefixLength; } } else { indent_ = std::min(indent_, lineIndent); } // We need to check for the title underline before checking for the // paragraph break so that the title is correctly recognized. if (lineCount == 2 && isTitleUnderline(text_, lineStart)) { type_ = eParagraphType_Title; } // Check for end-of-input or an empty line, i.e., a normal paragraph // break. if (lineEnd + 1 >= text_.length() || text_[lineEnd + 1] == '\n') { nextBreakSize_ = 2; break; } // Always return the title as a separate paragraph, as it requires // different processing. // TODO: This should allow nicer formatting that shares // implementation with writeTitle() and honors the nesting depths etc., // but that is not implemented. if (type_ == eParagraphType_Title) { // If we are here, there was no actual paragraph break, so do not // produce one in the output either. nextBreakSize_ = 1; break; } // Next loop starts at the character after the newline. i = lineEnd + 1; } nextBegin_ = end_; // Check if the next paragraph should be treated as a literal paragraph, // and deal with transformations for the :: marker. if (end_ - begin_ >= 2 && text_.compare(end_ - 2, 2, "::") == 0) { literalIndent_ = indent_; // Return the actual literal block if the paragraph was just an "::". if (end_ - begin_ == 2) { // Avoid leading whitespace at the beginning; breakSize_ == 0 // only for the first paragraph. if (breakSize_ == 0) { nextBreakSize_ = 0; } return nextParagraph(); } // Remove one of the colons, or both if preceded by whitespace. const bool bRemoveDoubleColon = (text_[end_ - 3] == ' '); end_ -= (bRemoveDoubleColon ? 3 : 1); } else { literalIndent_ = -1; } // Treat a table like a literal block (preserve newlines). if (startsTable(text_, begin_ + firstLineIndent_)) { type_ = eParagraphType_Literal; } return true; }
void CKaraokeLyricsText::Render() { if ( !m_karaokeLayout ) return; // Get the current song timing unsigned int songTime = (unsigned int) MathUtils::round_int( (getSongTime() * 10) ); bool updatePreamble = false; bool updateText = false; // No returns in switch if anything needs to be drawn! Just break! switch ( m_lyricsState ) { // the next paragraph lyrics are not shown yet. Screen is clear. // m_index points to the first entry. case STATE_WAITING: if ( songTime + m_showLyricsBeforeStart < m_lyrics[ m_index ].timing ) return; // Is it time to play already? if ( songTime >= m_lyrics[ m_index ].timing ) { m_lyricsState = STATE_PLAYING_PARAGRAPH; } else { m_lyricsState = STATE_PREAMBLE; m_lastPreambleUpdate = songTime; } updateText = true; break; // the next paragraph lyrics are shown, but the paragraph hasn't start yet. // Using m_lastPreambleUpdate, we redraw the marker each second. case STATE_PREAMBLE: if ( songTime < m_lyrics[ m_index ].timing ) { // Time to redraw preamble? if ( songTime + m_showPreambleBeforeStart >= m_lyrics[ m_index ].timing ) { if ( songTime - m_lastPreambleUpdate >= 10 ) { // Fall through out of switch() to redraw m_lastPreambleUpdate = songTime; updatePreamble = true; } } } else { updateText = true; m_lyricsState = STATE_PLAYING_PARAGRAPH; } break; // The lyrics are shown, but nothing is colored or no color is changed yet. // m_indexStart, m_indexEnd and m_index are set, m_index timing shows when to color. case STATE_PLAYING_PARAGRAPH: if ( songTime >= m_lyrics[ m_index ].timing ) { m_index++; updateText = true; if ( m_index > m_indexEndPara ) m_lyricsState = STATE_END_PARAGRAPH; } break; // the whole paragraph is colored, but still shown, waiting until it's time to clear the lyrics. // m_index still points to the last entry, and m_indexNextPara points to the first entry of next // paragraph, or to LYRICS_END. When the next paragraph is about to start (which is // m_indexNextPara timing - m_showLyricsBeforeStart), the state switches to STATE_START_PARAGRAPH. When time // goes after m_index timing + m_delayAfter, the state switches to STATE_WAITING, case STATE_END_PARAGRAPH: { unsigned int paraEnd = m_lyrics[ m_indexEndPara ].timing + m_delayAfter; // If the next paragraph starts before current ends, use its start time as our end if ( m_indexNextPara != LYRICS_END && m_lyrics[ m_indexNextPara ].timing <= paraEnd + m_showLyricsBeforeStart ) { if ( m_lyrics[ m_indexNextPara ].timing > m_showLyricsBeforeStart ) paraEnd = m_lyrics[ m_indexNextPara ].timing - m_showLyricsBeforeStart; else paraEnd = 0; } if ( songTime >= paraEnd ) { // Is the song ended? if ( m_indexNextPara != LYRICS_END ) { // Are we still waiting? if ( songTime >= m_lyrics[ m_indexNextPara ].timing ) m_lyricsState = STATE_PLAYING_PARAGRAPH; else m_lyricsState = STATE_WAITING; // Get next paragraph nextParagraph(); updateText = true; } else { m_lyricsState = STATE_END_SONG; return; } } } break; case STATE_END_SONG: // the song is completed, there are no more lyrics to show. This state is finita la comedia. return; } // Calculate drawing parameters RESOLUTION resolution = g_graphicsContext.GetVideoResolution(); g_graphicsContext.SetRenderingResolution(resolution, false); float maxWidth = (float) g_settings.m_ResInfo[resolution].Overscan.right - g_settings.m_ResInfo[resolution].Overscan.left; // We must only fall through for STATE_DRAW_SYLLABLE or STATE_PREAMBLE if ( updateText ) { // So we need to update the layout with current paragraph text, optionally colored according to index bool color_used = false; m_currentLyrics = ""; // Draw the current paragraph test if needed if ( songTime + m_showLyricsBeforeStart >= m_lyrics[ m_indexStartPara ].timing ) { for ( unsigned int i = m_indexStartPara; i <= m_indexEndPara; i++ ) { if ( m_lyrics[i].flags & LYRICS_NEW_LINE ) m_currentLyrics += "[CR]"; if ( i == m_indexStartPara && songTime >= m_lyrics[ m_indexStartPara ].timing ) { color_used = true; m_currentLyrics += "[COLOR " + m_colorSinging + "]"; } if ( songTime < m_lyrics[ i ].timing && color_used ) { color_used = false; m_currentLyrics += "[/COLOR]"; } m_currentLyrics += m_lyrics[i].text; } if ( color_used ) m_currentLyrics += "[/COLOR]"; // CLog::Log( LOGERROR, "Updating text: state %d, time %d, start %d, index %d (time %d) [%s], text %s", // m_lyricsState, songTime, m_lyrics[ m_indexStartPara ].timing, m_index, m_lyrics[ m_index ].timing, // m_lyrics[ m_index ].text.c_str(), m_currentLyrics.c_str()); } m_karaokeLayout->Update(m_currentLyrics, maxWidth * 0.9f); updateText = false; } if ( updatePreamble ) { m_currentPreamble = ""; // Get number of seconds left to the song start if ( m_lyrics[ m_indexStartPara ].timing >= songTime ) { unsigned int seconds = (m_lyrics[ m_indexStartPara ].timing - songTime) / 10; while ( seconds-- > 0 ) m_currentPreamble += "- "; } m_preambleLayout->Update( m_currentPreamble, maxWidth * 0.9f ); } float x = maxWidth * 0.5f + g_settings.m_ResInfo[resolution].Overscan.left; float y = (float)g_settings.m_ResInfo[resolution].Overscan.top + (g_settings.m_ResInfo[resolution].Overscan.bottom - g_settings.m_ResInfo[resolution].Overscan.top) / 8; float textWidth, textHeight; m_karaokeLayout->GetTextExtent(textWidth, textHeight); m_karaokeLayout->RenderOutline(x, y, 0, m_colorLyricsOutline, XBFONT_CENTER_X, maxWidth); if ( !m_currentPreamble.IsEmpty() ) { float pretextWidth, pretextHeight; m_preambleLayout->GetTextExtent(pretextWidth, pretextHeight); m_preambleLayout->RenderOutline(x - textWidth / 2, y - pretextHeight, 0, m_colorLyricsOutline, XBFONT_LEFT, maxWidth); } }