f9b5be1c846b9c740a14316d6929d8ad62c6aa77
[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     static NeverDestroyed<const String> start(ASCIILiteral("start"));
76     return start;
77 }
78
79 static const String& middleKeyword()
80 {
81     static NeverDestroyed<const String> middle(ASCIILiteral("middle"));
82     return middle;
83 }
84
85 static const String& endKeyword()
86 {
87     static NeverDestroyed<const String> end(ASCIILiteral("end"));
88     return end;
89 }
90
91 static const String& leftKeyword()
92 {
93     static NeverDestroyed<const String> left("left");
94     return left;
95 }
96
97 static const String& rightKeyword()
98 {
99     static NeverDestroyed<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     static NeverDestroyed<const String> verticalrl(ASCIILiteral("rl"));
111     return verticalrl;
112 }
113
114 static const String& verticalGrowingRightKeyword()
115 {
116     static NeverDestroyed<const String> verticallr(ASCIILiteral("lr"));
117     return verticallr;
118 }
119
120 // ----------------------------
121
122 Ref<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     static NeverDestroyed<const AtomicString> trackDisplayBoxShadowPseudoId("-webkit-media-text-track-display", AtomicString::ConstructFromLiteral);
229     return trackDisplayBoxShadowPseudoId;
230 }
231
232 RenderPtr<RenderElement> VTTCueBox::createElementRenderer(RenderStyle&& style, const RenderTreePosition&)
233 {
234     return createRenderer<RenderVTTCue>(*this, WTFMove(style));
235 }
236
237 // ----------------------------
238
239 const AtomicString& VTTCue::cueBackdropShadowPseudoId()
240 {
241     static NeverDestroyed<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     // FIXME: We should set m_cue in VTTCueBox to nullptr instead.
272     if (m_displayTree && m_displayTree->document().refCount())
273         m_displayTree->remove(ASSERT_NO_EXCEPTION);
274 }
275
276 void VTTCue::initialize(ScriptExecutionContext& context)
277 {
278     m_linePosition = undefinedPosition;
279     m_computedLinePosition = undefinedPosition;
280     m_textPosition = 50;
281     m_cueSize = 100;
282     m_writingDirection = Horizontal;
283     m_cueAlignment = Middle;
284     m_webVTTNodeTree = nullptr;
285     m_cueBackdropBox = HTMLDivElement::create(downcast<Document>(context));
286     m_cueHighlightBox = HTMLSpanElement::create(spanTag, downcast<Document>(context));
287     m_displayDirection = CSSValueLtr;
288     m_displaySize = 0;
289     m_snapToLines = true;
290     m_displayTreeShouldChange = true;
291     m_notifyRegion = true;
292     m_originalStartTime = MediaTime::zeroTime();
293 }
294
295 Ref<VTTCueBox> VTTCue::createDisplayTree()
296 {
297     return VTTCueBox::create(ownerDocument(), *this);
298 }
299
300 VTTCueBox& VTTCue::displayTreeInternal()
301 {
302     if (!m_displayTree)
303         m_displayTree = createDisplayTree();
304     return *m_displayTree;
305 }
306
307 void VTTCue::didChange()
308 {
309     TextTrackCue::didChange();
310     m_displayTreeShouldChange = true;
311 }
312
313 const String& VTTCue::vertical() const
314 {
315     switch (m_writingDirection) {
316     case Horizontal: 
317         return horizontalKeyword();
318     case VerticalGrowingLeft:
319         return verticalGrowingLeftKeyword();
320     case VerticalGrowingRight:
321         return verticalGrowingRightKeyword();
322     default:
323         ASSERT_NOT_REACHED();
324         return emptyString();
325     }
326 }
327
328 ExceptionOr<void> VTTCue::setVertical(const String& value)
329 {
330     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-vertical
331     // On setting, the text track cue writing direction must be set to the value given 
332     // in the first cell of the row in the table above whose second cell is a 
333     // case-sensitive match for the new value, if any. If none of the values match, then
334     // the user agent must instead throw a SyntaxError exception.
335     
336     WritingDirection direction = m_writingDirection;
337     if (value == horizontalKeyword())
338         direction = Horizontal;
339     else if (value == verticalGrowingLeftKeyword())
340         direction = VerticalGrowingLeft;
341     else if (value == verticalGrowingRightKeyword())
342         direction = VerticalGrowingRight;
343     else
344         return Exception { SYNTAX_ERR };
345     
346     if (direction == m_writingDirection)
347         return { };
348
349     willChange();
350     m_writingDirection = direction;
351     didChange();
352
353     return { };
354 }
355
356 void VTTCue::setSnapToLines(bool value)
357 {
358     if (m_snapToLines == value)
359         return;
360     
361     willChange();
362     m_snapToLines = value;
363     didChange();
364 }
365
366 ExceptionOr<void> VTTCue::setLine(double position)
367 {
368     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line
369     // On setting, if the text track cue snap-to-lines flag is not set, and the new
370     // value is negative or greater than 100, then throw an IndexSizeError exception.
371     if (!m_snapToLines && !(position >= 0 && position <= 100))
372         return Exception { INDEX_SIZE_ERR };
373
374     // Otherwise, set the text track cue line position to the new value.
375     if (m_linePosition == position)
376         return { };
377
378     willChange();
379     m_linePosition = position;
380     m_computedLinePosition = calculateComputedLinePosition();
381     didChange();
382
383     return { };
384 }
385
386 ExceptionOr<void> VTTCue::setPosition(double position)
387 {
388     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-position
389     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception.
390     // Otherwise, set the text track cue text position to the new value.
391     if (!(position >= 0 && position <= 100))
392         return Exception { INDEX_SIZE_ERR };
393
394     // Otherwise, set the text track cue line position to the new value.
395     if (m_textPosition == position)
396         return { };
397     
398     willChange();
399     m_textPosition = position;
400     didChange();
401
402     return { };
403 }
404
405 ExceptionOr<void> VTTCue::setSize(int size)
406 {
407     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size
408     // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError
409     // exception. Otherwise, set the text track cue size to the new value.
410     if (!(size >= 0 && size <= 100))
411         return Exception { INDEX_SIZE_ERR };
412
413     // Otherwise, set the text track cue line position to the new value.
414     if (m_cueSize == size)
415         return { };
416     
417     willChange();
418     m_cueSize = size;
419     didChange();
420
421     return { };
422 }
423
424 const String& VTTCue::align() const
425 {
426     switch (m_cueAlignment) {
427     case Start:
428         return startKeyword();
429     case Middle:
430         return middleKeyword();
431     case End:
432         return endKeyword();
433     case Left:
434         return leftKeyword();
435     case Right:
436         return rightKeyword();
437     default:
438         ASSERT_NOT_REACHED();
439         return emptyString();
440     }
441 }
442
443 ExceptionOr<void> VTTCue::setAlign(const String& value)
444 {
445     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-align
446     // On setting, the text track cue alignment must be set to the value given in the 
447     // first cell of the row in the table above whose second cell is a case-sensitive
448     // match for the new value, if any. If none of the values match, then the user
449     // agent must instead throw a SyntaxError exception.
450     
451     CueAlignment alignment;
452     if (value == startKeyword())
453         alignment = Start;
454     else if (value == middleKeyword())
455         alignment = Middle;
456     else if (value == endKeyword())
457         alignment = End;
458     else if (value == leftKeyword())
459         alignment = Left;
460     else if (value == rightKeyword())
461         alignment = Right;
462     else
463         return Exception { SYNTAX_ERR };
464     
465     if (alignment == m_cueAlignment)
466         return { };
467
468     willChange();
469     m_cueAlignment = alignment;
470     didChange();
471
472     return { };
473 }
474     
475 void VTTCue::setText(const String& text)
476 {
477     if (m_content == text)
478         return;
479     
480     willChange();
481     // Clear the document fragment but don't bother to create it again just yet as we can do that
482     // when it is requested.
483     m_webVTTNodeTree = nullptr;
484     m_content = text;
485     didChange();
486 }
487
488 void VTTCue::createWebVTTNodeTree()
489 {
490     if (!m_webVTTNodeTree)
491         m_webVTTNodeTree = WebVTTParser::createDocumentFragmentFromCueText(ownerDocument(), m_content);
492 }
493
494 void VTTCue::copyWebVTTNodeToDOMTree(ContainerNode* webVTTNode, ContainerNode* parent)
495 {
496     for (Node* node = webVTTNode->firstChild(); node; node = node->nextSibling()) {
497         RefPtr<Node> clonedNode;
498         if (is<WebVTTElement>(*node))
499             clonedNode = downcast<WebVTTElement>(*node).createEquivalentHTMLElement(ownerDocument());
500         else
501             clonedNode = node->cloneNode(false);
502         parent->appendChild(*clonedNode, ASSERT_NO_EXCEPTION);
503         if (is<ContainerNode>(*node))
504             copyWebVTTNodeToDOMTree(downcast<ContainerNode>(node), downcast<ContainerNode>(clonedNode.get()));
505     }
506 }
507
508 RefPtr<DocumentFragment> VTTCue::getCueAsHTML()
509 {
510     createWebVTTNodeTree();
511     if (!m_webVTTNodeTree)
512         return nullptr;
513
514     auto clonedFragment = DocumentFragment::create(ownerDocument());
515     copyWebVTTNodeToDOMTree(m_webVTTNodeTree.get(), clonedFragment.ptr());
516     return WTFMove(clonedFragment);
517 }
518
519 RefPtr<DocumentFragment> VTTCue::createCueRenderingTree()
520 {
521     createWebVTTNodeTree();
522     if (!m_webVTTNodeTree)
523         return nullptr;
524
525     auto clonedFragment = DocumentFragment::create(ownerDocument());
526     m_webVTTNodeTree->cloneChildNodes(clonedFragment);
527     return WTFMove(clonedFragment);
528 }
529
530 void VTTCue::setRegionId(const String& regionId)
531 {
532     if (m_regionId == regionId)
533         return;
534
535     willChange();
536     m_regionId = regionId;
537     didChange();
538 }
539
540 void VTTCue::notifyRegionWhenRemovingDisplayTree(bool notifyRegion)
541 {
542     m_notifyRegion = notifyRegion;
543 }
544
545 void VTTCue::setIsActive(bool active)
546 {
547     TextTrackCue::setIsActive(active);
548
549     if (!active) {
550         if (!hasDisplayTree())
551             return;
552
553         // Remove the display tree as soon as the cue becomes inactive.
554         removeDisplayTree();
555     }
556 }
557
558 int VTTCue::calculateComputedLinePosition()
559 {
560     // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-computed-line-position
561
562     // If the text track cue line position is numeric, then that is the text
563     // track cue computed line position.
564     if (m_linePosition != undefinedPosition)
565         return m_linePosition;
566
567     // If the text track cue snap-to-lines flag of the text track cue is not
568     // set, the text track cue computed line position is the value 100;
569     if (!m_snapToLines)
570         return 100;
571
572     // Otherwise, it is the value returned by the following algorithm:
573
574     // If cue is not associated with a text track, return -1 and abort these
575     // steps.
576     if (!track())
577         return -1;
578
579     // Let n be the number of text tracks whose text track mode is showing or
580     // showing by default and that are in the media element's list of text
581     // tracks before track.
582     int n = track()->trackIndexRelativeToRenderedTracks();
583
584     // Increment n by one.
585     n++;
586
587     // Negate n.
588     n = -n;
589
590     return n;
591 }
592
593 static bool isCueParagraphSeparator(UChar character)
594 {
595     // Within a cue, paragraph boundaries are only denoted by Type B characters,
596     // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRAPH SEPARATOR.
597     return u_charType(character) == U_PARAGRAPH_SEPARATOR;
598 }
599
600 void VTTCue::determineTextDirection()
601 {
602     static NeverDestroyed<const String> rtTag(ASCIILiteral("rt"));
603     createWebVTTNodeTree();
604     if (!m_webVTTNodeTree)
605         return;
606
607     // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the
608     // concatenation of the values of each WebVTT Text Object in nodes, in a
609     // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and
610     // their descendants.
611     StringBuilder paragraphBuilder;
612     for (Node* node = m_webVTTNodeTree->firstChild(); node; node = NodeTraversal::next(*node, m_webVTTNodeTree.get())) {
613         // FIXME: The code does not match the comment above. This does not actually exclude Ruby Text Object descendant.
614         if (!node->isTextNode() || node->localName() == rtTag)
615             continue;
616
617         paragraphBuilder.append(node->nodeValue());
618     }
619
620     String paragraph = paragraphBuilder.toString();
621     if (!paragraph.length())
622         return;
623
624     for (size_t i = 0; i < paragraph.length(); ++i) {
625         UChar current = paragraph[i];
626         if (!current || isCueParagraphSeparator(current))
627             return;
628
629         if (UChar current = paragraph[i]) {
630             UCharDirection charDirection = u_charDirection(current);
631             if (charDirection == U_LEFT_TO_RIGHT) {
632                 m_displayDirection = CSSValueLtr;
633                 return;
634             }
635             if (charDirection == U_RIGHT_TO_LEFT || charDirection == U_RIGHT_TO_LEFT_ARABIC) {
636                 m_displayDirection = CSSValueRtl;
637                 return;
638             }
639         }
640     }
641 }
642
643 void VTTCue::calculateDisplayParameters()
644 {
645     // Steps 10.2, 10.3
646     determineTextDirection();
647
648     // 10.4 If the text track cue writing direction is horizontal, then let
649     // block-flow be 'tb'. Otherwise, if the text track cue writing direction is
650     // vertical growing left, then let block-flow be 'lr'. Otherwise, the text
651     // track cue writing direction is vertical growing right; let block-flow be
652     // 'rl'.
653
654     // The above step is done through the writing direction static map.
655
656     // 10.5 Determine the value of maximum size for cue as per the appropriate
657     // rules from the following list:
658     int maximumSize = m_textPosition;
659     if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr)
660         || (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl)
661         || (m_writingDirection == Horizontal && m_cueAlignment == Left)
662         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == Start || m_cueAlignment == Left))
663         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == Start || m_cueAlignment == Left))) {
664         maximumSize = 100 - m_textPosition;
665     } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr)
666         || (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl)
667         || (m_writingDirection == Horizontal && m_cueAlignment == Right)
668         || (m_writingDirection == VerticalGrowingLeft && (m_cueAlignment == End || m_cueAlignment == Right))
669         || (m_writingDirection == VerticalGrowingRight && (m_cueAlignment == End || m_cueAlignment == Right))) {
670         maximumSize = m_textPosition;
671     } else if (m_cueAlignment == Middle) {
672         maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition);
673         maximumSize = maximumSize * 2;
674     } else
675         ASSERT_NOT_REACHED();
676
677     // 10.6 If the text track cue size is less than maximum size, then let size
678     // be text track cue size. Otherwise, let size be maximum size.
679     m_displaySize = std::min(m_cueSize, maximumSize);
680
681     // FIXME: Understand why step 10.7 is missing (just a copy/paste error?)
682     // Could be done within a spec implementation check - http://crbug.com/301580
683
684     // 10.8 Determine the value of x-position or y-position for cue as per the
685     // appropriate rules from the following list:
686     if (m_writingDirection == Horizontal) {
687         switch (m_cueAlignment) {
688         case Start:
689             if (m_displayDirection == CSSValueLtr)
690                 m_displayPosition.first = m_textPosition;
691             else
692                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
693             break;
694         case End:
695             if (m_displayDirection == CSSValueRtl)
696                 m_displayPosition.first = 100 - m_textPosition;
697             else
698                 m_displayPosition.first = m_textPosition - m_displaySize;
699             break;
700         case Left:
701             if (m_displayDirection == CSSValueLtr)
702                 m_displayPosition.first = m_textPosition;
703             else
704                 m_displayPosition.first = 100 - m_textPosition;
705             break;
706         case Right:
707             if (m_displayDirection == CSSValueLtr)
708                 m_displayPosition.first = m_textPosition - m_displaySize;
709             else
710                 m_displayPosition.first = 100 - m_textPosition - m_displaySize;
711             break;
712         case Middle:
713             if (m_displayDirection == CSSValueLtr)
714                 m_displayPosition.first = m_textPosition - m_displaySize / 2;
715             else
716                 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2;
717             break;
718         case NumberOfAlignments:
719             ASSERT_NOT_REACHED();
720         }
721     }
722
723     // A text track cue has a text track cue computed line position whose value
724     // is defined in terms of the other aspects of the cue.
725     m_computedLinePosition = calculateComputedLinePosition();
726
727     // 10.9 Determine the value of whichever of x-position or y-position is not
728     // yet calculated for cue as per the appropriate rules from the following
729     // list:
730     if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
731         m_displayPosition.second = 0;
732
733     if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal)
734         m_displayPosition.second = m_computedLinePosition;
735
736     if (m_snapToLines && m_displayPosition.first == undefinedPosition
737         && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
738         m_displayPosition.first = 0;
739
740     if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight))
741         m_displayPosition.first = m_computedLinePosition;
742 }
743     
744 void VTTCue::markFutureAndPastNodes(ContainerNode* root, const MediaTime& previousTimestamp, const MediaTime& movieTime)
745 {
746     static NeverDestroyed<const String> timestampTag(ASCIILiteral("timestamp"));
747     
748     bool isPastNode = true;
749     MediaTime currentTimestamp = previousTimestamp;
750     if (currentTimestamp > movieTime)
751         isPastNode = false;
752     
753     for (Node* child = root->firstChild(); child; child = NodeTraversal::next(*child, root)) {
754         if (child->nodeName() == timestampTag) {
755             MediaTime currentTimestamp;
756             bool check = WebVTTParser::collectTimeStamp(child->nodeValue(), currentTimestamp);
757             ASSERT_UNUSED(check, check);
758             
759             currentTimestamp += m_originalStartTime;
760             if (currentTimestamp > movieTime)
761                 isPastNode = false;
762         }
763         
764         if (is<WebVTTElement>(*child)) {
765             downcast<WebVTTElement>(*child).setIsPastNode(isPastNode);
766             // Make an elemenet id match a cue id for style matching purposes.
767             if (!id().isEmpty())
768                 downcast<WebVTTElement>(*child).setIdAttribute(id());
769         }
770     }
771 }
772
773 void VTTCue::updateDisplayTree(const MediaTime& movieTime)
774 {
775     // The display tree may contain WebVTT timestamp objects representing
776     // timestamps (processing instructions), along with displayable nodes.
777
778     if (!track()->isRendered())
779         return;
780
781     // Clear the contents of the set.
782     m_cueHighlightBox->removeChildren();
783
784     // Update the two sets containing past and future WebVTT objects.
785     RefPtr<DocumentFragment> referenceTree = createCueRenderingTree();
786     if (!referenceTree)
787         return;
788
789     markFutureAndPastNodes(referenceTree.get(), startMediaTime(), movieTime);
790     m_cueHighlightBox->appendChild(*referenceTree);
791 }
792
793 VTTCueBox& VTTCue::getDisplayTree(const IntSize& videoSize, int fontSize)
794 {
795     Ref<VTTCueBox> displayTree = displayTreeInternal();
796     if (!m_displayTreeShouldChange || !track()->isRendered())
797         return displayTree.get();
798
799     // 10.1 - 10.10
800     calculateDisplayParameters();
801
802     // 10.11. Apply the terms of the CSS specifications to nodes within the
803     // following constraints, thus obtaining a set of CSS boxes positioned
804     // relative to an initial containing block:
805     displayTree->removeChildren();
806
807     // The document tree is the tree of WebVTT Node Objects rooted at nodes.
808
809     // The children of the nodes must be wrapped in an anonymous box whose
810     // 'display' property has the value 'inline'. This is the WebVTT cue
811     // background box.
812
813     // Note: This is contained by default in m_cueHighlightBox.
814     m_cueHighlightBox->setPseudo(cueShadowPseudoId());
815
816     m_cueBackdropBox->setPseudo(cueBackdropShadowPseudoId());
817     m_cueBackdropBox->appendChild(*m_cueHighlightBox, ASSERT_NO_EXCEPTION);
818     displayTree->appendChild(*m_cueBackdropBox, ASSERT_NO_EXCEPTION);
819
820     // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not
821     // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose
822     // 'display' property has the value 'ruby-base'.
823
824     displayTree->setFontSizeFromCaptionUserPrefs(fontSize);
825     displayTree->applyCSSProperties(videoSize);
826
827     m_displayTreeShouldChange = false;
828
829     // 10.15. Let cue's text track cue display state have the CSS boxes in
830     // boxes.
831     return displayTree.get();
832 }
833
834 void VTTCue::removeDisplayTree()
835 {
836     // The region needs to be informed about the cue removal.
837     if (m_notifyRegion && track()) {
838         if (VTTRegionList* regions = track()->regions()) {
839             if (VTTRegion* region = regions->getRegionById(m_regionId))
840                 region->willRemoveTextTrackCueBox(m_displayTree.get());
841         }
842     }
843
844     if (!hasDisplayTree())
845         return;
846     displayTreeInternal().remove(ASSERT_NO_EXCEPTION);
847 }
848
849 std::pair<double, double> VTTCue::getPositionCoordinates() const
850 {
851     // This method is used for setting x and y when snap to lines is not set.
852     std::pair<double, double> coordinates;
853
854     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) {
855         coordinates.first = m_textPosition;
856         coordinates.second = m_computedLinePosition;
857
858         return coordinates;
859     }
860
861     if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) {
862         coordinates.first = 100 - m_textPosition;
863         coordinates.second = m_computedLinePosition;
864
865         return coordinates;
866     }
867
868     if (m_writingDirection == VerticalGrowingLeft) {
869         coordinates.first = 100 - m_computedLinePosition;
870         coordinates.second = m_textPosition;
871
872         return coordinates;
873     }
874
875     if (m_writingDirection == VerticalGrowingRight) {
876         coordinates.first = m_computedLinePosition;
877         coordinates.second = m_textPosition;
878
879         return coordinates;
880     }
881
882     ASSERT_NOT_REACHED();
883
884     return coordinates;
885 }
886
887 VTTCue::CueSetting VTTCue::settingName(VTTScanner& input)
888 {
889     CueSetting parsedSetting = None;
890     if (input.scan("vertical"))
891         parsedSetting = Vertical;
892     else if (input.scan("line"))
893         parsedSetting = Line;
894     else if (input.scan("position"))
895         parsedSetting = Position;
896     else if (input.scan("size"))
897         parsedSetting = Size;
898     else if (input.scan("align"))
899         parsedSetting = Align;
900     else if (input.scan("region"))
901         parsedSetting = RegionId;
902
903     // Verify that a ':' follows.
904     if (parsedSetting != None && input.scan(':'))
905         return parsedSetting;
906
907     return None;
908 }
909
910 void VTTCue::setCueSettings(const String& inputString)
911 {
912     if (inputString.isEmpty())
913         return;
914
915     VTTScanner input(inputString);
916
917     while (!input.isAtEnd()) {
918
919         // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order, 
920         // separated from each other by one or more U+0020 SPACE characters or U+0009 CHARACTER TABULATION (tab) characters.
921         input.skipWhile<WebVTTParser::isValidSettingDelimiter>();
922         if (input.isAtEnd())
923             break;
924
925         // When the user agent is to parse the WebVTT settings given by a string input for a text track cue cue, 
926         // the user agent must run the following steps:
927         // 1. Let settings be the result of splitting input on spaces.
928         // 2. For each token setting in the list settings, run the following substeps:
929         //    1. If setting does not contain a U+003A COLON character (:), or if the first U+003A COLON character (:) 
930         //       in setting is either the first or last character of setting, then jump to the step labeled next setting.
931         //    2. Let name be the leading substring of setting up to and excluding the first U+003A COLON character (:) in that string.
932         CueSetting name = settingName(input);
933
934         // 3. Let value be the trailing substring of setting starting from the character immediately after the first U+003A COLON character (:) in that string.
935         VTTScanner::Run valueRun = input.collectUntil<WebVTTParser::isValidSettingDelimiter>();
936
937         // 4. Run the appropriate substeps that apply for the value of name, as follows:
938         switch (name) {
939         case Vertical: {
940             // If name is a case-sensitive match for "vertical"
941             // 1. If value is a case-sensitive match for the string "rl", then let cue's text track cue writing direction 
942             //    be vertical growing left.
943             if (input.scanRun(valueRun, verticalGrowingLeftKeyword()))
944                 m_writingDirection = VerticalGrowingLeft;
945             
946             // 2. Otherwise, if value is a case-sensitive match for the string "lr", then let cue's text track cue writing 
947             //    direction be vertical growing right.
948             else if (input.scanRun(valueRun, verticalGrowingRightKeyword()))
949                 m_writingDirection = VerticalGrowingRight;
950
951             else
952                 LOG(Media, "VTTCue::setCueSettings, invalid Vertical");
953             break;
954         }
955         case Line: {
956             bool isValid = false;
957             do {
958                 // 1-2 - Collect chars that are either '-', '%', or a digit.
959                 // 1. If value contains any characters other than U+002D HYPHEN-MINUS characters (-), U+0025 PERCENT SIGN
960                 //    characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump
961                 //    to the step labeled next setting.
962                 float linePosition;
963                 bool isNegative;
964                 if (!input.scanFloat(linePosition, &isNegative))
965                     break;
966
967                 bool isPercentage = input.scan('%');
968                 if (!input.isAt(valueRun.end()))
969                     break;
970
971                 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT
972                 //    NINE (9), then jump to the step labeled next setting.
973                 // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then
974                 //    jump to the step labeled next setting.
975                 // 4. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then
976                 //    jump to the step labeled next setting.
977                 // 5. If the first character in value is a U+002D HYPHEN-MINUS character (-) and the last character in value is a
978                 //    U+0025 PERCENT SIGN character (%), then jump to the step labeled next setting.
979                 if (isPercentage && isNegative)
980                     break;
981
982                 // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and
983                 //    let number be that number.
984                 // 7. If the last character in value is a U+0025 PERCENT SIGN character (%), but number is not in the range
985                 //    0 ≤ number ≤ 100, then jump to the step labeled next setting.
986                 // 8. Let cue's text track cue line position be number.
987                 // 9. If the last character in value is a U+0025 PERCENT SIGN character (%), then let cue's text track cue
988                 //    snap-to-lines flag be false. Otherwise, let it be true.
989                 if (isPercentage) {
990                     if (linePosition < 0 || linePosition > 100)
991                         break;
992
993                     // 10 - If '%' then set snap-to-lines flag to false.
994                     m_snapToLines = false;
995                 } else {
996                     if (linePosition - static_cast<int>(linePosition))
997                         break;
998
999                     m_snapToLines = true;
1000                 }
1001                 
1002                 m_linePosition = linePosition;
1003                 isValid = true;
1004             } while (0);
1005
1006             if (!isValid)
1007                 LOG(Media, "VTTCue::setCueSettings, invalid Line");
1008
1009             break;
1010         }
1011         case Position: {
1012             float position;
1013             if (WebVTTParser::parseFloatPercentageValue(input, position) && input.isAt(valueRun.end()))
1014                 m_textPosition = position;
1015             else
1016                 LOG(Media, "VTTCue::setCueSettings, invalid Position");
1017             break;
1018         }
1019         case Size: {
1020             float cueSize;
1021             if (WebVTTParser::parseFloatPercentageValue(input, cueSize) && input.isAt(valueRun.end()))
1022                 m_cueSize = cueSize;
1023             else
1024                 LOG(Media, "VTTCue::setCueSettings, invalid Size");
1025             break;
1026         }
1027         case Align: {
1028             // 1. If value is a case-sensitive match for the string "start", then let cue's text track cue alignment be start alignment.
1029             if (input.scanRun(valueRun, startKeyword()))
1030                 m_cueAlignment = Start;
1031
1032             // 2. If value is a case-sensitive match for the string "middle", then let cue's text track cue alignment be middle alignment.
1033             else if (input.scanRun(valueRun, middleKeyword()))
1034                 m_cueAlignment = Middle;
1035
1036             // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment.
1037             else if (input.scanRun(valueRun, endKeyword()))
1038                 m_cueAlignment = End;
1039
1040             // 4. If value is a case-sensitive match for the string "left", then let cue's text track cue alignment be left alignment.
1041             else if (input.scanRun(valueRun, leftKeyword()))
1042                 m_cueAlignment = Left;
1043
1044             // 5. If value is a case-sensitive match for the string "right", then let cue's text track cue alignment be right alignment.
1045             else if (input.scanRun(valueRun, rightKeyword()))
1046                 m_cueAlignment = Right;
1047
1048             else
1049                 LOG(Media, "VTTCue::setCueSettings, invalid Align");
1050
1051             break;
1052         }
1053         case RegionId:
1054             m_regionId = input.extractString(valueRun);
1055             break;
1056         case None:
1057             break;
1058         }
1059
1060         // Make sure the entire run is consumed.
1061         input.skipRun(valueRun);
1062     }
1063
1064     // If cue's line position is not auto or cue's size is not 100 or cue's
1065     // writing direction is not horizontal, but cue's region identifier is not
1066     // the empty string, let cue's region identifier be the empty string.
1067     if (m_regionId.isEmpty())
1068         return;
1069
1070     if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDirection != Horizontal)
1071         m_regionId = emptyString();
1072 }
1073
1074 CSSValueID VTTCue::getCSSAlignment() const
1075 {
1076     return displayAlignmentMap[m_cueAlignment];
1077 }
1078
1079 CSSValueID VTTCue::getCSSWritingDirection() const
1080 {
1081     return m_displayDirection;
1082 }
1083
1084 CSSValueID VTTCue::getCSSWritingMode() const
1085 {
1086     return displayWritingModeMap[m_writingDirection];
1087 }
1088
1089 int VTTCue::getCSSSize() const
1090 {
1091     return m_displaySize;
1092 }
1093
1094 std::pair<double, double> VTTCue::getCSSPosition() const
1095 {
1096     if (!m_snapToLines)
1097         return getPositionCoordinates();
1098
1099     return m_displayPosition;
1100 }
1101
1102 bool VTTCue::cueContentsMatch(const TextTrackCue& cue) const
1103 {
1104     const VTTCue* vttCue = toVTTCue(&cue);
1105     if (text() != vttCue->text())
1106         return false;
1107     if (cueSettings() != vttCue->cueSettings())
1108         return false;
1109     if (position() != vttCue->position())
1110         return false;
1111     if (line() != vttCue->line())
1112         return false;
1113     if (size() != vttCue->size())
1114         return false;
1115     if (align() != vttCue->align())
1116         return false;
1117     
1118     return true;
1119 }
1120
1121 bool VTTCue::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const
1122 {
1123     if (!TextTrackCue::isEqual(cue, match))
1124         return false;
1125
1126     if (cue.cueType() != WebVTT)
1127         return false;
1128
1129     return cueContentsMatch(cue);
1130 }
1131
1132 bool VTTCue::doesExtendCue(const TextTrackCue& cue) const
1133 {
1134     if (!cueContentsMatch(cue))
1135         return false;
1136     
1137     return TextTrackCue::doesExtendCue(cue);
1138 }
1139     
1140 void VTTCue::setFontSize(int fontSize, const IntSize&, bool important)
1141 {
1142     if (!hasDisplayTree() || !fontSize)
1143         return;
1144     
1145     LOG(Media, "TextTrackCue::setFontSize - setting cue font size to %i", fontSize);
1146
1147     m_displayTreeShouldChange = true;
1148     displayTreeInternal().setInlineStyleProperty(CSSPropertyFontSize, fontSize, CSSPrimitiveValue::CSS_PX, important);
1149 }
1150
1151 VTTCue* toVTTCue(TextTrackCue* cue)
1152 {
1153     return const_cast<VTTCue*>(toVTTCue(const_cast<const TextTrackCue*>(cue)));
1154 }
1155
1156 const VTTCue* toVTTCue(const TextTrackCue* cue)
1157 {
1158     ASSERT_WITH_SECURITY_IMPLICATION(cue->isRenderable());
1159     return static_cast<const VTTCue*>(cue);
1160 }
1161
1162 } // namespace WebCore
1163
1164 #endif