Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / html / track / VTTCue.cpp
1 /*
2  * Copyright (C) 2011, 2013 Google Inc.  All rights reserved.
3  * Copyright (C) 2011-2014 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #include "config.h"
33
34 #if ENABLE(VIDEO_TRACK)
35 #include "VTTCue.h"
36
37 #include "CSSPropertyNames.h"
38 #include "CSSValueKeywords.h"
39 #include "DocumentFragment.h"
40 #include "Event.h"
41 #include "HTMLDivElement.h"
42 #include "HTMLSpanElement.h"
43 #include "Logging.h"
44 #include "NodeTraversal.h"
45 #include "RenderVTTCue.h"
46 #include "Text.h"
47 #include "TextTrack.h"
48 #include "TextTrackCueList.h"
49 #include "VTTRegionList.h"
50 #include "VTTScanner.h"
51 #include "WebVTTElement.h"
52 #include "WebVTTParser.h"
53 #include <wtf/MathExtras.h>
54 #include <wtf/text/StringBuilder.h>
55
56 namespace WebCore {
57
58 // This constant should correspond with the percentage returned by CaptionUserPreferences::captionFontSizeScaleAndImportance.
59 const static double DEFAULTCAPTIONFONTSIZEPERCENTAGE = 5;
60
61 static const int undefinedPosition = -1;
62
63 static const CSSValueID displayWritingModeMap[] = {
64     CSSValueHorizontalTb, CSSValueVerticalRl, CSSValueVerticalLr
65 };
66 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayWritingModeMap) == VTTCue::NumberOfWritingDirections, displayWritingModeMap_has_wrong_size);
67
68 static const CSSValueID displayAlignmentMap[] = {
69     CSSValueStart, CSSValueCenter, CSSValueEnd, CSSValueLeft, CSSValueRight
70 };
71 COMPILE_ASSERT(WTF_ARRAY_LENGTH(displayAlignmentMap) == VTTCue::NumberOfAlignments, displayAlignmentMap_has_wrong_size);
72
73 static const String& startKeyword()
74 {
75     DEPRECATED_DEFINE_STATIC_LOCAL(const String, start, (ASCIILiteral("start")));
76     return start;
77 }
78
79 static const String& middleKeyword()
80 {
81     DEPRECATED_DEFINE_STATIC_LOCAL(const String, middle, (ASCIILiteral("middle")));
82     return middle;
83 }
84
85 static const String& endKeyword()
86 {
87     DEPRECATED_DEFINE_STATIC_LOCAL(const String, end, (ASCIILiteral("end")));
88     return end;
89 }
90
91 static const String& leftKeyword()
92 {
93     DEPRECATED_DEFINE_STATIC_LOCAL(const String, left, ("left"));
94     return left;
95 }
96
97 static const String& rightKeyword()
98 {
99     DEPRECATED_DEFINE_STATIC_LOCAL(const String, right, ("right"));
100     return right;
101 }
102
103 static const String& horizontalKeyword()
104 {
105     return emptyString();
106 }
107
108 static const String& verticalGrowingLeftKeyword()
109 {
110     DEPRECATED_DEFINE_STATIC_LOCAL(const String, verticalrl, (ASCIILiteral("rl")));
111     return verticalrl;
112 }
113
114 static const String& verticalGrowingRightKeyword()
115 {
116     DEPRECATED_DEFINE_STATIC_LOCAL(const String, verticallr, (ASCIILiteral("lr")));
117     return verticallr;
118 }
119
120 // ----------------------------
121
122 PassRefPtr<VTTCueBox> VTTCueBox::create(Document& document, VTTCue& cue)
123 {
124     VTTCueBox* cueBox = new VTTCueBox(document, cue);
125     cueBox->setPseudo(VTTCueBox::vttCueBoxShadowPseudoId());
126     return adoptRef(cueBox);
127 }
128
129 VTTCueBox::VTTCueBox(Document& document, VTTCue& cue)
130     : HTMLElement(divTag, document)
131     , m_cue(cue)
132 {
133     setPseudo(vttCueBoxShadowPseudoId());
134 }
135
136 VTTCue* VTTCueBox::getCue() const
137 {
138     return &m_cue;
139 }
140
141 void VTTCueBox::applyCSSProperties(const IntSize& videoSize)
142 {
143     // FIXME: Apply all the initial CSS positioning properties. http://wkb.ug/79916
144     if (!m_cue.regionId().isEmpty()) {
145         setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative);
146         return;
147     }
148
149     // 3.5.1 On the (root) List of WebVTT Node Objects:
150
151     // the 'position' property must be set to 'absolute'
152     setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
153
154     //  the 'unicode-bidi' property must be set to 'plaintext'
155     setInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueWebkitPlaintext);
156
157     // the 'direction' property must be set to direction
158     setInlineStyleProperty(CSSPropertyDirection, m_cue.getCSSWritingDirection());
159
160     // the 'writing-mode' property must be set to writing-mode
161     setInlineStyleProperty(CSSPropertyWebkitWritingMode, m_cue.getCSSWritingMode(), false);
162
163     std::pair<float, float> position = m_cue.getCSSPosition();
164
165     // the 'top' property must be set to top,
166     setInlineStyleProperty(CSSPropertyTop, static_cast<double>(position.second), CSSPrimitiveValue::CSS_PERCENTAGE);
167
168     // the 'left' property must be set to left
169     setInlineStyleProperty(CSSPropertyLeft, static_cast<double>(position.first), CSSPrimitiveValue::CSS_PERCENTAGE);
170
171     double authorFontSize = std::min(videoSize.width(), videoSize.height()) * DEFAULTCAPTIONFONTSIZEPERCENTAGE / 100.0;
172     double multiplier = 1.0;
173     if (authorFontSize)
174         multiplier = m_fontSizeFromCaptionUserPrefs / authorFontSize;
175
176     double textPosition = m_cue.position();
177     double maxSize = 100.0;
178     CSSValueID alignment = m_cue.getCSSAlignment();
179     if (alignment == CSSValueEnd || alignment == CSSValueRight)
180         maxSize = textPosition;
181     else if (alignment == CSSValueStart || alignment == CSSValueLeft)
182         maxSize = 100.0 - textPosition;
183
184     double newCueSize = std::min(m_cue.getCSSSize() * multiplier, 100.0);
185     // the 'width' property must be set to width, and the 'height' property  must be set to height
186     if (m_cue.vertical() == horizontalKeyword()) {
187         setInlineStyleProperty(CSSPropertyWidth, newCueSize, CSSPrimitiveValue::CSS_PERCENTAGE);
188         setInlineStyleProperty(CSSPropertyHeight, CSSValueAuto);
189         setInlineStyleProperty(CSSPropertyMinWidth, "-webkit-min-content");
190         setInlineStyleProperty(CSSPropertyMaxWidth, maxSize, CSSPrimitiveValue::CSS_PERCENTAGE);
191         if ((alignment == CSSValueMiddle || alignment == CSSValueCenter) && multiplier != 1.0)
192             setInlineStyleProperty(CSSPropertyLeft, static_cast<double>(position.first - (newCueSize - m_cue.getCSSSize()) / 2), CSSPrimitiveValue::CSS_PERCENTAGE);
193     } else {
194         setInlineStyleProperty(CSSPropertyWidth, CSSValueAuto);
195         setInlineStyleProperty(CSSPropertyHeight, newCueSize, CSSPrimitiveValue::CSS_PERCENTAGE);
196         setInlineStyleProperty(CSSPropertyMinHeight, "-webkit-min-content");
197         setInlineStyleProperty(CSSPropertyMaxHeight, maxSize, CSSPrimitiveValue::CSS_PERCENTAGE);
198         if ((alignment == CSSValueMiddle || alignment == CSSValueCenter) && multiplier != 1.0)
199             setInlineStyleProperty(CSSPropertyTop, static_cast<double>(position.second - (newCueSize - m_cue.getCSSSize()) / 2), CSSPrimitiveValue::CSS_PERCENTAGE);
200     }
201
202     // The 'text-align' property on the (root) List of WebVTT Node Objects must
203     // be set to the value in the second cell of the row of the table below
204     // whose first cell is the value of the corresponding cue's text track cue
205     // alignment:
206     setInlineStyleProperty(CSSPropertyTextAlign, m_cue.getCSSAlignment());
207     
208     if (!m_cue.snapToLines()) {
209         // 10.13.1 Set up x and y:
210         // Note: x and y are set through the CSS left and top above.
211
212         // 10.13.2 Position the boxes in boxes such that the point x% along the
213         // width of the bounding box of the boxes in boxes is x% of the way
214         // across the width of the video's rendering area, and the point y%
215         // along the height of the bounding box of the boxes in boxes is y%
216         // of the way across the height of the video's rendering area, while
217         // maintaining the relative positions of the boxes in boxes to each
218         // other.
219         setInlineStyleProperty(CSSPropertyTransform,
220             String::format("translate(-%.2f%%, -%.2f%%)", position.first, position.second));
221
222         setInlineStyleProperty(CSSPropertyWhiteSpace, CSSValuePre);
223     }
224 }
225
226 const AtomicString& VTTCueBox::vttCueBoxShadowPseudoId()
227 {
228     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, trackDisplayBoxShadowPseudoId, ("-webkit-media-text-track-display", AtomicString::ConstructFromLiteral));
229     return trackDisplayBoxShadowPseudoId;
230 }
231
232 RenderPtr<RenderElement> VTTCueBox::createElementRenderer(Ref<RenderStyle>&& style, const RenderTreePosition&)
233 {
234     return createRenderer<RenderVTTCue>(*this, WTFMove(style));
235 }
236
237 // ----------------------------
238
239 const AtomicString& VTTCue::cueBackdropShadowPseudoId()
240 {
241     DEPRECATED_DEFINE_STATIC_LOCAL(const AtomicString, cueBackdropShadowPseudoId, ("-webkit-media-text-track-display-backdrop", AtomicString::ConstructFromLiteral));
242     return cueBackdropShadowPseudoId;
243 }
244
245 Ref<VTTCue> VTTCue::create(ScriptExecutionContext& context, const WebVTTCueData& data)
246 {
247     return adoptRef(*new VTTCue(context, data));
248 }
249
250 VTTCue::VTTCue(ScriptExecutionContext& context, const MediaTime& start, const MediaTime& end, const String& content)
251     : TextTrackCue(context, start, end)
252     , m_content(content)
253 {
254     initialize(context);
255 }
256
257 VTTCue::VTTCue(ScriptExecutionContext& context, const WebVTTCueData& cueData)
258     : TextTrackCue(context, MediaTime::zeroTime(), MediaTime::zeroTime())
259 {
260     initialize(context);
261     setText(cueData.content());
262     setStartTime(cueData.startTime());
263     setEndTime(cueData.endTime());
264     setId(cueData.id());
265     setCueSettings(cueData.settings());
266     m_originalStartTime = cueData.originalStartTime();
267 }
268
269 VTTCue::~VTTCue()
270 {
271     if (!hasDisplayTree())
272         return;
273
274     displayTreeInternal()->remove(ASSERT_NO_EXCEPTION);
275 }
276
277 void VTTCue::initialize(ScriptExecutionContext& context)
278 {
279     m_linePosition = undefinedPosition;
280     m_computedLinePosition = undefinedPosition;
281     m_textPosition = 50;
282     m_cueSize = 100;
283     m_writingDirection = Horizontal;
284     m_cueAlignment = Middle;
285     m_webVTTNodeTree = nullptr;
286     m_cueBackdropBox = HTMLDivElement::create(downcast<Document>(context));
287     m_cueHighlightBox = HTMLSpanElement::create(spanTag, downcast<Document>(context));
288     m_displayDirection = CSSValueLtr;
289     m_displaySize = 0;
290     m_snapToLines = true;
291     m_displayTreeShouldChange = true;
292     m_notifyRegion = true;
293     m_originalStartTime = MediaTime::zeroTime();
294 }
295
296 PassRefPtr<VTTCueBox> VTTCue::createDisplayTree()
297 {
298     return VTTCueBox::create(ownerDocument(), *this);
299 }
300
301 VTTCueBox* VTTCue::displayTreeInternal()
302 {
303     if (!m_displayTree)
304         m_displayTree = createDisplayTree();
305     return m_displayTree.get();
306 }
307
308 void VTTCue::didChange()
309 {
310     TextTrackCue::didChange();
311     m_displayTreeShouldChange = true;
312 }
313
314 const String& VTTCue::vertical() const
315 {
316     switch (m_writingDirection) {
317     case Horizontal: 
318         return horizontalKeyword();
319     case VerticalGrowingLeft:
320         return verticalGrowingLeftKeyword();
321     case VerticalGrowingRight:
322         return verticalGrowingRightKeyword();
323     default:
324         ASSERT_NOT_REACHED();
325         return emptyString();
326     }
327 }
328
329 void VTTCue::setVertical(const String& value, ExceptionCode& ec)
330 {
331     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-vertical
332     // On setting, the text track cue writing direction must be set to the value given 
333     // in the first cell of the row in the table above whose second cell is a 
334     // case-sensitive match for the new value, if any. If none of the values match, then
335     // the user agent must instead throw a SyntaxError exception.
336     
337     WritingDirection direction = m_writingDirection;
338     if (value == horizontalKeyword())
339         direction = Horizontal;
340     else if (value == verticalGrowingLeftKeyword())
341         direction = VerticalGrowingLeft;
342     else if (value == verticalGrowingRightKeyword())
343         direction = VerticalGrowingRight;
344     else
345         ec = SYNTAX_ERR;
346     
347     if (direction == m_writingDirection)
348         return;
349
350     willChange();
351     m_writingDirection = direction;
352     didChange();
353 }
354
355 void VTTCue::setSnapToLines(bool value)
356 {
357     if (m_snapToLines == value)
358         return;
359     
360     willChange();
361     m_snapToLines = value;
362     didChange();
363 }
364
365 void VTTCue::setLine(double position, ExceptionCode& ec)
366 {
367     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
368     // On setting, if the text track cue snap-to-lines flag is not set, and the new
369     // value is negative or greater than 100, then throw an IndexSizeError exception.
370     if (!m_snapToLines && (position < 0 || position > 100)) {
371         ec = INDEX_SIZE_ERR;
372         return;
373     }
374
375     // Otherwise, set the text track cue line position to the new value.
376     if (m_linePosition == position)
377         return;
378
379     willChange();
380     m_linePosition = position;
381     m_computedLinePosition = calculateComputedLinePosition();
382     didChange();
383 }
384
385 void VTTCue::setPosition(double position, ExceptionCode& ec)
386 {
387     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-position
388     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
389     // Otherwise, set the text track cue text position to the new value.
390     if (position < 0 || position > 100) {
391         ec = INDEX_SIZE_ERR;
392         return;
393     }
394     
395     // Otherwise, set the text track cue line position to the new value.
396     if (m_textPosition == position)
397         return;
398     
399     willChange();
400     m_textPosition = position;
401     didChange();
402 }
403
404 void VTTCue::setSize(int size, ExceptionCode& ec)
405 {
406     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
407     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
408     // exception. Otherwise, set the text track cue size to the new value.
409     if (size < 0 || size > 100) {
410         ec = INDEX_SIZE_ERR;
411         return;
412     }
413     
414     // Otherwise, set the text track cue line position to the new value.
415     if (m_cueSize == size)
416         return;
417     
418     willChange();
419     m_cueSize = size;
420     didChange();
421 }
422
423 const String& VTTCue::align() const
424 {
425     switch (m_cueAlignment) {
426     case Start:
427         return startKeyword();
428     case Middle:
429         return middleKeyword();
430     case End:
431         return endKeyword();
432     case Left:
433         return leftKeyword();
434     case Right:
435         return rightKeyword();
436     default:
437         ASSERT_NOT_REACHED();
438         return emptyString();
439     }
440 }
441
442 void VTTCue::setAlign(const String& value, ExceptionCode& ec)
443 {
444     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-align
445     // On setting, the text track cue alignment must be set to the value given in the 
446     // first cell of the row in the table above whose second cell is a case-sensitive
447     // match for the new value, if any. If none of the values match, then the user
448     // agent must instead throw a SyntaxError exception.
449     
450     CueAlignment alignment = m_cueAlignment;
451     if (value == startKeyword())
452         alignment = Start;
453     else if (value == middleKeyword())
454         alignment = Middle;
455     else if (value == endKeyword())
456         alignment = End;
457     else if (value == leftKeyword())
458         alignment = Left;
459     else if (value == rightKeyword())
460         alignment = Right;
461     else
462         ec = SYNTAX_ERR;
463     
464     if (alignment == m_cueAlignment)
465         return;
466
467     willChange();
468     m_cueAlignment = alignment;
469     didChange();
470 }
471     
472 void VTTCue::setText(const String& text)
473 {
474     if (m_content == text)
475         return;
476     
477     willChange();
478     // Clear the document fragment but don't bother to create it again just yet as we can do that
479     // when it is requested.
480     m_webVTTNodeTree = nullptr;
481     m_content = text;
482     didChange();
483 }
484
485 void VTTCue::createWebVTTNodeTree()
486 {
487     if (!m_webVTTNodeTree)
488         m_webVTTNodeTree = WebVTTParser::createDocumentFragmentFromCueText(ownerDocument(), m_content);
489 }
490
491 void VTTCue::copyWebVTTNodeToDOMTree(ContainerNode* webVTTNode, ContainerNode* parent)
492 {
493     for (Node* node = webVTTNode->firstChild(); node; node = node->nextSibling()) {
494         RefPtr<Node> clonedNode;
495         if (is<WebVTTElement>(*node))
496             clonedNode = downcast<WebVTTElement>(*node).createEquivalentHTMLElement(ownerDocument());
497         else
498             clonedNode = node->cloneNode(false);
499         parent->appendChild(*clonedNode, ASSERT_NO_EXCEPTION);
500         if (is<ContainerNode>(*node))
501             copyWebVTTNodeToDOMTree(downcast<ContainerNode>(node), downcast<ContainerNode>(clonedNode.get()));
502     }
503 }
504
505 PassRefPtr<DocumentFragment> VTTCue::getCueAsHTML()
506 {
507     createWebVTTNodeTree();
508     if (!m_webVTTNodeTree)
509         return 0;
510
511     RefPtr<DocumentFragment> clonedFragment = DocumentFragment::create(ownerDocument());
512     copyWebVTTNodeToDOMTree(m_webVTTNodeTree.get(), clonedFragment.get());
513     return clonedFragment.release();
514 }
515
516 PassRefPtr<DocumentFragment> VTTCue::createCueRenderingTree()
517 {
518     RefPtr<DocumentFragment> clonedFragment;
519     createWebVTTNodeTree();
520     if (!m_webVTTNodeTree)
521         return nullptr;
522
523     clonedFragment = DocumentFragment::create(ownerDocument());
524     m_webVTTNodeTree->cloneChildNodes(*clonedFragment);
525     return clonedFragment.release();
526 }
527
528 void VTTCue::setRegionId(const String& regionId)
529 {
530     if (m_regionId == regionId)
531         return;
532
533     willChange();
534     m_regionId = regionId;
535     didChange();
536 }
537
538 void VTTCue::notifyRegionWhenRemovingDisplayTree(bool notifyRegion)
539 {
540     m_notifyRegion = notifyRegion;
541 }
542
543 void VTTCue::setIsActive(bool active)
544 {
545     TextTrackCue::setIsActive(active);
546
547     if (!active) {
548         if (!hasDisplayTree())
549             return;
550
551         // Remove the display tree as soon as the cue becomes inactive.
552         removeDisplayTree();
553     }
554 }
555
556 int VTTCue::calculateComputedLinePosition()
557 {
558     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-computed-line-position
559
560     // If the text track cue line position is numeric, then that is the text
561     // track cue computed line position.
562     if (m_linePosition != undefinedPosition)
563         return m_linePosition;
564
565     // If the text track cue snap-to-lines flag of the text track cue is not
566     // set, the text track cue computed line position is the value 100;
567     if (!m_snapToLines)
568         return 100;
569
570     // Otherwise, it is the value returned by the following algorithm:
571
572     // If cue is not associated with a text track, return -1 and abort these
573     // steps.
574     if (!track())
575         return -1;
576
577     // Let n be the number of text tracks whose text track mode is showing or
578     // showing by default and that are in the media element's list of text
579     // tracks before track.
580     int n = track()->trackIndexRelativeToRenderedTracks();
581
582     // Increment n by one.
583     n++;
584
585     // Negate n.
586     n = -n;
587
588     return n;
589 }
590
591 static bool isCueParagraphSeparator(UChar character)
592 {
593     // Within a cue, paragraph boundaries are only denoted by Type B characters,
594     // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRAPH SEPARATOR.
595     return u_charType(character) == U_PARAGRAPH_SEPARATOR;
596 }
597
598 void VTTCue::determineTextDirection()
599 {
600     DEPRECATED_DEFINE_STATIC_LOCAL(const String, rtTag, (ASCIILiteral("rt")));
601     createWebVTTNodeTree();
602     if (!m_webVTTNodeTree)
603         return;
604
605     // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the
606     // concatenation of the values of each WebVTT Text Object in nodes, in a
607     // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and
608     // their descendants.
609     StringBuilder paragraphBuilder;
610     for (Node* node = m_webVTTNodeTree->firstChild(); node; node = NodeTraversal::next(*node, m_webVTTNodeTree.get())) {
611         // FIXME: The code does not match the comment above. This does not actually exclude Ruby Text Object descendant.
612         if (!node->isTextNode() || node->localName() == rtTag)
613             continue;
614
615         paragraphBuilder.append(node->nodeValue());
616     }
617
618     String paragraph = paragraphBuilder.toString();
619     if (!paragraph.length())
620         return;
621
622     for (size_t i = 0; i < paragraph.length(); ++i) {
623         UChar current = paragraph[i];
624         if (!current || isCueParagraphSeparator(current))
625             return;
626
627         if (UChar current = paragraph[i]) {
628             UCharDirection charDirection = u_charDirection(current);
629             if (charDirection == U_LEFT_TO_RIGHT) {
630                 m_displayDirection = CSSValueLtr;
631                 return;
632             }
633             if (charDirection == U_RIGHT_TO_LEFT || charDirection == U_RIGHT_TO_LEFT_ARABIC) {
634                 m_displayDirection = CSSValueRtl;
635                 return;
636             }
637         }
638     }
639 }
640
641 void VTTCue::calculateDisplayParameters()
642 {
643     // Steps 10.2, 10.3
644     determineTextDirection();
645
646     // 10.4 If the text track cue writing direction is horizontal, then let
647     // block-flow be 'tb'. Otherwise, if the text track cue writing direction is
648     // vertical growing left, then let block-flow be 'lr'. Otherwise, the text
649     // track cue writing direction is vertical growing right; let block-flow be
650     // 'rl'.
651
652     // The above step is done through the writing direction static map.
653
654     // 10.5 Determine the value of maximum size for cue as per the appropriate
655     // rules from the following list:
656     int maximumSize = m_textPosition;
657     if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
658         || (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)
659         || (m_writingDirection == Horizontal && m_cueAlignment == Left)
660         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == Start || m_cueAlignment == Left))
661         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == Start || m_cueAlignment == Left))) {
662         maximumSize = 100 - m_textPosition;
663     } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
664         || (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)
665         || (m_writingDirection == Horizontal && m_cueAlignment == Right)
666         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == End || m_cueAlignment == Right))
667         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == End || m_cueAlignment == Right))) {
668         maximumSize = m_textPosition;
669     } else if (m_cueAlignment == Middle) {
670         maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition);
671         maximumSize = maximumSize * 2;
672     } else
673         ASSERT_NOT_REACHED();
674
675     // 10.6 If the text track cue size is less than maximum size, then let size
676     // be text track cue size. Otherwise, let size be maximum size.
677     m_displaySize = std::min(m_cueSize, maximumSize);
678
679     // FIXME: Understand why step 10.7 is missing (just a copy/paste error?)
680     // Could be done within a spec implementation check - http://crbug.com/301580
681
682     // 10.8 Determine the value of x-position or y-position for cue as per the
683     // appropriate rules from the following list:
684     if (m_writingDirection == Horizontal) {
685         switch (m_cueAlignment) {
686         case Start:
687             if (m_displayDirection == CSSValueLtr)
688                 m_displayPosition.first = m_textPosition;
689             else
690                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
691             break;
692         case End:
693             if (m_displayDirection == CSSValueRtl)
694                 m_displayPosition.first = 100 - m_textPosition;
695             else
696                 m_displayPosition.first = m_textPosition - m_displaySize;
697             break;
698         case Left:
699             if (m_displayDirection == CSSValueLtr)
700                 m_displayPosition.first = m_textPosition;
701             else
702                 m_displayPosition.first = 100 - m_textPosition;
703             break;
704         case Right:
705             if (m_displayDirection == CSSValueLtr)
706                 m_displayPosition.first = m_textPosition - m_displaySize;
707             else
708                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
709             break;
710         case Middle:
711             if (m_displayDirection == CSSValueLtr)
712                 m_displayPosition.first = m_textPosition - m_displaySize / 2;
713             else
714                 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2;
715             break;
716         case NumberOfAlignments:
717             ASSERT_NOT_REACHED();
718         }
719     }
720
721     // A text track cue has a text track cue computed line position whose value
722     // is defined in terms of the other aspects of the cue.
723     m_computedLinePosition = calculateComputedLinePosition();
724
725     // 10.9 Determine the value of whichever of x-position or y-position is not
726     // yet calculated for cue as per the appropriate rules from the following
727     // list:
728     if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
729         m_displayPosition.second = 0;
730
731     if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
732         m_displayPosition.second = m_computedLinePosition;
733
734     if (m_snapToLines && m_displayPosition.first == undefinedPosition
735         && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
736         m_displayPosition.first = 0;
737
738     if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
739         m_displayPosition.first = m_computedLinePosition;
740 }
741     
742 void VTTCue::markFutureAndPastNodes(ContainerNode* root, const MediaTime& previousTimestamp, const MediaTime& movieTime)
743 {
744     DEPRECATED_DEFINE_STATIC_LOCAL(const String, timestampTag, (ASCIILiteral("timestamp")));
745     
746     bool isPastNode = true;
747     MediaTime currentTimestamp = previousTimestamp;
748     if (currentTimestamp > movieTime)
749         isPastNode = false;
750     
751     for (Node* child = root->firstChild(); child; child = NodeTraversal::next(*child, root)) {
752         if (child->nodeName() == timestampTag) {
753             MediaTime currentTimestamp;
754             bool check = WebVTTParser::collectTimeStamp(child->nodeValue(), currentTimestamp);
755             ASSERT_UNUSED(check, check);
756             
757             currentTimestamp += m_originalStartTime;
758             if (currentTimestamp > movieTime)
759                 isPastNode = false;
760         }
761         
762         if (is<WebVTTElement>(*child)) {
763             downcast<WebVTTElement>(*child).setIsPastNode(isPastNode);
764             // Make an elemenet id match a cue id for style matching purposes.
765             if (!id().isEmpty())
766                 downcast<WebVTTElement>(*child).setIdAttribute(id());
767         }
768     }
769 }
770
771 void VTTCue::updateDisplayTree(const MediaTime& movieTime)
772 {
773     // The display tree may contain WebVTT timestamp objects representing
774     // timestamps (processing instructions), along with displayable nodes.
775
776     if (!track()->isRendered())
777         return;
778
779     // Clear the contents of the set.
780     m_cueHighlightBox->removeChildren();
781
782     // Update the two sets containing past and future WebVTT objects.
783     RefPtr<DocumentFragment> referenceTree = createCueRenderingTree();
784     if (!referenceTree)
785         return;
786
787     markFutureAndPastNodes(referenceTree.get(), startMediaTime(), movieTime);
788     m_cueHighlightBox->appendChild(referenceTree.releaseNonNull());
789 }
790
791 VTTCueBox* VTTCue::getDisplayTree(const IntSize& videoSize, int fontSize)
792 {
793     RefPtr<VTTCueBox> displayTree = displayTreeInternal();
794     if (!m_displayTreeShouldChange || !track()->isRendered())
795         return displayTree.get();
796
797     // 10.1 - 10.10
798     calculateDisplayParameters();
799
800     // 10.11. Apply the terms of the CSS specifications to nodes within the
801     // following constraints, thus obtaining a set of CSS boxes positioned
802     // relative to an initial containing block:
803     displayTree->removeChildren();
804
805     // The document tree is the tree of WebVTT Node Objects rooted at nodes.
806
807     // The children of the nodes must be wrapped in an anonymous box whose
808     // 'display' property has the value 'inline'. This is the WebVTT cue
809     // background box.
810
811     // Note: This is contained by default in m_cueHighlightBox.
812     m_cueHighlightBox->setPseudo(cueShadowPseudoId());
813
814     m_cueBackdropBox->setPseudo(cueBackdropShadowPseudoId());
815     m_cueBackdropBox->appendChild(*m_cueHighlightBox, ASSERT_NO_EXCEPTION);
816     displayTree->appendChild(*m_cueBackdropBox, ASSERT_NO_EXCEPTION);
817
818     // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not
819     // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose
820     // 'display' property has the value 'ruby-base'.
821
822     displayTree->setFontSizeFromCaptionUserPrefs(fontSize);
823     displayTree->applyCSSProperties(videoSize);
824
825     m_displayTreeShouldChange = false;
826
827     // 10.15. Let cue's text track cue display state have the CSS boxes in
828     // boxes.
829     return displayTree.get();
830 }
831
832 void VTTCue::removeDisplayTree()
833 {
834     // The region needs to be informed about the cue removal.
835     if (m_notifyRegion && track()) {
836         if (VTTRegionList* regions = track()->regions()) {
837             if (VTTRegion* region = regions->getRegionById(m_regionId))
838                 region->willRemoveTextTrackCueBox(m_displayTree.get());
839         }
840     }
841
842     if (!hasDisplayTree())
843         return;
844     displayTreeInternal()->remove(ASSERT_NO_EXCEPTION);
845 }
846
847 std::pair<double, double> VTTCue::getPositionCoordinates() const
848 {
849     // This method is used for setting x and y when snap to lines is not set.
850     std::pair<double, double> coordinates;
851
852     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
853         coordinates.first = m_textPosition;
854         coordinates.second = m_computedLinePosition;
855
856         return coordinates;
857     }
858
859     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
860         coordinates.first = 100 - m_textPosition;
861         coordinates.second = m_computedLinePosition;
862
863         return coordinates;
864     }
865
866     if (m_writingDirection == VerticalGrowingLeft) {
867         coordinates.first = 100 - m_computedLinePosition;
868         coordinates.second = m_textPosition;
869
870         return coordinates;
871     }
872
873     if (m_writingDirection == VerticalGrowingRight) {
874         coordinates.first = m_computedLinePosition;
875         coordinates.second = m_textPosition;
876
877         return coordinates;
878     }
879
880     ASSERT_NOT_REACHED();
881
882     return coordinates;
883 }
884
885 VTTCue::CueSetting VTTCue::settingName(VTTScanner& input)
886 {
887     CueSetting parsedSetting = None;
888     if (input.scan("vertical"))
889         parsedSetting = Vertical;
890     else if (input.scan("line"))
891         parsedSetting = Line;
892     else if (input.scan("position"))
893         parsedSetting = Position;
894     else if (input.scan("size"))
895         parsedSetting = Size;
896     else if (input.scan("align"))
897         parsedSetting = Align;
898     else if (input.scan("region"))
899         parsedSetting = RegionId;
900
901     // Verify that a ':' follows.
902     if (parsedSetting != None && input.scan(':'))
903         return parsedSetting;
904
905     return None;
906 }
907
908 void VTTCue::setCueSettings(const String& inputString)
909 {
910     if (inputString.isEmpty())
911         return;
912
913     VTTScanner input(inputString);
914
915     while (!input.isAtEnd()) {
916
917         // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order, 
918         // separated from each other by one or more U+0020 SPACE characters or U+0009 CHARACTER TABULATION (tab) characters.
919         input.skipWhile<WebVTTParser::isValidSettingDelimiter>();
920         if (input.isAtEnd())
921             break;
922
923         // When the user agent is to parse the WebVTT settings given by a string input for a text track cue cue, 
924         // the user agent must run the following steps:
925         // 1. Let settings be the result of splitting input on spaces.
926         // 2. For each token setting in the list settings, run the following substeps:
927         //    1. If setting does not contain a U+003A COLON character (:), or if the first U+003A COLON character (:) 
928         //       in setting is either the first or last character of setting, then jump to the step labeled next setting.
929         //    2. Let name be the leading substring of setting up to and excluding the first U+003A COLON character (:) in that string.
930         CueSetting name = settingName(input);
931
932         // 3. Let value be the trailing substring of setting starting from the character immediately after the first U+003A COLON character (:) in that string.
933         VTTScanner::Run valueRun = input.collectUntil<WebVTTParser::isValidSettingDelimiter>();
934
935         // 4. Run the appropriate substeps that apply for the value of name, as follows:
936         switch (name) {
937         case Vertical: {
938             // If name is a case-sensitive match for "vertical"
939             // 1. If value is a case-sensitive match for the string "rl", then let cue's text track cue writing direction 
940             //    be vertical growing left.
941             if (input.scanRun(valueRun, verticalGrowingLeftKeyword()))
942                 m_writingDirection = VerticalGrowingLeft;
943             
944             // 2. Otherwise, if value is a case-sensitive match for the string "lr", then let cue's text track cue writing 
945             //    direction be vertical growing right.
946             else if (input.scanRun(valueRun, verticalGrowingRightKeyword()))
947                 m_writingDirection = VerticalGrowingRight;
948
949             else
950                 LOG(Media, "VTTCue::setCueSettings, invalid Vertical");
951             break;
952         }
953         case Line: {
954             bool isValid = false;
955             do {
956                 // 1-2 - Collect chars that are either '-', '%', or a digit.
957                 // 1. If value contains any characters other than U+002D HYPHEN-MINUS characters (-), U+0025 PERCENT SIGN
958                 //    characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump
959                 //    to the step labeled next setting.
960                 float linePosition;
961                 bool isNegative;
962                 if (!input.scanFloat(linePosition, &isNegative))
963                     break;
964
965                 bool isPercentage = input.scan('%');
966                 if (!input.isAt(valueRun.end()))
967                     break;
968
969                 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
970                 //    NINE (9), then jump to the step labeled next setting.
971                 // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then
972                 //    jump to the step labeled next setting.
973                 // 4. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then
974                 //    jump to the step labeled next setting.
975                 // 5. If the first character in value is a U+002D HYPHEN-MINUS character (-) and the last character in value is a
976                 //    U+0025 PERCENT SIGN character (%), then jump to the step labeled next setting.
977                 if (isPercentage && isNegative)
978                     break;
979
980                 // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and
981                 //    let number be that number.
982                 // 7. If the last character in value is a U+0025 PERCENT SIGN character (%), but number is not in the range
983                 //    0 ≤ number ≤ 100, then jump to the step labeled next setting.
984                 // 8. Let cue's text track cue line position be number.
985                 // 9. If the last character in value is a U+0025 PERCENT SIGN character (%), then let cue's text track cue
986                 //    snap-to-lines flag be false. Otherwise, let it be true.
987                 if (isPercentage) {
988                     if (linePosition < 0 || linePosition > 100)
989                         break;
990
991                     // 10 - If '%' then set snap-to-lines flag to false.
992                     m_snapToLines = false;
993                 } else {
994                     if (linePosition - static_cast<int>(linePosition))
995                         break;
996
997                     m_snapToLines = true;
998                 }
999                 
1000                 m_linePosition = linePosition;
1001                 isValid = true;
1002             } while (0);
1003
1004             if (!isValid)
1005                 LOG(Media, "VTTCue::setCueSettings, invalid Line");
1006
1007             break;
1008         }
1009         case Position: {
1010             float position;
1011             if (WebVTTParser::parseFloatPercentageValue(input, position) && input.isAt(valueRun.end()))
1012                 m_textPosition = position;
1013             else
1014                 LOG(Media, "VTTCue::setCueSettings, invalid Position");
1015             break;
1016         }
1017         case Size: {
1018             float cueSize;
1019             if (WebVTTParser::parseFloatPercentageValue(input, cueSize) && input.isAt(valueRun.end()))
1020                 m_cueSize = cueSize;
1021             else
1022                 LOG(Media, "VTTCue::setCueSettings, invalid Size");
1023             break;
1024         }
1025         case Align: {
1026             // 1. If value is a case-sensitive match for the string "start", then let cue's text track cue alignment be start alignment.
1027             if (input.scanRun(valueRun, startKeyword()))
1028                 m_cueAlignment = Start;
1029
1030             // 2. If value is a case-sensitive match for the string "middle", then let cue's text track cue alignment be middle alignment.
1031             else if (input.scanRun(valueRun, middleKeyword()))
1032                 m_cueAlignment = Middle;
1033
1034             // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment.
1035             else if (input.scanRun(valueRun, endKeyword()))
1036                 m_cueAlignment = End;
1037
1038             // 4. If value is a case-sensitive match for the string "left", then let cue's text track cue alignment be left alignment.
1039             else if (input.scanRun(valueRun, leftKeyword()))
1040                 m_cueAlignment = Left;
1041
1042             // 5. If value is a case-sensitive match for the string "right", then let cue's text track cue alignment be right alignment.
1043             else if (input.scanRun(valueRun, rightKeyword()))
1044                 m_cueAlignment = Right;
1045
1046             else
1047                 LOG(Media, "VTTCue::setCueSettings, invalid Align");
1048
1049             break;
1050         }
1051         case RegionId:
1052             m_regionId = input.extractString(valueRun);
1053             break;
1054         case None:
1055             break;
1056         }
1057
1058         // Make sure the entire run is consumed.
1059         input.skipRun(valueRun);
1060     }
1061
1062     // If cue's line position is not auto or cue's size is not 100 or cue's
1063     // writing direction is not horizontal, but cue's region identifier is not
1064     // the empty string, let cue's region identifier be the empty string.
1065     if (m_regionId.isEmpty())
1066         return;
1067
1068     if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDirection != Horizontal)
1069         m_regionId = emptyString();
1070 }
1071
1072 CSSValueID VTTCue::getCSSAlignment() const
1073 {
1074     return displayAlignmentMap[m_cueAlignment];
1075 }
1076
1077 CSSValueID VTTCue::getCSSWritingDirection() const
1078 {
1079     return m_displayDirection;
1080 }
1081
1082 CSSValueID VTTCue::getCSSWritingMode() const
1083 {
1084     return displayWritingModeMap[m_writingDirection];
1085 }
1086
1087 int VTTCue::getCSSSize() const
1088 {
1089     return m_displaySize;
1090 }
1091
1092 std::pair<double, double> VTTCue::getCSSPosition() const
1093 {
1094     if (!m_snapToLines)
1095         return getPositionCoordinates();
1096
1097     return m_displayPosition;
1098 }
1099
1100 bool VTTCue::cueContentsMatch(const TextTrackCue& cue) const
1101 {
1102     const VTTCue* vttCue = toVTTCue(&cue);
1103     if (text() != vttCue->text())
1104         return false;
1105     if (cueSettings() != vttCue->cueSettings())
1106         return false;
1107     if (position() != vttCue->position())
1108         return false;
1109     if (line() != vttCue->line())
1110         return false;
1111     if (size() != vttCue->size())
1112         return false;
1113     if (align() != vttCue->align())
1114         return false;
1115     
1116     return true;
1117 }
1118
1119 bool VTTCue::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const
1120 {
1121     if (!TextTrackCue::isEqual(cue, match))
1122         return false;
1123
1124     if (cue.cueType() != WebVTT)
1125         return false;
1126
1127     return cueContentsMatch(cue);
1128 }
1129
1130 bool VTTCue::doesExtendCue(const TextTrackCue& cue) const
1131 {
1132     if (!cueContentsMatch(cue))
1133         return false;
1134     
1135     return TextTrackCue::doesExtendCue(cue);
1136 }
1137     
1138 void VTTCue::setFontSize(int fontSize, const IntSize&, bool important)
1139 {
1140     if (!hasDisplayTree() || !fontSize)
1141         return;
1142     
1143     LOG(Media, "TextTrackCue::setFontSize - setting cue font size to %i", fontSize);
1144
1145     m_displayTreeShouldChange = true;
1146     displayTreeInternal()->setInlineStyleProperty(CSSPropertyFontSize, fontSize, CSSPrimitiveValue::CSS_PX, important);
1147 }
1148
1149 VTTCue* toVTTCue(TextTrackCue* cue)
1150 {
1151     return const_cast<VTTCue*>(toVTTCue(const_cast<const TextTrackCue*>(cue)));
1152 }
1153
1154 const VTTCue* toVTTCue(const TextTrackCue* cue)
1155 {
1156     ASSERT_WITH_SECURITY_IMPLICATION(cue->isRenderable());
1157     return static_cast<const VTTCue*>(cue);
1158 }
1159
1160 } // namespace WebCore
1161
1162 #endif