1 /* 2 * Copyright (C) 2011 Google Inc. All rights reserved. 3 * Copyright (C) 2011, 2012, 2013 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 #include "core/html/track/TextTrackCue.h" 35 36 #include "CSSPropertyNames.h" 37 #include "CSSValueKeywords.h" 38 #include "bindings/v8/ExceptionStatePlaceholder.h" 39 #include "core/dom/DocumentFragment.h" 40 #include "core/dom/Event.h" 41 #include "core/dom/NodeTraversal.h" 42 #include "core/html/HTMLDivElement.h" 43 #include "core/html/track/TextTrack.h" 44 #include "core/html/track/TextTrackCueList.h" 45 #include "core/html/track/TextTrackRegionList.h" 46 #include "core/html/track/WebVTTElement.h" 47 #include "core/html/track/WebVTTParser.h" 48 #include "core/rendering/RenderTextTrackCue.h" 49 #include "wtf/MathExtras.h" 50 #include "wtf/text/StringBuilder.h" 51 52 namespace WebCore { 53 54 static const int invalidCueIndex = -1; 55 static const int undefinedPosition = -1; 56 static const int autoSize = 0; 57 58 static const String& startKeyword() 59 { 60 DEFINE_STATIC_LOCAL(const String, start, ("start")); 61 return start; 62 } 63 64 static const String& middleKeyword() 65 { 66 DEFINE_STATIC_LOCAL(const String, middle, ("middle")); 67 return middle; 68 } 69 70 static const String& endKeyword() 71 { 72 DEFINE_STATIC_LOCAL(const String, end, ("end")); 73 return end; 74 } 75 76 static const String& horizontalKeyword() 77 { 78 return emptyString(); 79 } 80 81 static const String& verticalGrowingLeftKeyword() 82 { 83 DEFINE_STATIC_LOCAL(const String, verticalrl, ("rl")); 84 return verticalrl; 85 } 86 87 static const String& verticalGrowingRightKeyword() 88 { 89 DEFINE_STATIC_LOCAL(const String, verticallr, ("lr")); 90 return verticallr; 91 } 92 93 // ---------------------------- 94 95 TextTrackCueBox::TextTrackCueBox(Document* document, TextTrackCue* cue) 96 : HTMLDivElement(divTag, document) 97 , m_cue(cue) 98 { 99 setPart(textTrackCueBoxShadowPseudoId()); 100 } 101 102 TextTrackCue* TextTrackCueBox::getCue() const 103 { 104 return m_cue; 105 } 106 107 void TextTrackCueBox::applyCSSProperties(const IntSize&) 108 { 109 // FIXME: Apply all the initial CSS positioning properties. http://wkb.ug/79916 110 #if ENABLE(WEBVTT_REGIONS) 111 if (!m_cue->regionId().isEmpty()) { 112 setInlineStyleProperty(CSSPropertyPosition, CSSValueRelative); 113 return; 114 } 115 #endif 116 117 // 3.5.1 On the (root) List of WebVTT Node Objects: 118 119 // the 'position' property must be set to 'absolute' 120 setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute); 121 122 // the 'unicode-bidi' property must be set to 'plaintext' 123 setInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValueWebkitPlaintext); 124 125 // the 'direction' property must be set to direction 126 setInlineStyleProperty(CSSPropertyDirection, m_cue->getCSSWritingDirection()); 127 128 // the 'writing-mode' property must be set to writing-mode 129 setInlineStyleProperty(CSSPropertyWebkitWritingMode, m_cue->getCSSWritingMode()); 130 131 std::pair<float, float> position = m_cue->getCSSPosition(); 132 133 // the 'top' property must be set to top, 134 setInlineStyleProperty(CSSPropertyTop, position.second, CSSPrimitiveValue::CSS_PERCENTAGE); 135 136 // the 'left' property must be set to left 137 setInlineStyleProperty(CSSPropertyLeft, position.first, CSSPrimitiveValue::CSS_PERCENTAGE); 138 139 // the 'width' property must be set to width, and the 'height' property must be set to height 140 if (m_cue->vertical() == horizontalKeyword()) { 141 setInlineStyleProperty(CSSPropertyWidth, static_cast<double>(m_cue->getCSSSize()), CSSPrimitiveValue::CSS_PERCENTAGE); 142 setInlineStyleProperty(CSSPropertyHeight, CSSValueAuto); 143 } else { 144 setInlineStyleProperty(CSSPropertyWidth, CSSValueAuto); 145 setInlineStyleProperty(CSSPropertyHeight, static_cast<double>(m_cue->getCSSSize()), CSSPrimitiveValue::CSS_PERCENTAGE); 146 } 147 148 // The 'text-align' property on the (root) List of WebVTT Node Objects must 149 // be set to the value in the second cell of the row of the table below 150 // whose first cell is the value of the corresponding cue's text track cue 151 // alignment: 152 if (m_cue->align() == startKeyword()) 153 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueStart); 154 else if (m_cue->align() == endKeyword()) 155 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueEnd); 156 else 157 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueCenter); 158 159 if (!m_cue->snapToLines()) { 160 // 10.13.1 Set up x and y: 161 // Note: x and y are set through the CSS left and top above. 162 163 // 10.13.2 Position the boxes in boxes such that the point x% along the 164 // width of the bounding box of the boxes in boxes is x% of the way 165 // across the width of the video's rendering area, and the point y% 166 // along the height of the bounding box of the boxes in boxes is y% 167 // of the way across the height of the video's rendering area, while 168 // maintaining the relative positions of the boxes in boxes to each 169 // other. 170 setInlineStyleProperty(CSSPropertyWebkitTransform, 171 String::format("translate(-%.2f%%, -%.2f%%)", position.first, position.second)); 172 173 setInlineStyleProperty(CSSPropertyWhiteSpace, CSSValuePre); 174 } 175 } 176 177 const AtomicString& TextTrackCueBox::textTrackCueBoxShadowPseudoId() 178 { 179 DEFINE_STATIC_LOCAL(const AtomicString, trackDisplayBoxShadowPseudoId, ("-webkit-media-text-track-display", AtomicString::ConstructFromLiteral)); 180 return trackDisplayBoxShadowPseudoId; 181 } 182 183 RenderObject* TextTrackCueBox::createRenderer(RenderStyle*) 184 { 185 return new RenderTextTrackCue(this); 186 } 187 188 // ---------------------------- 189 190 TextTrackCue::TextTrackCue(ScriptExecutionContext* context, double start, double end, const String& content) 191 : m_startTime(start) 192 , m_endTime(end) 193 , m_content(content) 194 , m_linePosition(undefinedPosition) 195 , m_computedLinePosition(undefinedPosition) 196 , m_textPosition(50) 197 , m_cueSize(100) 198 , m_cueIndex(invalidCueIndex) 199 , m_writingDirection(Horizontal) 200 , m_cueAlignment(Middle) 201 , m_webVTTNodeTree(0) 202 , m_track(0) 203 , m_scriptExecutionContext(context) 204 , m_isActive(false) 205 , m_pauseOnExit(false) 206 , m_snapToLines(true) 207 , m_cueBackgroundBox(HTMLDivElement::create(toDocument(context))) 208 , m_displayTreeShouldChange(true) 209 , m_displayDirection(CSSValueLtr) 210 { 211 ASSERT(m_scriptExecutionContext->isDocument()); 212 ScriptWrappable::init(this); 213 214 // 4. If the text track cue writing direction is horizontal, then let 215 // writing-mode be 'horizontal-tb'. Otherwise, if the text track cue writing 216 // direction is vertical growing left, then let writing-mode be 217 // 'vertical-rl'. Otherwise, the text track cue writing direction is 218 // vertical growing right; let writing-mode be 'vertical-lr'. 219 m_displayWritingModeMap[Horizontal] = CSSValueHorizontalTb; 220 m_displayWritingModeMap[VerticalGrowingLeft] = CSSValueVerticalRl; 221 m_displayWritingModeMap[VerticalGrowingRight] = CSSValueVerticalLr; 222 } 223 224 TextTrackCue::~TextTrackCue() 225 { 226 displayTreeInternal()->remove(ASSERT_NO_EXCEPTION); 227 } 228 229 PassRefPtr<TextTrackCueBox> TextTrackCue::createDisplayTree() 230 { 231 return TextTrackCueBox::create(ownerDocument(), this); 232 } 233 234 PassRefPtr<TextTrackCueBox> TextTrackCue::displayTreeInternal() 235 { 236 if (!m_displayTree) 237 m_displayTree = createDisplayTree(); 238 return m_displayTree; 239 } 240 241 void TextTrackCue::cueWillChange() 242 { 243 if (m_track) 244 m_track->cueWillChange(this); 245 } 246 247 void TextTrackCue::cueDidChange() 248 { 249 if (m_track) 250 m_track->cueDidChange(this); 251 252 m_displayTreeShouldChange = true; 253 } 254 255 TextTrack* TextTrackCue::track() const 256 { 257 return m_track; 258 } 259 260 void TextTrackCue::setTrack(TextTrack* track) 261 { 262 m_track = track; 263 } 264 265 void TextTrackCue::setId(const String& id) 266 { 267 if (m_id == id) 268 return; 269 270 cueWillChange(); 271 m_id = id; 272 cueDidChange(); 273 } 274 275 void TextTrackCue::setStartTime(double value, ExceptionState& es) 276 { 277 // NaN, Infinity and -Infinity values should trigger a TypeError. 278 if (std::isinf(value) || std::isnan(value)) { 279 es.throwTypeError(); 280 return; 281 } 282 283 // TODO(93143): Add spec-compliant behavior for negative time values. 284 if (m_startTime == value || value < 0) 285 return; 286 287 cueWillChange(); 288 m_startTime = value; 289 cueDidChange(); 290 } 291 292 void TextTrackCue::setEndTime(double value, ExceptionState& es) 293 { 294 // NaN, Infinity and -Infinity values should trigger a TypeError. 295 if (std::isinf(value) || std::isnan(value)) { 296 es.throwTypeError(); 297 return; 298 } 299 300 // TODO(93143): Add spec-compliant behavior for negative time values. 301 if (m_endTime == value || value < 0) 302 return; 303 304 cueWillChange(); 305 m_endTime = value; 306 cueDidChange(); 307 } 308 309 void TextTrackCue::setPauseOnExit(bool value) 310 { 311 if (m_pauseOnExit == value) 312 return; 313 314 cueWillChange(); 315 m_pauseOnExit = value; 316 cueDidChange(); 317 } 318 319 const String& TextTrackCue::vertical() const 320 { 321 switch (m_writingDirection) { 322 case Horizontal: 323 return horizontalKeyword(); 324 case VerticalGrowingLeft: 325 return verticalGrowingLeftKeyword(); 326 case VerticalGrowingRight: 327 return verticalGrowingRightKeyword(); 328 default: 329 ASSERT_NOT_REACHED(); 330 return emptyString(); 331 } 332 } 333 334 void TextTrackCue::setVertical(const String& value, ExceptionState& es) 335 { 336 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-vertical 337 // On setting, the text track cue writing direction must be set to the value given 338 // in the first cell of the row in the table above whose second cell is a 339 // case-sensitive match for the new value, if any. If none of the values match, then 340 // the user agent must instead throw a SyntaxError exception. 341 342 WritingDirection direction = m_writingDirection; 343 if (value == horizontalKeyword()) 344 direction = Horizontal; 345 else if (value == verticalGrowingLeftKeyword()) 346 direction = VerticalGrowingLeft; 347 else if (value == verticalGrowingRightKeyword()) 348 direction = VerticalGrowingRight; 349 else 350 es.throwDOMException(SyntaxError); 351 352 if (direction == m_writingDirection) 353 return; 354 355 cueWillChange(); 356 m_writingDirection = direction; 357 cueDidChange(); 358 } 359 360 void TextTrackCue::setSnapToLines(bool value) 361 { 362 if (m_snapToLines == value) 363 return; 364 365 cueWillChange(); 366 m_snapToLines = value; 367 cueDidChange(); 368 } 369 370 void TextTrackCue::setLine(int position, ExceptionState& es) 371 { 372 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-line 373 // On setting, if the text track cue snap-to-lines flag is not set, and the new 374 // value is negative or greater than 100, then throw an IndexSizeError exception. 375 if (!m_snapToLines && (position < 0 || position > 100)) { 376 es.throwDOMException(IndexSizeError); 377 return; 378 } 379 380 // Otherwise, set the text track cue line position to the new value. 381 if (m_linePosition == position) 382 return; 383 384 cueWillChange(); 385 m_linePosition = position; 386 m_computedLinePosition = calculateComputedLinePosition(); 387 cueDidChange(); 388 } 389 390 void TextTrackCue::setPosition(int position, ExceptionState& es) 391 { 392 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-position 393 // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError exception. 394 // Otherwise, set the text track cue text position to the new value. 395 if (position < 0 || position > 100) { 396 es.throwDOMException(IndexSizeError); 397 return; 398 } 399 400 // Otherwise, set the text track cue line position to the new value. 401 if (m_textPosition == position) 402 return; 403 404 cueWillChange(); 405 m_textPosition = position; 406 cueDidChange(); 407 } 408 409 void TextTrackCue::setSize(int size, ExceptionState& es) 410 { 411 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#dom-texttrackcue-size 412 // On setting, if the new value is negative or greater than 100, then throw an IndexSizeError 413 // exception. Otherwise, set the text track cue size to the new value. 414 if (size < 0 || size > 100) { 415 es.throwDOMException(IndexSizeError); 416 return; 417 } 418 419 // Otherwise, set the text track cue line position to the new value. 420 if (m_cueSize == size) 421 return; 422 423 cueWillChange(); 424 m_cueSize = size; 425 cueDidChange(); 426 } 427 428 const String& TextTrackCue::align() const 429 { 430 switch (m_cueAlignment) { 431 case Start: 432 return startKeyword(); 433 case Middle: 434 return middleKeyword(); 435 case End: 436 return endKeyword(); 437 default: 438 ASSERT_NOT_REACHED(); 439 return emptyString(); 440 } 441 } 442 443 void TextTrackCue::setAlign(const String& value, ExceptionState& es) 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 = m_cueAlignment; 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 459 es.throwDOMException(SyntaxError); 460 461 if (alignment == m_cueAlignment) 462 return; 463 464 cueWillChange(); 465 m_cueAlignment = alignment; 466 cueDidChange(); 467 } 468 469 void TextTrackCue::setText(const String& text) 470 { 471 if (m_content == text) 472 return; 473 474 cueWillChange(); 475 // Clear the document fragment but don't bother to create it again just yet as we can do that 476 // when it is requested. 477 m_webVTTNodeTree = 0; 478 m_content = text; 479 cueDidChange(); 480 } 481 482 int TextTrackCue::cueIndex() 483 { 484 if (m_cueIndex == invalidCueIndex) 485 m_cueIndex = track()->cues()->getCueIndex(this); 486 487 return m_cueIndex; 488 } 489 490 void TextTrackCue::invalidateCueIndex() 491 { 492 m_cueIndex = invalidCueIndex; 493 } 494 495 void TextTrackCue::createWebVTTNodeTree() 496 { 497 if (!m_webVTTNodeTree) 498 m_webVTTNodeTree = WebVTTParser::create(0, m_scriptExecutionContext)->createDocumentFragmentFromCueText(m_content); 499 } 500 501 void TextTrackCue::copyWebVTTNodeToDOMTree(ContainerNode* webVTTNode, ContainerNode* parent) 502 { 503 for (Node* node = webVTTNode->firstChild(); node; node = node->nextSibling()) { 504 RefPtr<Node> clonedNode; 505 if (node->isWebVTTElement()) 506 clonedNode = toWebVTTElement(node)->createEquivalentHTMLElement(ownerDocument()); 507 else 508 clonedNode = node->cloneNode(false); 509 parent->appendChild(clonedNode, ASSERT_NO_EXCEPTION); 510 if (node->isContainerNode()) 511 copyWebVTTNodeToDOMTree(toContainerNode(node), toContainerNode(clonedNode.get())); 512 } 513 } 514 515 PassRefPtr<DocumentFragment> TextTrackCue::getCueAsHTML() 516 { 517 createWebVTTNodeTree(); 518 RefPtr<DocumentFragment> clonedFragment = DocumentFragment::create(ownerDocument()); 519 copyWebVTTNodeToDOMTree(m_webVTTNodeTree.get(), clonedFragment.get()); 520 return clonedFragment.release(); 521 } 522 523 PassRefPtr<DocumentFragment> TextTrackCue::createCueRenderingTree() 524 { 525 RefPtr<DocumentFragment> clonedFragment; 526 createWebVTTNodeTree(); 527 clonedFragment = DocumentFragment::create(ownerDocument()); 528 m_webVTTNodeTree->cloneChildNodes(clonedFragment.get()); 529 return clonedFragment.release(); 530 } 531 532 bool TextTrackCue::dispatchEvent(PassRefPtr<Event> event) 533 { 534 // When a TextTrack's mode is disabled: no cues are active, no events fired. 535 if (!track() || track()->mode() == TextTrack::disabledKeyword()) 536 return false; 537 538 return EventTarget::dispatchEvent(event); 539 } 540 541 #if ENABLE(WEBVTT_REGIONS) 542 void TextTrackCue::setRegionId(const String& regionId) 543 { 544 if (m_regionId == regionId) 545 return; 546 547 cueWillChange(); 548 m_regionId = regionId; 549 cueDidChange(); 550 } 551 #endif 552 553 bool TextTrackCue::isActive() 554 { 555 return m_isActive && track() && track()->mode() != TextTrack::disabledKeyword(); 556 } 557 558 void TextTrackCue::setIsActive(bool active) 559 { 560 m_isActive = active; 561 562 // Remove the display tree as soon as the cue becomes inactive. 563 if (!active) 564 removeDisplayTree(); 565 } 566 567 int TextTrackCue::calculateComputedLinePosition() 568 { 569 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#text-track-cue-computed-line-position 570 571 // If the text track cue line position is numeric, then that is the text 572 // track cue computed line position. 573 if (m_linePosition != undefinedPosition) 574 return m_linePosition; 575 576 // If the text track cue snap-to-lines flag of the text track cue is not 577 // set, the text track cue computed line position is the value 100; 578 if (!m_snapToLines) 579 return 100; 580 581 // Otherwise, it is the value returned by the following algorithm: 582 583 // If cue is not associated with a text track, return -1 and abort these 584 // steps. 585 if (!track()) 586 return -1; 587 588 // Let n be the number of text tracks whose text track mode is showing or 589 // showing by default and that are in the media element's list of text 590 // tracks before track. 591 int n = track()->trackIndexRelativeToRenderedTracks(); 592 593 // Increment n by one. 594 n++; 595 596 // Negate n. 597 n = -n; 598 599 return n; 600 } 601 602 static bool isCueParagraphSeparator(UChar character) 603 { 604 // Within a cue, paragraph boundaries are only denoted by Type B characters, 605 // such as U+000A LINE FEED (LF), U+0085 NEXT LINE (NEL), and U+2029 PARAGRAPH SEPARATOR. 606 return WTF::Unicode::category(character) & WTF::Unicode::Separator_Paragraph; 607 } 608 609 void TextTrackCue::determineTextDirection() 610 { 611 DEFINE_STATIC_LOCAL(const String, rtTag, ("rt")); 612 createWebVTTNodeTree(); 613 614 // Apply the Unicode Bidirectional Algorithm's Paragraph Level steps to the 615 // concatenation of the values of each WebVTT Text Object in nodes, in a 616 // pre-order, depth-first traversal, excluding WebVTT Ruby Text Objects and 617 // their descendants. 618 StringBuilder paragraphBuilder; 619 for (Node* node = m_webVTTNodeTree->firstChild(); node; node = NodeTraversal::next(node, m_webVTTNodeTree.get())) { 620 if (!node->isTextNode() || node->localName() == rtTag) 621 continue; 622 623 paragraphBuilder.append(node->nodeValue()); 624 } 625 626 String paragraph = paragraphBuilder.toString(); 627 if (!paragraph.length()) 628 return; 629 630 for (size_t i = 0; i < paragraph.length(); ++i) { 631 UChar current = paragraph[i]; 632 if (!current || isCueParagraphSeparator(current)) 633 return; 634 635 if (UChar current = paragraph[i]) { 636 WTF::Unicode::Direction charDirection = WTF::Unicode::direction(current); 637 if (charDirection == WTF::Unicode::LeftToRight) { 638 m_displayDirection = CSSValueLtr; 639 return; 640 } 641 if (charDirection == WTF::Unicode::RightToLeft 642 || charDirection == WTF::Unicode::RightToLeftArabic) { 643 m_displayDirection = CSSValueRtl; 644 return; 645 } 646 } 647 } 648 } 649 650 void TextTrackCue::calculateDisplayParameters() 651 { 652 // Steps 10.2, 10.3 653 determineTextDirection(); 654 655 // 10.4 If the text track cue writing direction is horizontal, then let 656 // block-flow be 'tb'. Otherwise, if the text track cue writing direction is 657 // vertical growing left, then let block-flow be 'lr'. Otherwise, the text 658 // track cue writing direction is vertical growing right; let block-flow be 659 // 'rl'. 660 m_displayWritingMode = m_displayWritingModeMap[m_writingDirection]; 661 662 // 10.5 Determine the value of maximum size for cue as per the appropriate 663 // rules from the following list: 664 int maximumSize = m_textPosition; 665 if ((m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueLtr) 666 || (m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueRtl) 667 || (m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Start) 668 || (m_writingDirection == VerticalGrowingRight && m_cueAlignment == Start)) { 669 maximumSize = 100 - m_textPosition; 670 } else if ((m_writingDirection == Horizontal && m_cueAlignment == End && m_displayDirection == CSSValueLtr) 671 || (m_writingDirection == Horizontal && m_cueAlignment == Start && m_displayDirection == CSSValueRtl) 672 || (m_writingDirection == VerticalGrowingLeft && m_cueAlignment == End) 673 || (m_writingDirection == VerticalGrowingRight && m_cueAlignment == End)) { 674 maximumSize = m_textPosition; 675 } else if (m_cueAlignment == Middle) { 676 maximumSize = m_textPosition <= 50 ? m_textPosition : (100 - m_textPosition); 677 maximumSize = maximumSize * 2; 678 } 679 680 // 10.6 If the text track cue size is less than maximum size, then let size 681 // be text track cue size. Otherwise, let size be maximum size. 682 m_displaySize = std::min(m_cueSize, maximumSize); 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 if (m_cueAlignment == Start) { 688 if (m_displayDirection == CSSValueLtr) 689 m_displayPosition.first = m_textPosition; 690 else 691 m_displayPosition.first = 100 - m_textPosition - m_displaySize; 692 } else if (m_cueAlignment == End) { 693 if (m_displayDirection == CSSValueRtl) 694 m_displayPosition.first = 100 - m_textPosition; 695 else 696 m_displayPosition.first = m_textPosition - m_displaySize; 697 } 698 } 699 700 if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Start) 701 || (m_writingDirection == VerticalGrowingRight && m_cueAlignment == Start)) { 702 m_displayPosition.second = m_textPosition; 703 } else if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == End) 704 || (m_writingDirection == VerticalGrowingRight && m_cueAlignment == End)) { 705 m_displayPosition.second = 100 - m_textPosition; 706 } 707 708 if (m_writingDirection == Horizontal && m_cueAlignment == Middle) { 709 if (m_displayDirection == CSSValueLtr) 710 m_displayPosition.first = m_textPosition - m_displaySize / 2; 711 else 712 m_displayPosition.first = 100 - m_textPosition - m_displaySize / 2; 713 } 714 715 if ((m_writingDirection == VerticalGrowingLeft && m_cueAlignment == Middle) 716 || (m_writingDirection == VerticalGrowingRight && m_cueAlignment == Middle)) 717 m_displayPosition.second = m_textPosition - m_displaySize / 2; 718 719 // 10.9 Determine the value of whichever of x-position or y-position is not 720 // yet calculated for cue as per the appropriate rules from the following 721 // list: 722 if (m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal) 723 m_displayPosition.second = 0; 724 725 if (!m_snapToLines && m_displayPosition.second == undefinedPosition && m_writingDirection == Horizontal) 726 m_displayPosition.second = m_computedLinePosition; 727 728 if (m_snapToLines && m_displayPosition.first == undefinedPosition 729 && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight)) 730 m_displayPosition.first = 0; 731 732 if (!m_snapToLines && (m_writingDirection == VerticalGrowingLeft || m_writingDirection == VerticalGrowingRight)) 733 m_displayPosition.first = m_computedLinePosition; 734 735 // A text track cue has a text track cue computed line position whose value 736 // is defined in terms of the other aspects of the cue. 737 m_computedLinePosition = calculateComputedLinePosition(); 738 } 739 740 void TextTrackCue::markFutureAndPastNodes(ContainerNode* root, double previousTimestamp, double movieTime) 741 { 742 DEFINE_STATIC_LOCAL(const String, timestampTag, ("timestamp")); 743 744 bool isPastNode = true; 745 double currentTimestamp = previousTimestamp; 746 if (currentTimestamp > movieTime) 747 isPastNode = false; 748 749 for (Node* child = root->firstChild(); child; child = NodeTraversal::next(child, root)) { 750 if (child->nodeName() == timestampTag) { 751 unsigned position = 0; 752 String timestamp = child->nodeValue(); 753 double currentTimestamp = WebVTTParser::create(0, m_scriptExecutionContext)->collectTimeStamp(timestamp, &position); 754 ASSERT(currentTimestamp != -1); 755 756 if (currentTimestamp > movieTime) 757 isPastNode = false; 758 } 759 760 if (child->isWebVTTElement()) { 761 toWebVTTElement(child)->setIsPastNode(isPastNode); 762 // Make an elemenet id match a cue id for style matching purposes. 763 if (!m_id.isEmpty()) 764 toElement(child)->setIdAttribute(m_id); 765 } 766 } 767 } 768 769 void TextTrackCue::updateDisplayTree(double movieTime) 770 { 771 // The display tree may contain WebVTT timestamp objects representing 772 // timestamps (processing instructions), along with displayable nodes. 773 774 if (!track()->isRendered()) 775 return; 776 777 // Clear the contents of the set. 778 m_cueBackgroundBox->removeChildren(); 779 780 // Update the two sets containing past and future WebVTT objects. 781 RefPtr<DocumentFragment> referenceTree = createCueRenderingTree(); 782 markFutureAndPastNodes(referenceTree.get(), startTime(), movieTime); 783 m_cueBackgroundBox->appendChild(referenceTree); 784 } 785 786 PassRefPtr<TextTrackCueBox> TextTrackCue::getDisplayTree(const IntSize& videoSize) 787 { 788 RefPtr<TextTrackCueBox> displayTree = displayTreeInternal(); 789 if (!m_displayTreeShouldChange || !track()->isRendered()) 790 return displayTree; 791 792 // 10.1 - 10.10 793 calculateDisplayParameters(); 794 795 // 10.11. Apply the terms of the CSS specifications to nodes within the 796 // following constraints, thus obtaining a set of CSS boxes positioned 797 // relative to an initial containing block: 798 displayTree->removeChildren(); 799 800 // The document tree is the tree of WebVTT Node Objects rooted at nodes. 801 802 // The children of the nodes must be wrapped in an anonymous box whose 803 // 'display' property has the value 'inline'. This is the WebVTT cue 804 // background box. 805 806 // Note: This is contained by default in m_cueBackgroundBox. 807 m_cueBackgroundBox->setPart(cueShadowPseudoId()); 808 displayTree->appendChild(m_cueBackgroundBox, ASSERT_NO_EXCEPTION, AttachLazily); 809 810 // FIXME(BUG 79916): Runs of children of WebVTT Ruby Objects that are not 811 // WebVTT Ruby Text Objects must be wrapped in anonymous boxes whose 812 // 'display' property has the value 'ruby-base'. 813 814 // FIXME(BUG 79916): Text runs must be wrapped according to the CSS 815 // line-wrapping rules, except that additionally, regardless of the value of 816 // the 'white-space' property, lines must be wrapped at the edge of their 817 // containing blocks, even if doing so requires splitting a word where there 818 // is no line breaking opportunity. (Thus, normally text wraps as needed, 819 // but if there is a particularly long word, it does not overflow as it 820 // normally would in CSS, it is instead forcibly wrapped at the box's edge.) 821 displayTree->applyCSSProperties(videoSize); 822 823 m_displayTreeShouldChange = false; 824 825 // 10.15. Let cue's text track cue display state have the CSS boxes in 826 // boxes. 827 return displayTree; 828 } 829 830 void TextTrackCue::removeDisplayTree() 831 { 832 #if ENABLE(WEBVTT_REGIONS) 833 // The region needs to be informed about the cue removal. 834 TextTrackRegion* region = m_track->regions()->getRegionById(m_regionId); 835 if (region) 836 region->willRemoveTextTrackCueBox(m_displayTree.get()); 837 #endif 838 839 displayTreeInternal()->remove(ASSERT_NO_EXCEPTION); 840 } 841 842 std::pair<double, double> TextTrackCue::getPositionCoordinates() const 843 { 844 // This method is used for setting x and y when snap to lines is not set. 845 std::pair<double, double> coordinates; 846 847 if (m_writingDirection == Horizontal && m_displayDirection == CSSValueLtr) { 848 coordinates.first = m_textPosition; 849 coordinates.second = m_computedLinePosition; 850 851 return coordinates; 852 } 853 854 if (m_writingDirection == Horizontal && m_displayDirection == CSSValueRtl) { 855 coordinates.first = 100 - m_textPosition; 856 coordinates.second = m_computedLinePosition; 857 858 return coordinates; 859 } 860 861 if (m_writingDirection == VerticalGrowingLeft) { 862 coordinates.first = 100 - m_computedLinePosition; 863 coordinates.second = m_textPosition; 864 865 return coordinates; 866 } 867 868 if (m_writingDirection == VerticalGrowingRight) { 869 coordinates.first = m_computedLinePosition; 870 coordinates.second = m_textPosition; 871 872 return coordinates; 873 } 874 875 ASSERT_NOT_REACHED(); 876 877 return coordinates; 878 } 879 880 TextTrackCue::CueSetting TextTrackCue::settingName(const String& name) 881 { 882 DEFINE_STATIC_LOCAL(const String, verticalKeyword, ("vertical")); 883 DEFINE_STATIC_LOCAL(const String, lineKeyword, ("line")); 884 DEFINE_STATIC_LOCAL(const String, positionKeyword, ("position")); 885 DEFINE_STATIC_LOCAL(const String, sizeKeyword, ("size")); 886 DEFINE_STATIC_LOCAL(const String, alignKeyword, ("align")); 887 #if ENABLE(WEBVTT_REGIONS) 888 DEFINE_STATIC_LOCAL(const String, regionIdKeyword, ("region")); 889 #endif 890 891 if (name == verticalKeyword) 892 return Vertical; 893 else if (name == lineKeyword) 894 return Line; 895 else if (name == positionKeyword) 896 return Position; 897 else if (name == sizeKeyword) 898 return Size; 899 else if (name == alignKeyword) 900 return Align; 901 #if ENABLE(WEBVTT_REGIONS) 902 else if (name == regionIdKeyword) 903 return RegionId; 904 #endif 905 906 return None; 907 } 908 909 void TextTrackCue::setCueSettings(const String& input) 910 { 911 m_settings = input; 912 unsigned position = 0; 913 914 while (position < input.length()) { 915 916 // The WebVTT cue settings part of a WebVTT cue consists of zero or more of the following components, in any order, 917 // separated from each other by one or more U+0020 SPACE characters or U+0009 CHARACTER TABULATION (tab) characters. 918 while (position < input.length() && WebVTTParser::isValidSettingDelimiter(input[position])) 919 position++; 920 if (position >= input.length()) 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 unsigned endOfSetting = position; 930 String setting = WebVTTParser::collectWord(input, &endOfSetting); 931 CueSetting name; 932 size_t colonOffset = setting.find(':', 1); 933 if (colonOffset == notFound || colonOffset == 0 || colonOffset == setting.length() - 1) 934 goto NextSetting; 935 936 // 2. Let name be the leading substring of setting up to and excluding the first U+003A COLON character (:) in that string. 937 name = settingName(setting.substring(0, colonOffset)); 938 939 // 3. Let value be the trailing substring of setting starting from the character immediately after the first U+003A COLON character (:) in that string. 940 position += colonOffset + 1; 941 if (position >= input.length()) 942 break; 943 944 // 4. Run the appropriate substeps that apply for the value of name, as follows: 945 switch (name) { 946 case Vertical: 947 { 948 // If name is a case-sensitive match for "vertical" 949 // 1. If value is a case-sensitive match for the string "rl", then let cue's text track cue writing direction 950 // be vertical growing left. 951 String writingDirection = WebVTTParser::collectWord(input, &position); 952 if (writingDirection == verticalGrowingLeftKeyword()) 953 m_writingDirection = VerticalGrowingLeft; 954 955 // 2. Otherwise, if value is a case-sensitive match for the string "lr", then let cue's text track cue writing 956 // direction be vertical growing right. 957 else if (writingDirection == verticalGrowingRightKeyword()) 958 m_writingDirection = VerticalGrowingRight; 959 } 960 break; 961 case Line: 962 { 963 // 1-2 - Collect chars that are either '-', '%', or a digit. 964 // 1. If value contains any characters other than U+002D HYPHEN-MINUS characters (-), U+0025 PERCENT SIGN 965 // characters (%), and characters in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump 966 // to the step labeled next setting. 967 StringBuilder linePositionBuilder; 968 while (position < input.length() && (input[position] == '-' || input[position] == '%' || isASCIIDigit(input[position]))) 969 linePositionBuilder.append(input[position++]); 970 if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position])) 971 break; 972 973 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT 974 // NINE (9), then jump to the step labeled next setting. 975 // 3. If any character in value other than the first character is a U+002D HYPHEN-MINUS character (-), then 976 // jump to the step labeled next setting. 977 // 4. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then 978 // jump to the step labeled next setting. 979 String linePosition = linePositionBuilder.toString(); 980 if (linePosition.find('-', 1) != notFound || linePosition.reverseFind("%", linePosition.length() - 2) != notFound) 981 break; 982 983 // 5. If the first character in value is a U+002D HYPHEN-MINUS character (-) and the last character in value is a 984 // U+0025 PERCENT SIGN character (%), then jump to the step labeled next setting. 985 if (linePosition[0] == '-' && linePosition[linePosition.length() - 1] == '%') 986 break; 987 988 // 6. Ignoring the trailing percent sign, if any, interpret value as a (potentially signed) integer, and 989 // let number be that number. 990 // NOTE: toInt ignores trailing non-digit characters, such as '%'. 991 bool validNumber; 992 int number = linePosition.toInt(&validNumber); 993 if (!validNumber) 994 break; 995 996 // 7. If the last character in value is a U+0025 PERCENT SIGN character (%), but number is not in the range 997 // 0 number 100, then jump to the step labeled next setting. 998 // 8. Let cue's text track cue line position be number. 999 // 9. If the last character in value is a U+0025 PERCENT SIGN character (%), then let cue's text track cue 1000 // snap-to-lines flag be false. Otherwise, let it be true. 1001 if (linePosition[linePosition.length() - 1] == '%') { 1002 if (number < 0 || number > 100) 1003 break; 1004 1005 // 10 - If '%' then set snap-to-lines flag to false. 1006 m_snapToLines = false; 1007 } 1008 1009 m_linePosition = number; 1010 } 1011 break; 1012 case Position: 1013 { 1014 // 1. If value contains any characters other than U+0025 PERCENT SIGN characters (%) and characters in the range 1015 // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump to the step labeled next setting. 1016 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), 1017 // then jump to the step labeled next setting. 1018 String textPosition = WebVTTParser::collectDigits(input, &position); 1019 if (textPosition.isEmpty()) 1020 break; 1021 if (position >= input.length()) 1022 break; 1023 1024 // 3. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), then jump 1025 // to the step labeled next setting. 1026 // 4. If the last character in value is not a U+0025 PERCENT SIGN character (%), then jump to the step labeled 1027 // next setting. 1028 if (input[position++] != '%') 1029 break; 1030 if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position])) 1031 break; 1032 1033 // 5. Ignoring the trailing percent sign, interpret value as an integer, and let number be that number. 1034 // 6. If number is not in the range 0 number 100, then jump to the step labeled next setting. 1035 // NOTE: toInt ignores trailing non-digit characters, such as '%'. 1036 bool validNumber; 1037 int number = textPosition.toInt(&validNumber); 1038 if (!validNumber) 1039 break; 1040 if (number < 0 || number > 100) 1041 break; 1042 1043 // 7. Let cue's text track cue text position be number. 1044 m_textPosition = number; 1045 } 1046 break; 1047 case Size: 1048 { 1049 // 1. If value contains any characters other than U+0025 PERCENT SIGN characters (%) and characters in the 1050 // range U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9), then jump to the step labeled next setting. 1051 // 2. If value does not contain at least one character in the range U+0030 DIGIT ZERO (0) to U+0039 DIGIT 1052 // NINE (9), then jump to the step labeled next setting. 1053 String cueSize = WebVTTParser::collectDigits(input, &position); 1054 if (cueSize.isEmpty()) 1055 break; 1056 if (position >= input.length()) 1057 break; 1058 1059 // 3. If any character in value other than the last character is a U+0025 PERCENT SIGN character (%), 1060 // then jump to the step labeled next setting. 1061 // 4. If the last character in value is not a U+0025 PERCENT SIGN character (%), then jump to the step 1062 // labeled next setting. 1063 if (input[position++] != '%') 1064 break; 1065 if (position < input.length() && !WebVTTParser::isValidSettingDelimiter(input[position])) 1066 break; 1067 1068 // 5. Ignoring the trailing percent sign, interpret value as an integer, and let number be that number. 1069 // 6. If number is not in the range 0 number 100, then jump to the step labeled next setting. 1070 bool validNumber; 1071 int number = cueSize.toInt(&validNumber); 1072 if (!validNumber) 1073 break; 1074 if (number < 0 || number > 100) 1075 break; 1076 1077 // 7. Let cue's text track cue size be number. 1078 m_cueSize = number; 1079 } 1080 break; 1081 case Align: 1082 { 1083 String cueAlignment = WebVTTParser::collectWord(input, &position); 1084 1085 // 1. If value is a case-sensitive match for the string "start", then let cue's text track cue alignment be start alignment. 1086 if (cueAlignment == startKeyword()) 1087 m_cueAlignment = Start; 1088 1089 // 2. If value is a case-sensitive match for the string "middle", then let cue's text track cue alignment be middle alignment. 1090 else if (cueAlignment == middleKeyword()) 1091 m_cueAlignment = Middle; 1092 1093 // 3. If value is a case-sensitive match for the string "end", then let cue's text track cue alignment be end alignment. 1094 else if (cueAlignment == endKeyword()) 1095 m_cueAlignment = End; 1096 } 1097 break; 1098 #if ENABLE(WEBVTT_REGIONS) 1099 case RegionId: 1100 m_regionId = WebVTTParser::collectWord(input, &position); 1101 break; 1102 #endif 1103 case None: 1104 break; 1105 } 1106 1107 NextSetting: 1108 position = endOfSetting; 1109 } 1110 #if ENABLE(WEBVTT_REGIONS) 1111 // If cue's line position is not auto or cue's size is not 100 or cue's 1112 // writing direction is not horizontal, but cue's region identifier is not 1113 // the empty string, let cue's region identifier be the empty string. 1114 if (m_regionId.isEmpty()) 1115 return; 1116 1117 if (m_linePosition != undefinedPosition || m_cueSize != 100 || m_writingDirection != Horizontal) 1118 m_regionId = emptyString(); 1119 #endif 1120 } 1121 1122 CSSValueID TextTrackCue::getCSSWritingDirection() const 1123 { 1124 return m_displayDirection; 1125 } 1126 1127 CSSValueID TextTrackCue::getCSSWritingMode() const 1128 { 1129 return m_displayWritingMode; 1130 } 1131 1132 int TextTrackCue::getCSSSize() const 1133 { 1134 return m_displaySize; 1135 } 1136 1137 std::pair<double, double> TextTrackCue::getCSSPosition() const 1138 { 1139 if (!m_snapToLines) 1140 return getPositionCoordinates(); 1141 1142 return m_displayPosition; 1143 } 1144 1145 const AtomicString& TextTrackCue::interfaceName() const 1146 { 1147 return eventNames().interfaceForTextTrackCue; 1148 } 1149 1150 ScriptExecutionContext* TextTrackCue::scriptExecutionContext() const 1151 { 1152 return m_scriptExecutionContext; 1153 } 1154 1155 EventTargetData* TextTrackCue::eventTargetData() 1156 { 1157 return &m_eventTargetData; 1158 } 1159 1160 EventTargetData* TextTrackCue::ensureEventTargetData() 1161 { 1162 return &m_eventTargetData; 1163 } 1164 1165 bool TextTrackCue::operator==(const TextTrackCue& cue) const 1166 { 1167 if (cueType() != cue.cueType()) 1168 return false; 1169 1170 if (m_endTime != cue.endTime()) 1171 return false; 1172 if (m_startTime != cue.startTime()) 1173 return false; 1174 if (m_content != cue.text()) 1175 return false; 1176 if (m_settings != cue.cueSettings()) 1177 return false; 1178 if (m_id != cue.id()) 1179 return false; 1180 if (m_textPosition != cue.position()) 1181 return false; 1182 if (m_linePosition != cue.line()) 1183 return false; 1184 if (m_cueSize != cue.size()) 1185 return false; 1186 if (align() != cue.align()) 1187 return false; 1188 1189 return true; 1190 } 1191 1192 } // namespace WebCore 1193