| /* |
| * 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/web_feature.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/core/paint/rounded_border_geometry.h" |
| #include "third_party/blink/renderer/platform/geometry/length_functions.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/instrumentation/use_counter.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) { |
| 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()); |
| } |
| |
| const ShapeValue& shape_value = *layout_box_->StyleRef().ShapeOutside(); |
| switch (ReferenceBox(shape_value)) { |
| 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: { |
| bool is_shape_image = shape_value.GetType() == ShapeValue::kImage; |
| |
| if (!is_shape_image) |
| 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 (!is_shape_image && |
| 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_content = *(style_image.CachedImage()); |
| if (image_content.IsAccessAllowed()) |
| return true; |
| |
| const KURL& url = image_content.Url(); |
| String url_string = url.IsNull() ? "''" : url.ElidedString(); |
| document.AddConsoleMessage(MakeGarbageCollected<ConsoleMessage>( |
| mojom::ConsoleMessageSource::kSecurity, |
| mojom::ConsoleMessageLevel::kError, |
| "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()); |
| |
| RespectImageOrientationEnum respect_orientation = |
| style_image->ForceOrientationIfNecessary( |
| LayoutObject::ShouldRespectImageOrientation(layout_box_)); |
| |
| const LayoutSize& image_size = RoundedLayoutSize(style_image->ImageSize( |
| layout_box_->GetDocument(), layout_box_->StyleRef().EffectiveZoom(), |
| FloatSize(reference_box_logical_size_), respect_orientation)); |
| |
| const LayoutRect& margin_rect = |
| GetShapeImageMarginRect(*layout_box_, reference_box_logical_size_); |
| const LayoutRect& image_rect = |
| (layout_box_->IsLayoutImage()) |
| ? To<LayoutImage>(layout_box_)->ReplacedContentRect().ToLayoutRect() |
| : 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, |
| respect_orientation); |
| } |
| |
| 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.GetImage()); |
| DCHECK(shape_value.GetImage()->CanRender()); |
| shape_ = CreateShapeForImage(shape_value.GetImage(), |
| shape_image_threshold, writing_mode, margin); |
| break; |
| case ShapeValue::kBox: { |
| // TODO(layout-dev): It seems incorrect to pass logical size to |
| // RoundedBorderGeometry(). |
| const FloatRoundedRect& shape_rect = RoundedBorderGeometry::RoundedBorder( |
| style, PhysicalRect(PhysicalOffset(), 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: { |
| StyleImage* image = shape_value->GetImage(); |
| DCHECK(image); |
| return image->CanRender() && |
| CheckShapeImageOrigin(box.GetDocument(), *image); |
| } |
| 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_; |
| } |
| |
| PhysicalRect 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 PhysicalRect(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; |
| } |
| |
| } // namespace blink |