| 1 | /* |
| 2 | * Copyright (C) 2012 Victor Carbune (victor@rosedu.org) |
| 3 | * Copyright (C) 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 |
| 7 | * are met: |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * |
| 14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| 15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| 17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| 18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| 19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| 20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| 21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| 22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 25 | */ |
| 26 | |
| 27 | #include "config.h" |
| 28 | |
| 29 | #if ENABLE(VIDEO_TRACK) |
| 30 | #include "RenderVTTCue.h" |
| 31 | |
| 32 | #include "RenderInline.h" |
| 33 | #include "RenderLayoutState.h" |
| 34 | #include "RenderView.h" |
| 35 | #include "TextTrackCueGeneric.h" |
| 36 | #include "VTTCue.h" |
| 37 | #include <wtf/IsoMallocInlines.h> |
| 38 | #include <wtf/StackStats.h> |
| 39 | |
| 40 | namespace WebCore { |
| 41 | |
| 42 | WTF_MAKE_ISO_ALLOCATED_IMPL(RenderVTTCue); |
| 43 | |
| 44 | RenderVTTCue::RenderVTTCue(VTTCueBox& element, RenderStyle&& style) |
| 45 | : RenderBlockFlow(element, WTFMove(style)) |
| 46 | , m_cue(element.getCue()) |
| 47 | { |
| 48 | } |
| 49 | |
| 50 | void RenderVTTCue::layout() |
| 51 | { |
| 52 | StackStats::LayoutCheckPoint layoutCheckPoint; |
| 53 | RenderBlockFlow::layout(); |
| 54 | |
| 55 | // If WebVTT Regions are used, the regular WebVTT layout algorithm is no |
| 56 | // longer necessary, since cues having the region parameter set do not have |
| 57 | // any positioning parameters. Also, in this case, the regions themselves |
| 58 | // have positioning information. |
| 59 | if (!m_cue->regionId().isEmpty()) |
| 60 | return; |
| 61 | |
| 62 | LayoutStateMaintainer statePusher(*this, locationOffset(), hasTransform() || hasReflection() || style().isFlippedBlocksWritingMode()); |
| 63 | |
| 64 | if (m_cue->cueType()== TextTrackCue::WebVTT) { |
| 65 | if (toVTTCue(m_cue)->snapToLines()) |
| 66 | repositionCueSnapToLinesSet(); |
| 67 | else |
| 68 | repositionCueSnapToLinesNotSet(); |
| 69 | } else |
| 70 | repositionGenericCue(); |
| 71 | } |
| 72 | |
| 73 | bool RenderVTTCue::initializeLayoutParameters(InlineFlowBox*& firstLineBox, LayoutUnit& step, LayoutUnit& position) |
| 74 | { |
| 75 | ASSERT(firstChild()); |
| 76 | if (!firstChild()) |
| 77 | return false; |
| 78 | |
| 79 | RenderBlock* parentBlock = containingBlock(); |
| 80 | |
| 81 | // firstChild() returns the wrapping (backdrop) <div>. The cue object is |
| 82 | // the <div>'s first child. |
| 83 | RenderObject& firstChild = *this->firstChild(); |
| 84 | RenderElement& backdropElement = downcast<RenderElement>(firstChild); |
| 85 | |
| 86 | firstLineBox = downcast<RenderInline>(*backdropElement.firstChild()).firstLineBox(); |
| 87 | if (!firstLineBox) |
| 88 | firstLineBox = this->firstRootBox(); |
| 89 | |
| 90 | // 1. Horizontal: Let step be the height of the first line box in boxes. |
| 91 | // Vertical: Let step be the width of the first line box in boxes. |
| 92 | step = m_cue->getWritingDirection() == VTTCue::Horizontal ? firstLineBox->height() : firstLineBox->width(); |
| 93 | |
| 94 | // 2. If step is zero, then jump to the step labeled done positioning below. |
| 95 | if (!step) |
| 96 | return false; |
| 97 | |
| 98 | // 3. Let line position be the text track cue computed line position. |
| 99 | int linePosition = m_cue->calculateComputedLinePosition(); |
| 100 | |
| 101 | // 4. Vertical Growing Left: Add one to line position then negate it. |
| 102 | if (m_cue->getWritingDirection() == VTTCue::VerticalGrowingLeft) |
| 103 | linePosition = -(linePosition + 1); |
| 104 | |
| 105 | // 5. Let position be the result of multiplying step and line position. |
| 106 | position = step * linePosition; |
| 107 | |
| 108 | // 6. Vertical Growing Left: Decrease position by the width of the |
| 109 | // bounding box of the boxes in boxes, then increase position by step. |
| 110 | if (m_cue->getWritingDirection() == VTTCue::VerticalGrowingLeft) { |
| 111 | position -= width(); |
| 112 | position += step; |
| 113 | } |
| 114 | |
| 115 | // 7. If line position is less than zero... |
| 116 | if (linePosition < 0) { |
| 117 | // Horizontal / Vertical: ... then increase position by the |
| 118 | // height / width of the video's rendering area ... |
| 119 | position += m_cue->getWritingDirection() == VTTCue::Horizontal ? parentBlock->height() : parentBlock->width(); |
| 120 | |
| 121 | // ... and negate step. |
| 122 | step = -step; |
| 123 | } |
| 124 | |
| 125 | return true; |
| 126 | } |
| 127 | |
| 128 | void RenderVTTCue::placeBoxInDefaultPosition(LayoutUnit position, bool& switched) |
| 129 | { |
| 130 | // 8. Move all boxes in boxes ... |
| 131 | if (m_cue->getWritingDirection() == VTTCue::Horizontal) |
| 132 | // Horizontal: ... down by the distance given by position |
| 133 | setY(y() + position); |
| 134 | else |
| 135 | // Vertical: ... right by the distance given by position |
| 136 | setX(x() + position); |
| 137 | |
| 138 | // 9. Default: Remember the position of all the boxes in boxes as their |
| 139 | // default position. |
| 140 | m_fallbackPosition = FloatPoint(x(), y()); |
| 141 | |
| 142 | // 10. Let switched be false. |
| 143 | switched = false; |
| 144 | } |
| 145 | |
| 146 | bool RenderVTTCue::isOutside() const |
| 147 | { |
| 148 | return !rectIsWithinContainer(absoluteContentBox()); |
| 149 | } |
| 150 | |
| 151 | bool RenderVTTCue::rectIsWithinContainer(const IntRect& rect) const |
| 152 | { |
| 153 | return containingBlock()->absoluteBoundingBoxRect().contains(rect); |
| 154 | } |
| 155 | |
| 156 | |
| 157 | bool RenderVTTCue::isOverlapping() const |
| 158 | { |
| 159 | return overlappingObject(); |
| 160 | } |
| 161 | |
| 162 | RenderObject* RenderVTTCue::overlappingObject() const |
| 163 | { |
| 164 | return overlappingObjectForRect(absoluteBoundingBoxRect()); |
| 165 | } |
| 166 | |
| 167 | RenderObject* RenderVTTCue::overlappingObjectForRect(const IntRect& rect) const |
| 168 | { |
| 169 | for (RenderObject* box = previousSibling(); box; box = box->previousSibling()) { |
| 170 | IntRect boxRect = box->absoluteBoundingBoxRect(); |
| 171 | |
| 172 | if (rect.intersects(boxRect)) |
| 173 | return box; |
| 174 | } |
| 175 | |
| 176 | return 0; |
| 177 | } |
| 178 | |
| 179 | bool RenderVTTCue::shouldSwitchDirection(InlineFlowBox* firstLineBox, LayoutUnit step) const |
| 180 | { |
| 181 | LayoutUnit top = y(); |
| 182 | LayoutUnit left = x(); |
| 183 | LayoutUnit bottom = top + firstLineBox->height(); |
| 184 | LayoutUnit right = left + firstLineBox->width(); |
| 185 | |
| 186 | // 12. Horizontal: If step is negative and the top of the first line |
| 187 | // box in boxes is now above the top of the video's rendering area, |
| 188 | // or if step is positive and the bottom of the first line box in |
| 189 | // boxes is now below the bottom of the video's rendering area, jump |
| 190 | // to the step labeled switch direction. |
| 191 | LayoutUnit parentHeight = containingBlock()->height(); |
| 192 | if (m_cue->getWritingDirection() == VTTCue::Horizontal && ((step < 0 && top < 0) || (step > 0 && bottom > parentHeight))) |
| 193 | return true; |
| 194 | |
| 195 | // 12. Vertical: If step is negative and the left edge of the first line |
| 196 | // box in boxes is now to the left of the left edge of the video's |
| 197 | // rendering area, or if step is positive and the right edge of the |
| 198 | // first line box in boxes is now to the right of the right edge of |
| 199 | // the video's rendering area, jump to the step labeled switch direction. |
| 200 | LayoutUnit parentWidth = containingBlock()->width(); |
| 201 | if (m_cue->getWritingDirection() != VTTCue::Horizontal && ((step < 0 && left < 0) || (step > 0 && right > parentWidth))) |
| 202 | return true; |
| 203 | |
| 204 | return false; |
| 205 | } |
| 206 | |
| 207 | void RenderVTTCue::moveBoxesByStep(LayoutUnit step) |
| 208 | { |
| 209 | // 13. Horizontal: Move all the boxes in boxes down by the distance |
| 210 | // given by step. (If step is negative, then this will actually |
| 211 | // result in an upwards movement of the boxes in absolute terms.) |
| 212 | if (m_cue->getWritingDirection() == VTTCue::Horizontal) |
| 213 | setY(y() + step); |
| 214 | |
| 215 | // 13. Vertical: Move all the boxes in boxes right by the distance |
| 216 | // given by step. (If step is negative, then this will actually |
| 217 | // result in a leftwards movement of the boxes in absolute terms.) |
| 218 | else |
| 219 | setX(x() + step); |
| 220 | } |
| 221 | |
| 222 | bool RenderVTTCue::switchDirection(bool& switched, LayoutUnit& step) |
| 223 | { |
| 224 | // 15. Switch direction: Move all the boxes in boxes back to their |
| 225 | // default position as determined in the step above labeled default. |
| 226 | setX(m_fallbackPosition.x()); |
| 227 | setY(m_fallbackPosition.y()); |
| 228 | |
| 229 | // 16. If switched is true, jump to the step labeled done |
| 230 | // positioning below. |
| 231 | if (switched) |
| 232 | return false; |
| 233 | |
| 234 | // 17. Negate step. |
| 235 | step = -step; |
| 236 | |
| 237 | // 18. Set switched to true. |
| 238 | switched = true; |
| 239 | return true; |
| 240 | } |
| 241 | |
| 242 | void RenderVTTCue::moveIfNecessaryToKeepWithinContainer() |
| 243 | { |
| 244 | IntRect containerRect = containingBlock()->absoluteBoundingBoxRect(); |
| 245 | IntRect cueRect = absoluteBoundingBoxRect(); |
| 246 | |
| 247 | int topOverflow = cueRect.y() - containerRect.y(); |
| 248 | int bottomOverflow = containerRect.maxY() - cueRect.maxY(); |
| 249 | |
| 250 | int verticalAdjustment = 0; |
| 251 | if (topOverflow < 0) |
| 252 | verticalAdjustment = -topOverflow; |
| 253 | else if (bottomOverflow < 0) |
| 254 | verticalAdjustment = bottomOverflow; |
| 255 | |
| 256 | if (verticalAdjustment) |
| 257 | setY(y() + verticalAdjustment); |
| 258 | |
| 259 | int leftOverflow = cueRect.x() - containerRect.x(); |
| 260 | int rightOverflow = containerRect.maxX() - cueRect.maxX(); |
| 261 | |
| 262 | int horizontalAdjustment = 0; |
| 263 | if (leftOverflow < 0) |
| 264 | horizontalAdjustment = -leftOverflow; |
| 265 | else if (rightOverflow < 0) |
| 266 | horizontalAdjustment = rightOverflow; |
| 267 | |
| 268 | if (horizontalAdjustment) |
| 269 | setX(x() + horizontalAdjustment); |
| 270 | } |
| 271 | |
| 272 | bool RenderVTTCue::findNonOverlappingPosition(int& newX, int& newY) const |
| 273 | { |
| 274 | newX = x(); |
| 275 | newY = y(); |
| 276 | IntRect srcRect = absoluteBoundingBoxRect(); |
| 277 | IntRect destRect = srcRect; |
| 278 | |
| 279 | // Move the box up, looking for a non-overlapping position: |
| 280 | while (RenderObject* box = overlappingObjectForRect(destRect)) { |
| 281 | if (m_cue->getWritingDirection() == VTTCue::Horizontal) |
| 282 | destRect.setY(box->absoluteBoundingBoxRect().y() - destRect.height()); |
| 283 | else |
| 284 | destRect.setX(box->absoluteBoundingBoxRect().x() - destRect.width()); |
| 285 | } |
| 286 | |
| 287 | if (rectIsWithinContainer(destRect)) { |
| 288 | newX += destRect.x() - srcRect.x(); |
| 289 | newY += destRect.y() - srcRect.y(); |
| 290 | return true; |
| 291 | } |
| 292 | |
| 293 | destRect = srcRect; |
| 294 | |
| 295 | // Move the box down, looking for a non-overlapping position: |
| 296 | while (RenderObject* box = overlappingObjectForRect(destRect)) { |
| 297 | if (m_cue->getWritingDirection() == VTTCue::Horizontal) |
| 298 | destRect.setY(box->absoluteBoundingBoxRect().maxY()); |
| 299 | else |
| 300 | destRect.setX(box->absoluteBoundingBoxRect().maxX()); |
| 301 | } |
| 302 | |
| 303 | if (rectIsWithinContainer(destRect)) { |
| 304 | newX += destRect.x() - srcRect.x(); |
| 305 | newY += destRect.y() - srcRect.y(); |
| 306 | return true; |
| 307 | } |
| 308 | |
| 309 | return false; |
| 310 | } |
| 311 | |
| 312 | void RenderVTTCue::repositionCueSnapToLinesSet() |
| 313 | { |
| 314 | InlineFlowBox* firstLineBox; |
| 315 | LayoutUnit step; |
| 316 | LayoutUnit position; |
| 317 | if (!initializeLayoutParameters(firstLineBox, step, position)) |
| 318 | return; |
| 319 | |
| 320 | bool switched; |
| 321 | placeBoxInDefaultPosition(position, switched); |
| 322 | |
| 323 | // 11. Step loop: If none of the boxes in boxes would overlap any of the boxes |
| 324 | // in output and all the boxes in output are within the video's rendering area |
| 325 | // then jump to the step labeled done positioning. |
| 326 | while (isOutside() || isOverlapping()) { |
| 327 | if (!shouldSwitchDirection(firstLineBox, step)) |
| 328 | // 13. Move all the boxes in boxes ... |
| 329 | // 14. Jump back to the step labeled step loop. |
| 330 | moveBoxesByStep(step); |
| 331 | else if (!switchDirection(switched, step)) |
| 332 | break; |
| 333 | |
| 334 | // 19. Jump back to the step labeled step loop. |
| 335 | } |
| 336 | |
| 337 | // Acommodate extra top and bottom padding, border or margin. |
| 338 | // Note: this is supported only for internal UA styling, not through the cue selector. |
| 339 | if (hasInlineDirectionBordersPaddingOrMargin()) |
| 340 | moveIfNecessaryToKeepWithinContainer(); |
| 341 | } |
| 342 | |
| 343 | void RenderVTTCue::repositionGenericCue() |
| 344 | { |
| 345 | ASSERT(firstChild()); |
| 346 | |
| 347 | // firstChild() returns the wrapping (backdrop) <div>. The cue object is |
| 348 | // the <div>'s first child. |
| 349 | RenderObject& firstChild = *this->firstChild(); |
| 350 | RenderElement& backdropElement = downcast<RenderElement>(firstChild); |
| 351 | |
| 352 | InlineFlowBox* firstLineBox = downcast<RenderInline>(*backdropElement.firstChild()).firstLineBox(); |
| 353 | if (static_cast<TextTrackCueGeneric*>(m_cue)->useDefaultPosition() && firstLineBox) { |
| 354 | LayoutUnit parentWidth = containingBlock()->logicalWidth(); |
| 355 | LayoutUnit width = firstLineBox->width(); |
| 356 | LayoutUnit right = (parentWidth / 2) - (width / 2); |
| 357 | setX(right); |
| 358 | } |
| 359 | repositionCueSnapToLinesNotSet(); |
| 360 | } |
| 361 | |
| 362 | void RenderVTTCue::repositionCueSnapToLinesNotSet() |
| 363 | { |
| 364 | // 3. If none of the boxes in boxes would overlap any of the boxes in output, and all the boxes in |
| 365 | // output are within the video's rendering area, then jump to the step labeled done positioning below. |
| 366 | if (!isOutside() && !isOverlapping()) |
| 367 | return; |
| 368 | |
| 369 | // 4. If there is a position to which the boxes in boxes can be moved while maintaining the relative |
| 370 | // positions of the boxes in boxes to each other such that none of the boxes in boxes would overlap |
| 371 | // any of the boxes in output, and all the boxes in output would be within the video's rendering area, |
| 372 | // then move the boxes in boxes to the closest such position to their current position, and then jump |
| 373 | // to the step labeled done positioning below. If there are multiple such positions that are equidistant |
| 374 | // from their current position, use the highest one amongst them; if there are several at that height, |
| 375 | // then use the leftmost one amongst them. |
| 376 | moveIfNecessaryToKeepWithinContainer(); |
| 377 | int x = 0; |
| 378 | int y = 0; |
| 379 | if (!findNonOverlappingPosition(x, y)) |
| 380 | return; |
| 381 | |
| 382 | setX(x); |
| 383 | setY(y); |
| 384 | } |
| 385 | |
| 386 | } // namespace WebCore |
| 387 | |
| 388 | #endif |
| 389 | |