blob: df1edc2851abc22b58627c7dc282c15201ede5cc [file] [log] [blame]
/*
* Copyright (C) 2012 Victor Carbune (victor@rosedu.org)
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/core/layout/layout_vtt_cue.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/core/html/media/media_controls.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/layout_state.h"
#include "third_party/blink/renderer/platform/wtf/math_extras.h"
namespace blink {
namespace {
class SnapToLinesLayouter {
STACK_ALLOCATED();
public:
SnapToLinesLayouter(LayoutVTTCue& cue_box, const IntRect& controls_rect)
: cue_box_(cue_box), controls_rect_(controls_rect), margin_(0.0) {
if (Settings* settings = cue_box_.GetDocument().GetSettings())
margin_ = settings->GetTextTrackMarginPercentage() / 100.0;
}
void UpdateLayout();
private:
bool IsOutside(const IntRect&) const;
bool IsOverlapping() const;
LayoutUnit ComputeInitialPositionAdjustment(LayoutUnit&,
LayoutUnit,
LayoutUnit) const;
bool ShouldSwitchDirection(InlineFlowBox*, LayoutUnit, LayoutUnit) const;
void MoveBoxesBy(LayoutUnit distance) {
cue_box_.SetLogicalTop(cue_box_.LogicalTop() + distance);
}
InlineFlowBox* FindFirstLineBox() const;
LayoutPoint specified_position_;
LayoutVTTCue& cue_box_;
IntRect controls_rect_;
double margin_;
};
InlineFlowBox* SnapToLinesLayouter::FindFirstLineBox() const {
if (!cue_box_.FirstChild()->IsLayoutInline())
return nullptr;
return ToLayoutInline(cue_box_.FirstChild())->FirstLineBox();
}
LayoutUnit SnapToLinesLayouter::ComputeInitialPositionAdjustment(
LayoutUnit& step,
LayoutUnit max_dimension,
LayoutUnit margin) const {
DCHECK(std::isfinite(cue_box_.SnapToLinesPosition()));
// 6. Let line be cue's computed line.
// 7. Round line to an integer by adding 0.5 and then flooring it.
LayoutUnit line_position(floorf(cue_box_.SnapToLinesPosition() + 0.5f));
WritingMode writing_mode = cue_box_.StyleRef().GetWritingMode();
// 8. Vertical Growing Left: Add one to line then negate it.
if (IsFlippedBlocksWritingMode(writing_mode))
line_position = -(line_position + 1);
// 9. Let position be the result of multiplying step and line offset.
LayoutUnit position = step * line_position;
// 10. Vertical Growing Left: Decrease position by the width of the
// bounding box of the boxes in boxes, then increase position by step.
if (IsFlippedBlocksWritingMode(writing_mode)) {
position -= cue_box_.Size().Width();
position += step;
}
// 11. If line is less than zero...
if (line_position < 0) {
// ... then increase position by max dimension ...
position += max_dimension;
// ... and negate step.
step = -step;
} else {
// ... Otherwise, increase position by margin.
position += margin;
}
return position;
}
// We use this helper to make sure all (bounding) boxes used for comparisons
// are relative to the same coordinate space. If we didn't the (bounding) boxes
// could be affect by transforms on an ancestor et.c, which could yield
// incorrect results.
IntRect ContentBoxRelativeToAncestor(const LayoutBox& box,
const LayoutBoxModelObject& ancestor) {
FloatRect cue_content_box(box.PhysicalContentBoxRect());
// We pass UseTransforms here primarily because we use a transform for
// non-snap-to-lines positioning (see VTTCue.cpp.)
FloatQuad mapped_content_quad =
box.LocalToAncestorQuad(cue_content_box, &ancestor, kUseTransforms);
return mapped_content_quad.EnclosingBoundingBox();
}
// Similar to above except uses the full bounding box instead of just the
// context box (which ignores padding). This is used for the timeline since the
// timeline is mostly padding.
IntRect PaddingBoxRelativeToAncestor(const LayoutBox& box,
const LayoutBoxModelObject& ancestor) {
FloatRect cue_content_box(box.PhysicalPaddingBoxRect());
// We pass UseTransforms here primarily because we use a transform for
// non-snap-to-lines positioning (see VTTCue.cpp.)
FloatQuad mapped_content_quad =
box.LocalToAncestorQuad(cue_content_box, &ancestor, kUseTransforms);
return mapped_content_quad.EnclosingBoundingBox();
}
IntRect CueBoundingBox(const LayoutBox& cue_box) {
return ContentBoxRelativeToAncestor(cue_box, *cue_box.ContainingBlock());
}
bool SnapToLinesLayouter::IsOutside(const IntRect& title_area) const {
return !title_area.Contains(CueBoundingBox(cue_box_));
}
bool SnapToLinesLayouter::IsOverlapping() const {
IntRect cue_box_rect = CueBoundingBox(cue_box_);
for (LayoutBox* box = cue_box_.PreviousSiblingBox(); box;
box = box->PreviousSiblingBox()) {
if (cue_box_rect.Intersects(CueBoundingBox(*box)))
return true;
}
return cue_box_rect.Intersects(controls_rect_);
}
bool SnapToLinesLayouter::ShouldSwitchDirection(InlineFlowBox* first_line_box,
LayoutUnit step,
LayoutUnit margin) const {
// 17. Horizontal: If step is negative and the top of the first line box in
// boxes is now above the top of the title area, or if step is positive and
// the bottom of the first line box in boxes is now below the bottom of the
// title area, jump to the step labeled switch direction.
// Vertical: If step is negative and the left edge of the first line
// box in boxes is now to the left of the left edge of the title area, or
// if step is positive and the right edge of the first line box in boxes is
// now to the right of the right edge of the title area, jump to the step
// labeled switch direction.
LayoutUnit logical_top = cue_box_.LogicalTop();
if (step < 0 && logical_top < margin)
return true;
if (step > 0 && logical_top + first_line_box->LogicalHeight() + margin >
cue_box_.ContainingBlock()->LogicalHeight())
return true;
return false;
}
void SnapToLinesLayouter::UpdateLayout() {
// http://dev.w3.org/html5/webvtt/#dfn-apply-webvtt-cue-settings
// Step 13, "If cue's text track cue snap-to-lines flag is set".
InlineFlowBox* first_line_box = FindFirstLineBox();
if (!first_line_box)
return;
// 1. Horizontal: Let margin be a user-agent-defined vertical length which
// will be used to define a margin at the top and bottom edges of the video
// into which cues will not be placed.
// Vertical: Let margin be a user-agent-defined horizontal length which
// will be used to define a margin at the top and bottom edges of the video
// into which cues will not be placed.
// 2. Horizontal: Let full dimension be the height of video's rendering area
// Vertical: Let full dimension be the width of video's rendering area.
WritingMode writing_mode = cue_box_.StyleRef().GetWritingMode();
LayoutBlock* parent_block = cue_box_.ContainingBlock();
LayoutUnit full_dimension = blink::IsHorizontalWritingMode(writing_mode)
? parent_block->Size().Height()
: parent_block->Size().Width();
LayoutUnit margin(full_dimension * margin_);
// 3. Let max dimension be full dimension - (2 * margin)
LayoutUnit max_dimension = full_dimension - 2 * margin;
// 4. Horizontal: Let step be the height of the first line box in boxes.
// Vertical: Let step be the width of the first line box in boxes.
LayoutUnit step = first_line_box->LogicalHeight();
// 5. If step is zero, then jump to the step labeled done positioning below.
if (!step)
return;
// Steps 6-11.
LayoutUnit position_adjustment =
ComputeInitialPositionAdjustment(step, max_dimension, margin);
// 12. Move all boxes in boxes ...
// Horizontal: ... down by the distance given by position
// Vertical: ... right by the distance given by position
MoveBoxesBy(position_adjustment);
// 13. Remember the position of all the boxes in boxes as their specified
// position.
specified_position_ = cue_box_.Location();
// XX. Let switched be false.
bool switched = false;
// 14. Horizontal: Let title area be a box that covers all of the video's
// rendering area except for a height of margin at the top of the rendering
// area and a height of margin at the bottom of the rendering area.
// Vertical: Let title area be a box that covers all of the video’s
// rendering area except for a width of margin at the left of the rendering
// area and a width of margin at the right of the rendering area.
IntRect title_area =
EnclosingIntRect(cue_box_.ContainingBlock()->PhysicalContentBoxRect());
if (blink::IsHorizontalWritingMode(writing_mode)) {
title_area.Move(0, margin.ToInt());
title_area.Contract(0, (2 * margin).ToInt());
} else {
title_area.Move(margin.ToInt(), 0);
title_area.Contract((2 * margin).ToInt(), 0);
}
// 15. Step loop: If none of the boxes in boxes would overlap any of the
// boxes in output, and all of the boxes in output are entirely within the
// title area box, then jump to the step labeled done positioning below.
while (IsOutside(title_area) || IsOverlapping()) {
// 16. Let current position score be the percentage of the area of the
// bounding box of the boxes in boxes that is outside the title area
// box.
if (!ShouldSwitchDirection(first_line_box, step, margin)) {
// 18. Horizontal: Move all the boxes in boxes down by the distance
// given by step. (If step is negative, then this will actually
// result in an upwards movement of the boxes in absolute terms.)
// Vertical: Move all the boxes in boxes right by the distance
// given by step. (If step is negative, then this will actually
// result in a leftwards movement of the boxes in absolute terms.)
MoveBoxesBy(step);
// 19. Jump back to the step labeled step loop.
continue;
}
// 20. Switch direction: If switched is true, then remove all the boxes
// in boxes, and jump to the step labeled done positioning below.
if (switched) {
// This does not "remove" the boxes, but rather just pushes them
// out of the viewport. Otherwise we'd need to mutate the layout
// tree during layout.
cue_box_.SetLogicalTop(cue_box_.ContainingBlock()->LogicalHeight() + 1);
break;
}
// 21. Otherwise, move all the boxes in boxes back to their specified
// position as determined in the earlier step.
cue_box_.SetLocation(specified_position_);
// 22. Negate step.
step = -step;
// 23. Set switched to true.
switched = true;
// 24. Jump back to the step labeled step loop.
}
}
} // unnamed namespace
LayoutVTTCue::LayoutVTTCue(ContainerNode* node, float snap_to_lines_position)
: LayoutBlockFlow(node), snap_to_lines_position_(snap_to_lines_position) {}
void LayoutVTTCue::RepositionCueSnapToLinesNotSet() {
// FIXME: Implement overlapping detection when snap-to-lines is not set.
// http://wkb.ug/84296
// http://dev.w3.org/html5/webvtt/#dfn-apply-webvtt-cue-settings
// Step 13, "If cue's text track cue snap-to-lines flag is not set".
// 1. Let bounding box be the bounding box of the boxes in boxes.
// 2. Run the appropriate steps from the following list:
// If the text track cue writing direction is horizontal
// If the text track cue line alignment is middle alignment
// Move all the boxes in boxes up by half of the height of
// bounding box.
// If the text track cue line alignment is end alignment
// Move all the boxes in boxes up by the height of bounding box.
//
// If the text track cue writing direction is vertical growing left or
// vertical growing right
// If the text track cue line alignment is middle alignment
// Move all the boxes in boxes left by half of the width of
// bounding box.
// If the text track cue line alignment is end alignment
// Move all the boxes in boxes left by the width of bounding box.
// 3. If none of the boxes in boxes would overlap any of the boxes in
// output, and all the boxes in output are within the video's rendering
// area, then jump to the step labeled done positioning below.
// 4. If there is a position to which the boxes in boxes can be moved while
// maintaining the relative positions of the boxes in boxes to each other
// such that none of the boxes in boxes would overlap any of the boxes in
// output, and all the boxes in output would be within the video's
// rendering area, then move the boxes in boxes to the closest such
// position to their current position, and then jump to the step labeled
// done positioning below. If there are multiple such positions that are
// equidistant from their current position, use the highest one amongst
// them; if there are several at that height, then use the leftmost one
// amongst them.
// 5. Otherwise, jump to the step labeled done positioning below. (The
// boxes will unfortunately overlap.)
}
IntRect LayoutVTTCue::ComputeControlsRect() const {
// Determine the area covered by the media controls, if any. For this, the
// LayoutVTTCue will walk the tree up to the HTMLMediaElement, then ask for
// the MediaControls.
DCHECK(Parent()->GetNode()->IsTextTrackContainer());
HTMLMediaElement* media_element =
ToHTMLMediaElement(Parent()->Parent()->GetNode());
DCHECK(media_element);
MediaControls* controls = media_element->GetMediaControls();
if (!controls || !controls->ContainerLayoutObject())
return IntRect();
if (RuntimeEnabledFeatures::ModernMediaControlsEnabled()) {
// Only a part of the media controls is used for overlap avoidance.
// For modern media controls, we avoid both the button panel and the
// timeline.
LayoutObject* button_panel_layout_object =
media_element->GetMediaControls()->ButtonPanelLayoutObject();
LayoutObject* timeline_layout_object =
media_element->GetMediaControls()->TimelineLayoutObject();
if (!button_panel_layout_object || !button_panel_layout_object->IsBox() ||
!timeline_layout_object || !timeline_layout_object->IsBox()) {
return IntRect();
}
IntRect button_panel_box = ContentBoxRelativeToAncestor(
ToLayoutBox(*button_panel_layout_object),
ToLayoutBox(*controls->ContainerLayoutObject()));
IntRect timeline_box = PaddingBoxRelativeToAncestor(
ToLayoutBox(*timeline_layout_object),
ToLayoutBox(*controls->ContainerLayoutObject()));
button_panel_box.Unite(timeline_box);
return button_panel_box;
}
// Only a part of the media controls is used for overlap avoidance.
LayoutObject* panel_layout_object =
media_element->GetMediaControls()->PanelLayoutObject();
// The (second part of the) following is mostly defensive - in general
// there should be a LayoutBox representing the part of the controls that
// are relevant for overlap avoidance. (The controls pseudo elements are
// generally reachable from outside the shadow tree though, hence the
// "mostly".)
if (!panel_layout_object || !panel_layout_object->IsBox())
return IntRect();
// Assume that the controls container are positioned in the same relative
// position as the text track container. (LayoutMedia::layout ensures this.)
return ContentBoxRelativeToAncestor(
ToLayoutBox(*panel_layout_object),
ToLayoutBox(*controls->ContainerLayoutObject()));
}
void LayoutVTTCue::UpdateLayout() {
LayoutBlockFlow::UpdateLayout();
DCHECK(FirstChild());
LayoutState state(*this);
// http://dev.w3.org/html5/webvtt/#dfn-apply-webvtt-cue-settings - step 13.
if (!std::isnan(snap_to_lines_position_))
SnapToLinesLayouter(*this, ComputeControlsRect()).UpdateLayout();
else
RepositionCueSnapToLinesNotSet();
}
} // namespace blink