| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "third_party/blink/renderer/core/layout/ng/ng_relative_utils.h" |
| |
| #include "base/optional.h" |
| #include "third_party/blink/renderer/core/layout/geometry/physical_offset.h" |
| #include "third_party/blink/renderer/core/layout/geometry/physical_size.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/core/style/computed_style.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| |
| namespace blink { |
| |
| LogicalOffset ComputeRelativeOffset( |
| const ComputedStyle& child_style, |
| WritingDirectionMode container_writing_direction, |
| const LogicalSize& available_size) { |
| if (child_style.GetPosition() != EPosition::kRelative) |
| return LogicalOffset(); |
| |
| const PhysicalSize physical_size = ToPhysicalSize( |
| available_size, container_writing_direction.GetWritingMode()); |
| |
| // Helper function to correctly resolve insets. |
| auto ResolveInset = [](const Length& length, |
| LayoutUnit size) -> base::Optional<LayoutUnit> { |
| if (length.IsAuto()) |
| return base::nullopt; |
| if (length.IsPercentOrCalc() && size == kIndefiniteSize) |
| return base::nullopt; |
| return MinimumValueForLength(length, size); |
| }; |
| |
| base::Optional<LayoutUnit> left = |
| ResolveInset(child_style.Left(), physical_size.width); |
| base::Optional<LayoutUnit> right = |
| ResolveInset(child_style.Right(), physical_size.width); |
| base::Optional<LayoutUnit> top = |
| ResolveInset(child_style.Top(), physical_size.height); |
| base::Optional<LayoutUnit> bottom = |
| ResolveInset(child_style.Bottom(), physical_size.height); |
| |
| // Common case optimization. |
| if (!left && !right && !top && !bottom) |
| return LogicalOffset(); |
| |
| // Conflict resolution rules: https://www.w3.org/TR/css-position-3/#rel-pos |
| if (!left && !right) { |
| left = LayoutUnit(); |
| right = LayoutUnit(); |
| } else if (!left) { |
| left = -*right; |
| } else if (!right) { |
| right = -*left; |
| } |
| |
| if (!top && !bottom) { |
| top = LayoutUnit(); |
| bottom = LayoutUnit(); |
| } else if (!top) { |
| top = -*bottom; |
| } else if (!bottom) { |
| bottom = -*top; |
| } |
| |
| switch (container_writing_direction.GetWritingMode()) { |
| case WritingMode::kHorizontalTb: |
| return container_writing_direction.IsLtr() ? LogicalOffset(*left, *top) |
| : LogicalOffset(*right, *top); |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| return container_writing_direction.IsLtr() |
| ? LogicalOffset(*top, *right) |
| : LogicalOffset(*bottom, *right); |
| case WritingMode::kVerticalLr: |
| return container_writing_direction.IsLtr() |
| ? LogicalOffset(*top, *left) |
| : LogicalOffset(*bottom, *left); |
| case WritingMode::kSidewaysLr: |
| return container_writing_direction.IsLtr() ? LogicalOffset(*bottom, *left) |
| : LogicalOffset(*top, *left); |
| default: |
| NOTREACHED(); |
| return LogicalOffset(); |
| } |
| } |
| |
| LogicalOffset ComputeRelativeOffsetForBoxFragment( |
| const NGPhysicalBoxFragment& fragment, |
| WritingDirectionMode container_writing_direction, |
| const LogicalSize& available_size) { |
| const auto& child_style = fragment.Style(); |
| DCHECK_EQ(child_style.GetPosition(), EPosition::kRelative); |
| |
| LogicalOffset relative_offset = ComputeRelativeOffset( |
| child_style, container_writing_direction, available_size); |
| |
| const auto* block_flow = |
| DynamicTo<LayoutBlockFlow>(fragment.GetLayoutObject()); |
| if (!block_flow) |
| return relative_offset; |
| |
| // We may be within a split-inline. This isn't ideal, but we need to walk up |
| // our inline ancestor chain applying any relative offsets. |
| for (const LayoutObject* layout_object = |
| block_flow->InlineElementContinuation(); |
| layout_object && layout_object->IsLayoutInline(); |
| layout_object = layout_object->Parent()) { |
| relative_offset += ComputeRelativeOffset( |
| layout_object->StyleRef(), container_writing_direction, available_size); |
| } |
| |
| return relative_offset; |
| } |
| |
| LogicalOffset ComputeRelativeOffsetForInline(const NGConstraintSpace& space, |
| const ComputedStyle& child_style) { |
| if (child_style.GetPosition() != EPosition::kRelative) |
| return LogicalOffset(); |
| |
| // The confliction resolution rules work based off the block's writing-mode |
| // and direction, not the child's container. E.g. |
| // <span style="direction: rtl;"> |
| // <span style="position: relative; left: 100px; right: -50px;"></span> |
| // </span> |
| // In the above example "left" wins. |
| const WritingDirectionMode writing_direction = space.GetWritingDirection(); |
| LogicalOffset relative_offset = ComputeRelativeOffset( |
| child_style, writing_direction, space.AvailableSize()); |
| |
| // Lines are built in a line-logical coordinate system: |
| // https://drafts.csswg.org/css-writing-modes-3/#line-directions |
| // Reverse the offset direction if we are in a RTL, or flipped writing-mode. |
| if (writing_direction.IsRtl()) |
| relative_offset.inline_offset = -relative_offset.inline_offset; |
| if (writing_direction.IsFlippedLines()) |
| relative_offset.block_offset = -relative_offset.block_offset; |
| |
| return relative_offset; |
| } |
| |
| } // namespace blink |