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