#include "config.h"
#if ENABLE(VIDEO_TRACK)
#include "TextTrackCue.h"
#include "CSSPropertyNames.h"
#include "CSSValueKeywords.h"
#include "DocumentFragment.h"
#include "Event.h"
#include "HTMLDivElement.h"
#include "Text.h"
#include "TextTrack.h"
#include "TextTrackCueList.h"
#include "WebVTTParser.h"
#include <wtf/text/StringBuilder.h>
namespace WebCore {
static const int invalidCueIndex = -1;
static const int undefinedPosition = -1;
static const int autoSize = 0;
static const String& startKeyword()
{
DEFINE_STATIC_LOCAL(const String, start, ("start"));
return start;
}
static const String& middleKeyword()
{
DEFINE_STATIC_LOCAL(const String, middle, ("middle"));
return middle;
}
static const String& endKeyword()
{
DEFINE_STATIC_LOCAL(const String, end, ("end"));
return end;
}
static const String& horizontalKeyword()
{
return emptyString();
}
static const String& verticalGrowingLeftKeyword()
{
DEFINE_STATIC_LOCAL(const String, verticalrl, ("rl"));
return verticalrl;
}
static const String& verticalGrowingRightKeyword()
{
DEFINE_STATIC_LOCAL(const String, verticallr, ("lr"));
return verticallr;
}
TextTrackCue::TextTrackCue(ScriptExecutionContext* context, const String& id, double start, double end, const String& content, const String& settings, bool pauseOnExit)
: m_id(id)
, m_startTime(start)
, m_endTime(end)
, m_content(content)
, m_linePosition(undefinedPosition)
, m_computedLinePosition(undefinedPosition)
, m_textPosition(50)
, m_cueSize(100)
, m_cueIndex(invalidCueIndex)
, m_writingDirection(Horizontal)
, m_cueAlignment(Middle)
, m_scriptExecutionContext(context)
, m_isActive(false)
, m_pauseOnExit(pauseOnExit)
, m_snapToLines(true)
, m_displayTreeShouldChange(true)
, m_displayTree(HTMLDivElement::create(static_cast<Document*>(context)))
, m_displayXPosition(undefinedPosition)
, m_displayYPosition(undefinedPosition)
{
ASSERT(m_scriptExecutionContext->isDocument());
m_displayWritingModeMap[Horizontal] = CSSValueHorizontalTb;
m_displayWritingModeMap[VerticalGrowingLeft] = CSSValueVerticalLr;
m_displayWritingModeMap[VerticalGrowingRight] = CSSValueVerticalRl;
parseSettings(settings);
m_computedLinePosition = calculateComputedLinePosition();
}
TextTrackCue::~TextTrackCue()
{
}
void TextTrackCue::cueWillChange()
{
if (m_track)
m_track->cueWillChange(this);
}
void TextTrackCue::cueDidChange()
{
if (m_track)
m_track->cueDidChange(this);
m_displayTreeShouldChange = true;
}
TextTrack* TextTrackCue::track() const
{
return m_track.get();
}
void TextTrackCue::setTrack(PassRefPtr<TextTrack>track)
{
m_track = track;
}
void TextTrackCue::setId(const String& id)
{
if (m_id == id)
return;
cueWillChange();
m_id = id;
cueDidChange();
}
void TextTrackCue::setStartTime(double value)
{
if (m_startTime == value)
return;
cueWillChange();
m_startTime = value;
cueDidChange();
}
void TextTrackCue::setEndTime(double value)
{
if (m_endTime == value)
return;
cueWillChange();
m_endTime = value;
cueDidChange();
}
void TextTrackCue::setPauseOnExit(bool value)
{
if (m_pauseOnExit == value)
return;
cueWillChange();
m_pauseOnExit = value;
cueDidChange();
}
const String& TextTrackCue::vertical() const
{
switch (m_writingDirection) {
case Horizontal:
return horizontalKeyword();
case VerticalGrowingLeft:
return verticalGrowingLeftKeyword();
case VerticalGrowingRight:
return verticalGrowingRightKeyword();
default:
ASSERT_NOT_REACHED();
return emptyString();
}
}
void TextTrackCue::setVertical(const String& value, ExceptionCode& ec)
{
WritingDirection direction = m_writingDirection;
if (value == horizontalKeyword())
direction = Horizontal;
else if (value == verticalGrowingLeftKeyword())
direction = VerticalGrowingLeft;
else if (value == verticalGrowingRightKeyword())
direction = VerticalGrowingRight;
else
ec = SYNTAX_ERR;
if (direction == m_writingDirection)
return;
cueWillChange();
m_writingDirection = direction;
cueDidChange();
}
void TextTrackCue::setSnapToLines(bool value)
{
if (m_snapToLines == value)
return;
cueWillChange();
m_snapToLines = value;
cueDidChange();
}
void TextTrackCue::setLine(int position, ExceptionCode& ec)
{
if (!m_snapToLines && (position < 0 || position > 100)) {
ec = INDEX_SIZE_ERR;
return;
}
if (m_linePosition == position)
return;
cueWillChange();
m_linePosition = position;
m_computedLinePosition = calculateComputedLinePosition();
cueDidChange();
}
void TextTrackCue::setPosition(int position, ExceptionCode& ec)
{
if (position < 0 || position > 100) {
ec = INDEX_SIZE_ERR;
return;
}
if (m_textPosition == position)
return;
cueWillChange();
m_textPosition = position;
cueDidChange();
}
void TextTrackCue::setSize(int size, ExceptionCode& ec)
{
if (size < 0 || size > 100) {
ec = INDEX_SIZE_ERR;
return;
}
if (m_cueSize == size)
return;
cueWillChange();
m_cueSize = size;
cueDidChange();
}
const String& TextTrackCue::align() const
{
switch (m_cueAlignment) {
case Start:
return startKeyword();
case Middle:
return middleKeyword();
case End:
return endKeyword();
default:
ASSERT_NOT_REACHED();
return emptyString();
}
}
void TextTrackCue::setAlign(const String& value, ExceptionCode& ec)
{
Alignment alignment = m_cueAlignment;
if (value == startKeyword())
alignment = Start;
else if (value == middleKeyword())
alignment = Middle;
else if (value == endKeyword())
alignment = End;
else
ec = SYNTAX_ERR;
if (alignment == m_cueAlignment)
return;
cueWillChange();
m_cueAlignment = alignment;
cueDidChange();
}
void TextTrackCue::setText(const String& text)
{
if (m_content == text)
return;
cueWillChange();
m_documentFragment = 0;
m_content = text;
cueDidChange();
}
int TextTrackCue::cueIndex()
{
if (m_cueIndex == invalidCueIndex)
m_cueIndex = track()->cues()->getCueIndex(this);
return m_cueIndex;
}
void TextTrackCue::invalidateCueIndex()
{
m_cueIndex = invalidCueIndex;
}
PassRefPtr<DocumentFragment> TextTrackCue::getCueAsHTML()
{
RefPtr<DocumentFragment> clonedFragment;
Document* document;
if (!m_documentFragment)
m_documentFragment = WebVTTParser::create(0, m_scriptExecutionContext)->createDocumentFragmentFromCueText(m_content);
document = static_cast<Document*>(m_scriptExecutionContext);
clonedFragment = DocumentFragment::create(document);
m_documentFragment->cloneChildNodes(clonedFragment.get());
return clonedFragment.release();
}
bool TextTrackCue::dispatchEvent(PassRefPtr<Event> event)
{
if (!track() || track()->mode() == TextTrack::DISABLED)
return false;
return EventTarget::dispatchEvent(event);
}
bool TextTrackCue::dispatchEvent(PassRefPtr<Event> event, ExceptionCode &ec)
{
return EventTarget::dispatchEvent(event, ec);
}
bool TextTrackCue::isActive()
{
return m_isActive && track() && track()->mode() != TextTrack::DISABLED;
}
void TextTrackCue::setIsActive(bool active)
{
m_isActive = active;
if (!active) {
ExceptionCode ec;
m_displayTree->remove(ec);
}
}
int TextTrackCue::calculateComputedLinePosition()
{
if (m_linePosition != undefinedPosition)
return m_linePosition;
if (!m_snapToLines)
return 100;
return 100;
}
void TextTrackCue::calculateDisplayParameters()
{
m_displayDirection = CSSValueLtr;
m_displayWritingMode = m_displayWritingModeMap[m_writingDirection];
int maximumSize = m_textPosition;
if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
|| (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)
|| (m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Start)
|| (m_writingDirection == VerticalGrowingRight && m_cueAlignment == End)) {
maximumSize = 100 - m_textPosition;
} else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
|| (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)
|| (m_writingDirection == VerticalGrowingLeft && m_cueAlignment == End)
|| (m_writingDirection == VerticalGrowingRight && m_cueAlignment == End)) {
maximumSize = m_textPosition;
} else if (m_cueAlignment == Middle) {
maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition);
maximumSize = maximumSize * 2;
}
m_displaySize = std::min(m_cueSize, maximumSize);
m_displayWidth = m_writingDirection == Horizontal ? m_displaySize : autoSize;
m_displayHeight = m_writingDirection == Horizontal ? autoSize : m_displaySize;
if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
|| (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)) {
m_displayXPosition = m_textPosition;
} else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
|| (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)) {
m_displayXPosition = 100 - m_textPosition;
}
if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Start)
|| (m_writingDirection == VerticalGrowingRight && m_cueAlignment == Start)) {
m_displayYPosition = m_textPosition;
} else if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == End)
|| (m_writingDirection == VerticalGrowingRight && m_cueAlignment == End)) {
m_displayYPosition = 100 - m_textPosition;
}
if (m_writingDirection == Horizontal && m_cueAlignment == Middle) {
m_displayXPosition = m_textPosition - m_displaySize / 2;
if (m_displayDirection == CSSValueRtl)
m_displayXPosition = 100 - m_displayXPosition;
}
if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Middle)
|| (m_writingDirection == VerticalGrowingRight && m_cueAlignment == Middle))
m_displayYPosition = m_textPosition - m_displaySize / 2;
if (m_snapToLines && m_displayYPosition == undefinedPosition && m_writingDirection == Horizontal)
m_displayYPosition = 0;
if (!m_snapToLines && m_writingDirection == Horizontal)
m_displayYPosition = m_computedLinePosition;
if (m_snapToLines && m_displayXPosition == undefinedPosition
&& (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
m_displayXPosition = 0;
if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
m_displayXPosition = m_computedLinePosition;
}
PassRefPtr<HTMLDivElement> TextTrackCue::getDisplayTree()
{
DEFINE_STATIC_LOCAL(const AtomicString, trackBackgroundShadowPseudoId, ("-webkit-media-text-track-background"));
DEFINE_STATIC_LOCAL(const AtomicString, trackDisplayBoxShadowPseudoId, ("-webkit-media-text-track-display"));
if (!m_displayTreeShouldChange)
return m_displayTree;
calculateDisplayParameters();
m_displayTree->removeChildren();
RefPtr<HTMLDivElement> cueBackgroundBox = HTMLDivElement::create(static_cast<Document*>(m_scriptExecutionContext));
cueBackgroundBox->setShadowPseudoId(trackBackgroundShadowPseudoId);
cueBackgroundBox->appendChild(getCueAsHTML(), ASSERT_NO_EXCEPTION, true);
m_displayTree->setShadowPseudoId(trackDisplayBoxShadowPseudoId, ASSERT_NO_EXCEPTION);
m_displayTree->appendChild(cueBackgroundBox, ASSERT_NO_EXCEPTION, true);
if (m_snapToLines)
m_displayTree->setInlineStyleProperty(CSSPropertyWidth, (double) m_cueSize, CSSPrimitiveValue::CSS_PERCENTAGE);
if (!m_snapToLines) {
std::pair<double, double> position = getPositionCoordinates();
m_displayTree->setInlineStyleProperty(CSSPropertyLeft, position.first, CSSPrimitiveValue::CSS_PERCENTAGE);
m_displayTree->setInlineStyleProperty(CSSPropertyTop, position.second, CSSPrimitiveValue::CSS_PERCENTAGE);
String translateX = "-" + String::number(position.first) + "%";
String translateY = "-" + String::number(position.second) + "%";
String webkitTransformTranslateValue = "translate(" + translateX + "," + translateY + ")";
m_displayTree->setInlineStyleProperty(CSSPropertyWebkitTransform,
webkitTransformTranslateValue);
m_displayTree->setInlineStyleProperty(CSSPropertyWhiteSpace,
CSSValuePre);
}
m_displayTreeShouldChange = false;
return m_displayTree;
}
std::pair<double, double> TextTrackCue::getPositionCoordinates()
{
std::pair<double, double> coordinates;
if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
coordinates.first = m_textPosition;
coordinates.second = m_computedLinePosition;
return coordinates;
}
if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
coordinates.first = 100 - m_textPosition;
coordinates.second = m_computedLinePosition;
return coordinates;
}
if (m_writingDirection == VerticalGrowingLeft) {
coordinates.first = 100 - m_computedLinePosition;
coordinates.second = m_textPosition;
return coordinates;
}
if (m_writingDirection == VerticalGrowingRight) {
coordinates.first = m_computedLinePosition;
coordinates.second = m_textPosition;
return coordinates;
}
ASSERT_NOT_REACHED();
return coordinates;
}
TextTrackCue::CueSetting TextTrackCue::settingName(const String& name)
{
DEFINE_STATIC_LOCAL(const String, verticalKeyword, ("vertical"));
DEFINE_STATIC_LOCAL(const String, lineKeyword, ("line"));
DEFINE_STATIC_LOCAL(const String, positionKeyword, ("position"));
DEFINE_STATIC_LOCAL(const String, sizeKeyword, ("size"));
DEFINE_STATIC_LOCAL(const String, alignKeyword, ("align"));
if (name == verticalKeyword)
return Vertical;
else if (name == lineKeyword)
return Line;
else if (name == positionKeyword)
return Position;
else if (name == sizeKeyword)
return Size;
else if (name == alignKeyword)
return Align;
return None;
}
void TextTrackCue::parseSettings(const String& input)
{
unsigned position = 0;
while (position < input.length()) {
while (position < input.length() && WebVTTParser::isValidSettingDelimiter(input[position]))
position++;
if (position >= input.length())
break;
unsigned endOfSetting = position;
String setting = WebVTTParser::collectWord(input, &endOfSetting);
CueSetting name;
size_t colonOffset = setting.find(':', 1);
if (colonOffset == notFound || colonOffset == 0 || colonOffset == setting.length() - 1)
goto NextSetting;
name = settingName(setting.substring(0, colonOffset));
position += colonOffset + 1;
if (position >= input.length())
break;
switch (name) {
case Vertical:
{
String writingDirection = WebVTTParser::collectWord(input, &position);
if (writingDirection == verticalGrowingLeftKeyword())
m_writingDirection = VerticalGrowingLeft;
else if (writingDirection == verticalGrowingRightKeyword())
m_writingDirection = VerticalGrowingRight;
}
break;
case Line:
{
StringBuilder linePositionBuilder;
while (position < input.length() && (input[position] == '-' || input[position] == '%' || isASCIIDigit(input[position])))
linePositionBuilder.append(input[position++]);
if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position]))
break;
String linePosition = linePositionBuilder.toString();
if (linePosition.find('-', 1) != notFound || linePosition.reverseFind("%", linePosition.length() - 2) != notFound)
break;
if (linePosition[0] == '-' && linePosition[linePosition.length() - 1] == '%')
break;
bool validNumber;
int number = linePosition.toInt(&validNumber);
if (!validNumber)
break;
if (linePosition[linePosition.length() - 1] == '%') {
if (number < 0 || number > 100)
break;
m_snapToLines = false;
}
m_linePosition = number;
}
break;
case Position:
{
String textPosition = WebVTTParser::collectDigits(input, &position);
if (textPosition.isEmpty())
break;
if (position >= input.length())
break;
if (input[position++] != '%')
break;
if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position]))
break;
bool validNumber;
int number = textPosition.toInt(&validNumber);
if (!validNumber)
break;
if (number < 0 || number > 100)
break;
m_textPosition = number;
}
break;
case Size:
{
String cueSize = WebVTTParser::collectDigits(input, &position);
if (cueSize.isEmpty())
break;
if (position >= input.length())
break;
if (input[position++] != '%')
break;
if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position]))
break;
bool validNumber;
int number = cueSize.toInt(&validNumber);
if (!validNumber)
break;
if (number < 0 || number > 100)
break;
m_cueSize = number;
}
break;
case Align:
{
String cueAlignment = WebVTTParser::collectWord(input, &position);
if (cueAlignment == startKeyword())
m_cueAlignment = Start;
else if (cueAlignment == middleKeyword())
m_cueAlignment = Middle;
else if (cueAlignment == endKeyword())
m_cueAlignment = End;
}
break;
case None:
break;
}
NextSetting:
position = endOfSetting;
}
}
const AtomicString& TextTrackCue::interfaceName() const
{
return eventNames().interfaceForTextTrackCue;
}
ScriptExecutionContext* TextTrackCue::scriptExecutionContext() const
{
return m_scriptExecutionContext;
}
EventTargetData* TextTrackCue::eventTargetData()
{
return &m_eventTargetData;
}
EventTargetData* TextTrackCue::ensureEventTargetData()
{
return &m_eventTargetData;
}
}
#endif