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 | |