Combine event and touch action regions into a single class
[WebKit-https.git] / Source / WebCore / rendering / RenderVTTCue.cpp
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