| /* |
| * Copyright (C) 2012 Adobe Systems Incorporated. All rights reserved. |
| * |
| * 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 THE COPYRIGHT HOLDER "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 THE COPYRIGHT HOLDER 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/shapes/shape_outside_info.h" |
| |
| #include <memory> |
| #include "base/auto_reset.h" |
| #include "third_party/blink/renderer/core/frame/use_counter.h" |
| #include "third_party/blink/renderer/core/inspector/console_message.h" |
| #include "third_party/blink/renderer/core/layout/api/line_layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/floating_objects.h" |
| #include "third_party/blink/renderer/core/layout/layout_block_flow.h" |
| #include "third_party/blink/renderer/core/layout/layout_box.h" |
| #include "third_party/blink/renderer/core/layout/layout_image.h" |
| #include "third_party/blink/renderer/platform/length_functions.h" |
| |
| namespace blink { |
| |
| CSSBoxType ReferenceBox(const ShapeValue& shape_value) { |
| if (shape_value.CssBox() == CSSBoxType::kMissing) |
| return CSSBoxType::kMargin; |
| return shape_value.CssBox(); |
| } |
| |
| void ShapeOutsideInfo::SetReferenceBoxLogicalSize( |
| LayoutSize new_reference_box_logical_size) { |
| const Document& document = layout_box_.GetDocument(); |
| bool is_horizontal_writing_mode = |
| layout_box_.ContainingBlock()->StyleRef().IsHorizontalWritingMode(); |
| |
| LayoutSize margin_box_for_use_counter = new_reference_box_logical_size; |
| if (is_horizontal_writing_mode) { |
| margin_box_for_use_counter.Expand(layout_box_.MarginWidth(), |
| layout_box_.MarginHeight()); |
| } else { |
| margin_box_for_use_counter.Expand(layout_box_.MarginHeight(), |
| layout_box_.MarginWidth()); |
| } |
| |
| switch (ReferenceBox(*layout_box_.StyleRef().ShapeOutside())) { |
| case CSSBoxType::kMargin: |
| UseCounter::Count(document, WebFeature::kShapeOutsideMarginBox); |
| if (is_horizontal_writing_mode) |
| new_reference_box_logical_size.Expand(layout_box_.MarginWidth(), |
| layout_box_.MarginHeight()); |
| else |
| new_reference_box_logical_size.Expand(layout_box_.MarginHeight(), |
| layout_box_.MarginWidth()); |
| break; |
| case CSSBoxType::kBorder: |
| UseCounter::Count(document, WebFeature::kShapeOutsideBorderBox); |
| break; |
| case CSSBoxType::kPadding: |
| UseCounter::Count(document, WebFeature::kShapeOutsidePaddingBox); |
| if (is_horizontal_writing_mode) |
| new_reference_box_logical_size.Shrink(layout_box_.BorderWidth(), |
| layout_box_.BorderHeight()); |
| else |
| new_reference_box_logical_size.Shrink(layout_box_.BorderHeight(), |
| layout_box_.BorderWidth()); |
| |
| if (new_reference_box_logical_size != margin_box_for_use_counter) { |
| UseCounter::Count( |
| document, |
| WebFeature::kShapeOutsidePaddingBoxDifferentFromMarginBox); |
| } |
| break; |
| case CSSBoxType::kContent: |
| UseCounter::Count(document, WebFeature::kShapeOutsideContentBox); |
| if (is_horizontal_writing_mode) |
| new_reference_box_logical_size.Shrink( |
| layout_box_.BorderAndPaddingWidth(), |
| layout_box_.BorderAndPaddingHeight()); |
| else |
| new_reference_box_logical_size.Shrink( |
| layout_box_.BorderAndPaddingHeight(), |
| layout_box_.BorderAndPaddingWidth()); |
| |
| if (new_reference_box_logical_size != margin_box_for_use_counter) { |
| UseCounter::Count( |
| document, |
| WebFeature::kShapeOutsideContentBoxDifferentFromMarginBox); |
| } |
| break; |
| case CSSBoxType::kMissing: |
| NOTREACHED(); |
| break; |
| } |
| |
| new_reference_box_logical_size.ClampNegativeToZero(); |
| |
| if (reference_box_logical_size_ == new_reference_box_logical_size) |
| return; |
| MarkShapeAsDirty(); |
| reference_box_logical_size_ = new_reference_box_logical_size; |
| } |
| |
| void ShapeOutsideInfo::SetPercentageResolutionInlineSize( |
| LayoutUnit percentage_resolution_inline_size) { |
| DCHECK(RuntimeEnabledFeatures::LayoutNGEnabled()); |
| |
| if (percentage_resolution_inline_size_ == percentage_resolution_inline_size) |
| return; |
| |
| MarkShapeAsDirty(); |
| percentage_resolution_inline_size_ = percentage_resolution_inline_size; |
| } |
| |
| static bool CheckShapeImageOrigin(Document& document, |
| const StyleImage& style_image) { |
| if (style_image.IsGeneratedImage()) |
| return true; |
| |
| DCHECK(style_image.CachedImage()); |
| ImageResourceContent& image_resource = *(style_image.CachedImage()); |
| if (image_resource.IsAccessAllowed(document.GetSecurityOrigin())) |
| return true; |
| |
| const KURL& url = image_resource.Url(); |
| String url_string = url.IsNull() ? "''" : url.ElidedString(); |
| document.AddConsoleMessage( |
| ConsoleMessage::Create(kSecurityMessageSource, kErrorMessageLevel, |
| "Unsafe attempt to load URL " + url_string + ".")); |
| |
| return false; |
| } |
| |
| static LayoutRect GetShapeImageMarginRect( |
| const LayoutBox& layout_box, |
| const LayoutSize& reference_box_logical_size) { |
| LayoutPoint margin_box_origin( |
| -layout_box.MarginLineLeft() - layout_box.BorderAndPaddingLogicalLeft(), |
| -layout_box.MarginBefore() - layout_box.BorderBefore() - |
| layout_box.PaddingBefore()); |
| LayoutSize margin_box_size_delta( |
| layout_box.MarginLogicalWidth() + |
| layout_box.BorderAndPaddingLogicalWidth(), |
| layout_box.MarginLogicalHeight() + |
| layout_box.BorderAndPaddingLogicalHeight()); |
| LayoutSize margin_rect_size(reference_box_logical_size + |
| margin_box_size_delta); |
| margin_rect_size.ClampNegativeToZero(); |
| return LayoutRect(margin_box_origin, margin_rect_size); |
| } |
| |
| std::unique_ptr<Shape> ShapeOutsideInfo::CreateShapeForImage( |
| StyleImage* style_image, |
| float shape_image_threshold, |
| WritingMode writing_mode, |
| float margin) const { |
| DCHECK(!style_image->IsPendingImage()); |
| const LayoutSize& image_size = RoundedLayoutSize(style_image->ImageSize( |
| layout_box_.GetDocument(), layout_box_.StyleRef().EffectiveZoom(), |
| reference_box_logical_size_)); |
| |
| const LayoutRect& margin_rect = |
| GetShapeImageMarginRect(layout_box_, reference_box_logical_size_); |
| const LayoutRect& image_rect = |
| (layout_box_.IsLayoutImage()) |
| ? ToLayoutImage(layout_box_).ReplacedContentRect() |
| : LayoutRect(LayoutPoint(), image_size); |
| |
| scoped_refptr<Image> image = |
| style_image->GetImage(layout_box_, layout_box_.GetDocument(), |
| layout_box_.StyleRef(), FloatSize(image_size)); |
| |
| return Shape::CreateRasterShape(image.get(), shape_image_threshold, |
| image_rect, margin_rect, writing_mode, |
| margin); |
| } |
| |
| const Shape& ShapeOutsideInfo::ComputedShape() const { |
| if (Shape* shape = shape_.get()) |
| return *shape; |
| |
| base::AutoReset<bool> is_in_computing_shape(&is_computing_shape_, true); |
| |
| const ComputedStyle& style = *layout_box_.Style(); |
| DCHECK(layout_box_.ContainingBlock()); |
| const LayoutBlock& containing_block = *layout_box_.ContainingBlock(); |
| const ComputedStyle& containing_block_style = containing_block.StyleRef(); |
| |
| WritingMode writing_mode = containing_block_style.GetWritingMode(); |
| // Make sure contentWidth is not negative. This can happen when containing |
| // block has a vertical scrollbar and its content is smaller than the |
| // scrollbar width. |
| LayoutUnit percentage_resolution_inline_size = |
| containing_block.IsLayoutNGMixin() |
| ? percentage_resolution_inline_size_ |
| : std::max(LayoutUnit(), containing_block.ContentWidth()); |
| |
| float margin = |
| FloatValueForLength(layout_box_.StyleRef().ShapeMargin(), |
| percentage_resolution_inline_size.ToFloat()); |
| |
| float shape_image_threshold = style.ShapeImageThreshold(); |
| DCHECK(style.ShapeOutside()); |
| const ShapeValue& shape_value = *style.ShapeOutside(); |
| |
| switch (shape_value.GetType()) { |
| case ShapeValue::kShape: |
| DCHECK(shape_value.Shape()); |
| shape_ = |
| Shape::CreateShape(shape_value.Shape(), reference_box_logical_size_, |
| writing_mode, margin); |
| break; |
| case ShapeValue::kImage: |
| DCHECK(shape_value.IsImageValid()); |
| shape_ = CreateShapeForImage(shape_value.GetImage(), |
| shape_image_threshold, writing_mode, margin); |
| break; |
| case ShapeValue::kBox: { |
| const FloatRoundedRect& shape_rect = style.GetRoundedBorderFor( |
| LayoutRect(LayoutPoint(), reference_box_logical_size_)); |
| shape_ = Shape::CreateLayoutBoxShape(shape_rect, writing_mode, margin); |
| break; |
| } |
| } |
| |
| DCHECK(shape_); |
| return *shape_; |
| } |
| |
| inline LayoutUnit BorderBeforeInWritingMode(const LayoutBox& layout_box, |
| WritingMode writing_mode) { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return LayoutUnit(layout_box.BorderTop()); |
| case WritingMode::kVerticalLr: |
| return LayoutUnit(layout_box.BorderLeft()); |
| case WritingMode::kVerticalRl: |
| return LayoutUnit(layout_box.BorderRight()); |
| // TODO(layout-dev): Sideways-lr and sideways-rl are not yet supported. |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| return LayoutUnit(layout_box.BorderBefore()); |
| } |
| |
| inline LayoutUnit BorderAndPaddingBeforeInWritingMode( |
| const LayoutBox& layout_box, |
| WritingMode writing_mode) { |
| switch (writing_mode) { |
| case WritingMode::kHorizontalTb: |
| return layout_box.BorderTop() + layout_box.PaddingTop(); |
| case WritingMode::kVerticalLr: |
| return layout_box.BorderLeft() + layout_box.PaddingLeft(); |
| case WritingMode::kVerticalRl: |
| return layout_box.BorderRight() + layout_box.PaddingRight(); |
| // TODO(layout-dev): Sideways-lr and sideways-rl are not yet supported. |
| default: |
| break; |
| } |
| |
| NOTREACHED(); |
| return layout_box.BorderAndPaddingBefore(); |
| } |
| |
| LayoutUnit ShapeOutsideInfo::LogicalTopOffset() const { |
| switch (ReferenceBox(*layout_box_.StyleRef().ShapeOutside())) { |
| case CSSBoxType::kMargin: |
| return -layout_box_.MarginBefore(layout_box_.ContainingBlock()->Style()); |
| case CSSBoxType::kBorder: |
| return LayoutUnit(); |
| case CSSBoxType::kPadding: |
| return BorderBeforeInWritingMode( |
| layout_box_, |
| layout_box_.ContainingBlock()->StyleRef().GetWritingMode()); |
| case CSSBoxType::kContent: |
| return BorderAndPaddingBeforeInWritingMode( |
| layout_box_, |
| layout_box_.ContainingBlock()->StyleRef().GetWritingMode()); |
| case CSSBoxType::kMissing: |
| break; |
| } |
| |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| inline LayoutUnit BorderStartWithStyleForWritingMode( |
| const LayoutBox& layout_box, |
| const ComputedStyle* style) { |
| if (style->IsHorizontalWritingMode()) { |
| if (style->IsLeftToRightDirection()) |
| return LayoutUnit(layout_box.BorderLeft()); |
| |
| return LayoutUnit(layout_box.BorderRight()); |
| } |
| if (style->IsLeftToRightDirection()) |
| return LayoutUnit(layout_box.BorderTop()); |
| |
| return LayoutUnit(layout_box.BorderBottom()); |
| } |
| |
| inline LayoutUnit BorderAndPaddingStartWithStyleForWritingMode( |
| const LayoutBox& layout_box, |
| const ComputedStyle* style) { |
| if (style->IsHorizontalWritingMode()) { |
| if (style->IsLeftToRightDirection()) |
| return layout_box.BorderLeft() + layout_box.PaddingLeft(); |
| |
| return layout_box.BorderRight() + layout_box.PaddingRight(); |
| } |
| if (style->IsLeftToRightDirection()) |
| return layout_box.BorderTop() + layout_box.PaddingTop(); |
| |
| return layout_box.BorderBottom() + layout_box.PaddingBottom(); |
| } |
| |
| LayoutUnit ShapeOutsideInfo::LogicalLeftOffset() const { |
| switch (ReferenceBox(*layout_box_.StyleRef().ShapeOutside())) { |
| case CSSBoxType::kMargin: |
| return -layout_box_.MarginStart(layout_box_.ContainingBlock()->Style()); |
| case CSSBoxType::kBorder: |
| return LayoutUnit(); |
| case CSSBoxType::kPadding: |
| return BorderStartWithStyleForWritingMode( |
| layout_box_, layout_box_.ContainingBlock()->Style()); |
| case CSSBoxType::kContent: |
| return BorderAndPaddingStartWithStyleForWritingMode( |
| layout_box_, layout_box_.ContainingBlock()->Style()); |
| case CSSBoxType::kMissing: |
| break; |
| } |
| |
| NOTREACHED(); |
| return LayoutUnit(); |
| } |
| |
| bool ShapeOutsideInfo::IsEnabledFor(const LayoutBox& box) { |
| ShapeValue* shape_value = box.StyleRef().ShapeOutside(); |
| if (!box.IsFloating() || !shape_value) |
| return false; |
| |
| switch (shape_value->GetType()) { |
| case ShapeValue::kShape: |
| return shape_value->Shape(); |
| case ShapeValue::kImage: |
| return shape_value->IsImageValid() && |
| CheckShapeImageOrigin(box.GetDocument(), |
| *(shape_value->GetImage())); |
| case ShapeValue::kBox: |
| return true; |
| } |
| |
| return false; |
| } |
| |
| ShapeOutsideDeltas ShapeOutsideInfo::ComputeDeltasForContainingBlockLine( |
| const LineLayoutBlockFlow& containing_block, |
| const FloatingObject& floating_object, |
| LayoutUnit line_top, |
| LayoutUnit line_height) { |
| DCHECK_GE(line_height, 0); |
| |
| LayoutUnit border_box_top = |
| containing_block.LogicalTopForFloat(floating_object) + |
| containing_block.MarginBeforeForChild(layout_box_); |
| LayoutUnit border_box_line_top = line_top - border_box_top; |
| |
| if (IsShapeDirty() || |
| !shape_outside_deltas_.IsForLine(border_box_line_top, line_height)) { |
| LayoutUnit reference_box_line_top = |
| border_box_line_top - LogicalTopOffset(); |
| LayoutUnit float_margin_box_width = std::max( |
| containing_block.LogicalWidthForFloat(floating_object), LayoutUnit()); |
| |
| if (ComputedShape().LineOverlapsShapeMarginBounds(reference_box_line_top, |
| line_height)) { |
| LineSegment segment = ComputedShape().GetExcludedInterval( |
| (border_box_line_top - LogicalTopOffset()), |
| std::min(line_height, ShapeLogicalBottom() - border_box_line_top)); |
| if (segment.is_valid) { |
| LayoutUnit logical_left_margin = |
| containing_block.StyleRef().IsLeftToRightDirection() |
| ? containing_block.MarginStartForChild(layout_box_) |
| : containing_block.MarginEndForChild(layout_box_); |
| LayoutUnit raw_left_margin_box_delta = |
| segment.logical_left + LogicalLeftOffset() + logical_left_margin; |
| LayoutUnit left_margin_box_delta = clampTo<LayoutUnit>( |
| raw_left_margin_box_delta, LayoutUnit(), float_margin_box_width); |
| |
| LayoutUnit logical_right_margin = |
| containing_block.StyleRef().IsLeftToRightDirection() |
| ? containing_block.MarginEndForChild(layout_box_) |
| : containing_block.MarginStartForChild(layout_box_); |
| LayoutUnit raw_right_margin_box_delta = |
| segment.logical_right + LogicalLeftOffset() - |
| containing_block.LogicalWidthForChild(layout_box_) - |
| logical_right_margin; |
| LayoutUnit right_margin_box_delta = clampTo<LayoutUnit>( |
| raw_right_margin_box_delta, -float_margin_box_width, LayoutUnit()); |
| |
| shape_outside_deltas_ = |
| ShapeOutsideDeltas(left_margin_box_delta, right_margin_box_delta, |
| true, border_box_line_top, line_height); |
| return shape_outside_deltas_; |
| } |
| } |
| |
| // Lines that do not overlap the shape should act as if the float |
| // wasn't there for layout purposes. So we set the deltas to remove the |
| // entire width of the float. |
| shape_outside_deltas_ = |
| ShapeOutsideDeltas(float_margin_box_width, -float_margin_box_width, |
| false, border_box_line_top, line_height); |
| } |
| |
| return shape_outside_deltas_; |
| } |
| |
| LayoutRect ShapeOutsideInfo::ComputedShapePhysicalBoundingBox() const { |
| LayoutRect physical_bounding_box = |
| ComputedShape().ShapeMarginLogicalBoundingBox(); |
| physical_bounding_box.SetX(physical_bounding_box.X() + LogicalLeftOffset()); |
| |
| if (layout_box_.StyleRef().IsFlippedBlocksWritingMode()) |
| physical_bounding_box.SetY(layout_box_.LogicalHeight() - |
| physical_bounding_box.MaxY()); |
| else |
| physical_bounding_box.SetY(physical_bounding_box.Y() + LogicalTopOffset()); |
| |
| if (!layout_box_.StyleRef().IsHorizontalWritingMode()) |
| physical_bounding_box = physical_bounding_box.TransposedRect(); |
| else |
| physical_bounding_box.SetY(physical_bounding_box.Y() + LogicalTopOffset()); |
| |
| return physical_bounding_box; |
| } |
| |
| FloatPoint ShapeOutsideInfo::ShapeToLayoutObjectPoint(FloatPoint point) const { |
| FloatPoint result = FloatPoint(point.X() + LogicalLeftOffset(), |
| point.Y() + LogicalTopOffset()); |
| if (layout_box_.StyleRef().IsFlippedBlocksWritingMode()) |
| result.SetY(layout_box_.LogicalHeight() - result.Y()); |
| if (!layout_box_.StyleRef().IsHorizontalWritingMode()) |
| result = result.TransposedPoint(); |
| return result; |
| } |
| |
| FloatSize ShapeOutsideInfo::ShapeToLayoutObjectSize(FloatSize size) const { |
| if (!layout_box_.StyleRef().IsHorizontalWritingMode()) |
| return size.TransposedSize(); |
| return size; |
| } |
| |
| } // namespace blink |