blob: aeba6f798ef7e4d0a277f3ce3b3342fe220c0855 [file] [log] [blame]
// Copyright 2016 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/paint/clip_path_clipper.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_inline.h"
#include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h"
#include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
#include "third_party/blink/renderer/core/paint/paint_info.h"
#include "third_party/blink/renderer/core/paint/paint_layer.h"
#include "third_party/blink/renderer/core/style/clip_path_operation.h"
#include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
#include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
#include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
#include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h"
namespace blink {
namespace {
SVGResourceClient* GetResourceClient(const LayoutObject& object) {
if (object.IsSVGChild())
return SVGResources::GetClient(object);
CHECK(object.IsBoxModelObject());
return To<LayoutBoxModelObject>(object).Layer()->ResourceInfo();
}
LayoutSVGResourceClipper* ResolveElementReference(
const LayoutObject& object,
const ReferenceClipPathOperation& reference_clip_path_operation) {
SVGResourceClient* client = GetResourceClient(object);
// We may not have a resource client for some non-rendered elements (like
// filter primitives) that we visit during paint property tree construction.
if (!client)
return nullptr;
LayoutSVGResourceClipper* resource_clipper =
GetSVGResourceAsType(*client, reference_clip_path_operation);
if (resource_clipper) {
SECURITY_DCHECK(!resource_clipper->NeedsLayout());
resource_clipper->ClearInvalidationMask();
}
return resource_clipper;
}
} // namespace
// Is the reference box (as returned by LocalReferenceBox) for |clip_path_owner|
// zoomed with EffectiveZoom()?
static bool UsesZoomedReferenceBox(const LayoutObject& clip_path_owner) {
return !clip_path_owner.IsSVGChild() || clip_path_owner.IsSVGForeignObject();
}
FloatRect ClipPathClipper::LocalReferenceBox(const LayoutObject& object) {
if (object.IsSVGChild())
return SVGResources::ReferenceBoxForEffects(object);
if (object.IsBox())
return FloatRect(To<LayoutBox>(object).BorderBoxRect());
return FloatRect(To<LayoutInline>(object).ReferenceBoxForClipPath());
}
base::Optional<FloatRect> ClipPathClipper::LocalClipPathBoundingBox(
const LayoutObject& object) {
if (object.IsText() || !object.StyleRef().HasClipPath())
return base::nullopt;
FloatRect reference_box = LocalReferenceBox(object);
ClipPathOperation& clip_path = *object.StyleRef().ClipPath();
if (clip_path.GetType() == ClipPathOperation::SHAPE) {
auto zoom =
UsesZoomedReferenceBox(object) ? object.StyleRef().EffectiveZoom() : 1;
auto& shape = To<ShapeClipPathOperation>(clip_path);
FloatRect bounding_box = shape.GetPath(reference_box, zoom).BoundingRect();
bounding_box.Intersect(LayoutRect::InfiniteIntRect());
return bounding_box;
}
DCHECK_EQ(clip_path.GetType(), ClipPathOperation::REFERENCE);
LayoutSVGResourceClipper* clipper = ResolveElementReference(
object, To<ReferenceClipPathOperation>(clip_path));
if (!clipper)
return base::nullopt;
FloatRect bounding_box = clipper->ResourceBoundingBox(reference_box);
if (UsesZoomedReferenceBox(object) &&
clipper->ClipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) {
bounding_box.Scale(clipper->StyleRef().EffectiveZoom());
// With kSvgUnitTypeUserspaceonuse, the clip path layout is relative to
// the current transform space, and the reference box is unused.
// While SVG object has no concept of paint offset, HTML object's
// local space is shifted by paint offset.
bounding_box.MoveBy(reference_box.Location());
}
bounding_box.Intersect(LayoutRect::InfiniteIntRect());
return bounding_box;
}
static AffineTransform MaskToContentTransform(
const LayoutSVGResourceClipper& resource_clipper,
bool uses_zoomed_reference_box,
const FloatRect& reference_box) {
AffineTransform mask_to_content;
if (resource_clipper.ClipPathUnits() ==
SVGUnitTypes::kSvgUnitTypeUserspaceonuse) {
if (uses_zoomed_reference_box) {
mask_to_content.Translate(reference_box.X(), reference_box.Y());
mask_to_content.Scale(resource_clipper.StyleRef().EffectiveZoom());
}
}
mask_to_content.Multiply(
resource_clipper.CalculateClipTransform(reference_box));
return mask_to_content;
}
static base::Optional<Path> PathBasedClipInternal(
const LayoutObject& clip_path_owner,
bool uses_zoomed_reference_box,
const FloatRect& reference_box) {
const ClipPathOperation& clip_path = *clip_path_owner.StyleRef().ClipPath();
if (const auto* reference_clip =
DynamicTo<ReferenceClipPathOperation>(clip_path)) {
LayoutSVGResourceClipper* resource_clipper =
ResolveElementReference(clip_path_owner, *reference_clip);
if (!resource_clipper)
return base::nullopt;
base::Optional<Path> path = resource_clipper->AsPath();
if (!path)
return path;
path->Transform(MaskToContentTransform(
*resource_clipper, uses_zoomed_reference_box, reference_box));
return path;
}
DCHECK_EQ(clip_path.GetType(), ClipPathOperation::SHAPE);
auto& shape = To<ShapeClipPathOperation>(clip_path);
float zoom = uses_zoomed_reference_box
? clip_path_owner.StyleRef().EffectiveZoom()
: 1;
return shape.GetPath(reference_box, zoom);
}
void ClipPathClipper::PaintClipPathAsMaskImage(
GraphicsContext& context,
const LayoutObject& layout_object,
const DisplayItemClient& display_item_client,
const PhysicalOffset& paint_offset) {
const auto* properties = layout_object.FirstFragment().PaintProperties();
DCHECK(properties);
DCHECK(properties->MaskClip());
DCHECK(properties->ClipPathMask());
PropertyTreeStateOrAlias property_tree_state(
properties->MaskClip()->LocalTransformSpace(), *properties->MaskClip(),
*properties->ClipPathMask());
ScopedPaintChunkProperties scoped_properties(
context.GetPaintController(), property_tree_state, display_item_client,
DisplayItem::kSVGClip);
if (DrawingRecorder::UseCachedDrawingIfPossible(context, display_item_client,
DisplayItem::kSVGClip))
return;
DrawingRecorder recorder(
context, display_item_client, DisplayItem::kSVGClip,
EnclosingIntRect(properties->MaskClip()->UnsnappedClipRect().Rect()));
context.Save();
context.Translate(paint_offset.left, paint_offset.top);
bool uses_zoomed_reference_box = UsesZoomedReferenceBox(layout_object);
FloatRect reference_box = LocalReferenceBox(layout_object);
bool is_first = true;
bool rest_of_the_chain_already_appled = false;
const LayoutObject* current_object = &layout_object;
while (!rest_of_the_chain_already_appled && current_object) {
const ClipPathOperation* clip_path = current_object->StyleRef().ClipPath();
if (!clip_path)
break;
// We wouldn't have reached here if the current clip-path is a shape,
// because it would have been applied as a path-based clip already.
LayoutSVGResourceClipper* resource_clipper = ResolveElementReference(
*current_object, To<ReferenceClipPathOperation>(*clip_path));
if (!resource_clipper)
break;
if (is_first)
context.Save();
else
context.BeginLayer(1.f, SkBlendMode::kDstIn);
if (resource_clipper->StyleRef().HasClipPath()) {
// Try to apply nested clip-path as path-based clip.
if (const base::Optional<Path>& path = PathBasedClipInternal(
*resource_clipper, uses_zoomed_reference_box, reference_box)) {
context.ClipPath(path->GetSkPath(), kAntiAliased);
rest_of_the_chain_already_appled = true;
}
}
context.ConcatCTM(MaskToContentTransform(
*resource_clipper, uses_zoomed_reference_box, reference_box));
context.DrawRecord(resource_clipper->CreatePaintRecord());
if (is_first)
context.Restore();
else
context.EndLayer();
is_first = false;
current_object = resource_clipper;
}
context.Restore();
}
bool ClipPathClipper::ShouldUseMaskBasedClip(const LayoutObject& object) {
if (object.IsText() || !object.StyleRef().HasClipPath())
return false;
const auto* reference_clip =
DynamicTo<ReferenceClipPathOperation>(object.StyleRef().ClipPath());
if (!reference_clip)
return false;
LayoutSVGResourceClipper* resource_clipper =
ResolveElementReference(object, *reference_clip);
if (!resource_clipper)
return false;
return !resource_clipper->AsPath();
}
base::Optional<Path> ClipPathClipper::PathBasedClip(
const LayoutObject& clip_path_owner) {
return PathBasedClipInternal(clip_path_owner,
UsesZoomedReferenceBox(clip_path_owner),
LocalReferenceBox(clip_path_owner));
}
} // namespace blink