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
40namespace WebCore {
41
42WTF_MAKE_ISO_ALLOCATED_IMPL(RenderVTTCue);
43
44RenderVTTCue::RenderVTTCue(VTTCueBox& element, RenderStyle&& style)
45 : RenderBlockFlow(element, WTFMove(style))
46 , m_cue(element.getCue())
47{
48}
49
50void 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
73bool 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
128void 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
146bool RenderVTTCue::isOutside() const
147{
148 return !rectIsWithinContainer(absoluteContentBox());
149}
150
151bool RenderVTTCue::rectIsWithinContainer(const IntRect& rect) const
152{
153 return containingBlock()->absoluteBoundingBoxRect().contains(rect);
154}
155
156
157bool RenderVTTCue::isOverlapping() const
158{
159 return overlappingObject();
160}
161
162RenderObject* RenderVTTCue::overlappingObject() const
163{
164 return overlappingObjectForRect(absoluteBoundingBoxRect());
165}
166
167RenderObject* 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
179bool 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
207void 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
222bool 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
242void 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
272bool 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
312void 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
343void 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
362void 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