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