1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/paint/clip_path_clipper.h"
6
7 #include "third_party/blink/renderer/core/dom/element_traversal.h"
8 #include "third_party/blink/renderer/core/layout/layout_box.h"
9 #include "third_party/blink/renderer/core/layout/layout_inline.h"
10 #include "third_party/blink/renderer/core/layout/svg/layout_svg_resource_clipper.h"
11 #include "third_party/blink/renderer/core/layout/svg/svg_layout_support.h"
12 #include "third_party/blink/renderer/core/layout/svg/svg_resources.h"
13 #include "third_party/blink/renderer/core/layout/svg/svg_resources_cache.h"
14 #include "third_party/blink/renderer/core/paint/paint_info.h"
15 #include "third_party/blink/renderer/core/style/clip_path_operation.h"
16 #include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
17 #include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
18 #include "third_party/blink/renderer/platform/graphics/paint/drawing_display_item.h"
19 #include "third_party/blink/renderer/platform/graphics/paint/drawing_recorder.h"
20 #include "third_party/blink/renderer/platform/graphics/paint/paint_controller.h"
21 #include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
22 #include "third_party/blink/renderer/platform/graphics/paint/scoped_paint_chunk_properties.h"
23
24 namespace blink {
25
26 namespace {
27
ResolveElementReference(const LayoutObject & layout_object,const ReferenceClipPathOperation & reference_clip_path_operation)28 LayoutSVGResourceClipper* ResolveElementReference(
29 const LayoutObject& layout_object,
30 const ReferenceClipPathOperation& reference_clip_path_operation) {
31 LayoutSVGResourceClipper* resource_clipper = nullptr;
32 if (layout_object.IsSVGChild()) {
33 // The reference will have been resolved in
34 // SVGResources::buildResources, so we can just use the LayoutObject's
35 // SVGResources.
36 SVGResources* resources =
37 SVGResourcesCache::CachedResourcesForLayoutObject(layout_object);
38 resource_clipper = resources ? resources->Clipper() : nullptr;
39 } else {
40 // TODO(fs): Doesn't work with external SVG references (crbug.com/109212.)
41 resource_clipper = GetSVGResourceAsType<LayoutSVGResourceClipper>(
42 reference_clip_path_operation.Resource());
43 }
44 if (resource_clipper) {
45 SECURITY_DCHECK(!resource_clipper->NeedsLayout());
46 resource_clipper->ClearInvalidationMask();
47 }
48 return resource_clipper;
49 }
50
51 } // namespace
52
53 // Is the reference box (as returned by LocalReferenceBox) for |clip_path_owner|
54 // zoomed with EffectiveZoom()?
UsesZoomedReferenceBox(const LayoutObject & clip_path_owner)55 static bool UsesZoomedReferenceBox(const LayoutObject& clip_path_owner) {
56 return !clip_path_owner.IsSVGChild() || clip_path_owner.IsSVGForeignObject();
57 }
58
LocalReferenceBox(const LayoutObject & object)59 FloatRect ClipPathClipper::LocalReferenceBox(const LayoutObject& object) {
60 if (object.IsSVGChild())
61 return SVGResources::ReferenceBoxForEffects(object);
62
63 if (object.IsBox())
64 return FloatRect(To<LayoutBox>(object).BorderBoxRect());
65
66 return FloatRect(To<LayoutInline>(object).ReferenceBoxForClipPath());
67 }
68
LocalClipPathBoundingBox(const LayoutObject & object)69 base::Optional<FloatRect> ClipPathClipper::LocalClipPathBoundingBox(
70 const LayoutObject& object) {
71 if (object.IsText() || !object.StyleRef().ClipPath())
72 return base::nullopt;
73
74 FloatRect reference_box = LocalReferenceBox(object);
75 ClipPathOperation& clip_path = *object.StyleRef().ClipPath();
76 if (clip_path.GetType() == ClipPathOperation::SHAPE) {
77 auto zoom =
78 UsesZoomedReferenceBox(object) ? object.StyleRef().EffectiveZoom() : 1;
79 auto& shape = To<ShapeClipPathOperation>(clip_path);
80 FloatRect bounding_box = shape.GetPath(reference_box, zoom).BoundingRect();
81 bounding_box.Intersect(LayoutRect::InfiniteIntRect());
82 return bounding_box;
83 }
84
85 DCHECK_EQ(clip_path.GetType(), ClipPathOperation::REFERENCE);
86 LayoutSVGResourceClipper* clipper = ResolveElementReference(
87 object, To<ReferenceClipPathOperation>(clip_path));
88 if (!clipper)
89 return base::nullopt;
90
91 FloatRect bounding_box = clipper->ResourceBoundingBox(reference_box);
92 if (UsesZoomedReferenceBox(object) &&
93 clipper->ClipPathUnits() == SVGUnitTypes::kSvgUnitTypeUserspaceonuse) {
94 bounding_box.Scale(clipper->StyleRef().EffectiveZoom());
95 // With kSvgUnitTypeUserspaceonuse, the clip path layout is relative to
96 // the current transform space, and the reference box is unused.
97 // While SVG object has no concept of paint offset, HTML object's
98 // local space is shifted by paint offset.
99 bounding_box.MoveBy(reference_box.Location());
100 }
101 bounding_box.Intersect(LayoutRect::InfiniteIntRect());
102 return bounding_box;
103 }
104
MaskToContentTransform(const LayoutSVGResourceClipper & resource_clipper,bool uses_zoomed_reference_box,const FloatRect & reference_box)105 static AffineTransform MaskToContentTransform(
106 const LayoutSVGResourceClipper& resource_clipper,
107 bool uses_zoomed_reference_box,
108 const FloatRect& reference_box) {
109 AffineTransform mask_to_content;
110 if (resource_clipper.ClipPathUnits() ==
111 SVGUnitTypes::kSvgUnitTypeUserspaceonuse) {
112 if (uses_zoomed_reference_box) {
113 mask_to_content.Translate(reference_box.X(), reference_box.Y());
114 mask_to_content.Scale(resource_clipper.StyleRef().EffectiveZoom());
115 }
116 }
117
118 mask_to_content.Multiply(
119 resource_clipper.CalculateClipTransform(reference_box));
120 return mask_to_content;
121 }
122
PathBasedClipInternal(const LayoutObject & clip_path_owner,bool uses_zoomed_reference_box,const FloatRect & reference_box)123 static base::Optional<Path> PathBasedClipInternal(
124 const LayoutObject& clip_path_owner,
125 bool uses_zoomed_reference_box,
126 const FloatRect& reference_box) {
127 const ClipPathOperation& clip_path = *clip_path_owner.StyleRef().ClipPath();
128 if (const auto* reference_clip =
129 DynamicTo<ReferenceClipPathOperation>(clip_path)) {
130 LayoutSVGResourceClipper* resource_clipper =
131 ResolveElementReference(clip_path_owner, *reference_clip);
132 if (!resource_clipper)
133 return base::nullopt;
134 base::Optional<Path> path = resource_clipper->AsPath();
135 if (!path)
136 return path;
137 path->Transform(MaskToContentTransform(
138 *resource_clipper, uses_zoomed_reference_box, reference_box));
139 return path;
140 }
141
142 DCHECK_EQ(clip_path.GetType(), ClipPathOperation::SHAPE);
143 auto& shape = To<ShapeClipPathOperation>(clip_path);
144 float zoom = uses_zoomed_reference_box
145 ? clip_path_owner.StyleRef().EffectiveZoom()
146 : 1;
147 return shape.GetPath(reference_box, zoom);
148 }
149
PaintClipPathAsMaskImage(GraphicsContext & context,const LayoutObject & layout_object,const DisplayItemClient & display_item_client,const PhysicalOffset & paint_offset)150 void ClipPathClipper::PaintClipPathAsMaskImage(
151 GraphicsContext& context,
152 const LayoutObject& layout_object,
153 const DisplayItemClient& display_item_client,
154 const PhysicalOffset& paint_offset) {
155 const auto* properties = layout_object.FirstFragment().PaintProperties();
156 DCHECK(properties);
157 DCHECK(properties->MaskClip());
158 DCHECK(properties->ClipPathMask());
159 PropertyTreeStateOrAlias property_tree_state(
160 properties->MaskClip()->LocalTransformSpace(), *properties->MaskClip(),
161 *properties->ClipPathMask());
162 ScopedPaintChunkProperties scoped_properties(
163 context.GetPaintController(), property_tree_state, display_item_client,
164 DisplayItem::kSVGClip);
165
166 if (DrawingRecorder::UseCachedDrawingIfPossible(context, display_item_client,
167 DisplayItem::kSVGClip))
168 return;
169
170 DrawingRecorder recorder(
171 context, display_item_client, DisplayItem::kSVGClip,
172 EnclosingIntRect(properties->MaskClip()->UnsnappedClipRect().Rect()));
173 context.Save();
174 context.Translate(paint_offset.left, paint_offset.top);
175
176 bool uses_zoomed_reference_box = UsesZoomedReferenceBox(layout_object);
177 FloatRect reference_box = LocalReferenceBox(layout_object);
178 bool is_first = true;
179 bool rest_of_the_chain_already_appled = false;
180 const LayoutObject* current_object = &layout_object;
181 while (!rest_of_the_chain_already_appled && current_object) {
182 const ClipPathOperation* clip_path = current_object->StyleRef().ClipPath();
183 if (!clip_path)
184 break;
185 // We wouldn't have reached here if the current clip-path is a shape,
186 // because it would have been applied as a path-based clip already.
187 LayoutSVGResourceClipper* resource_clipper = ResolveElementReference(
188 *current_object, To<ReferenceClipPathOperation>(*clip_path));
189 if (!resource_clipper)
190 break;
191
192 if (is_first)
193 context.Save();
194 else
195 context.BeginLayer(1.f, SkBlendMode::kDstIn);
196
197 if (resource_clipper->StyleRef().ClipPath()) {
198 // Try to apply nested clip-path as path-based clip.
199 if (const base::Optional<Path>& path = PathBasedClipInternal(
200 *resource_clipper, uses_zoomed_reference_box, reference_box)) {
201 context.ClipPath(path->GetSkPath(), kAntiAliased);
202 rest_of_the_chain_already_appled = true;
203 }
204 }
205 context.ConcatCTM(MaskToContentTransform(
206 *resource_clipper, uses_zoomed_reference_box, reference_box));
207 context.DrawRecord(resource_clipper->CreatePaintRecord());
208
209 if (is_first)
210 context.Restore();
211 else
212 context.EndLayer();
213
214 is_first = false;
215 current_object = resource_clipper;
216 }
217 context.Restore();
218 }
219
ShouldUseMaskBasedClip(const LayoutObject & object)220 bool ClipPathClipper::ShouldUseMaskBasedClip(const LayoutObject& object) {
221 if (object.IsText())
222 return false;
223 const auto* reference_clip =
224 DynamicTo<ReferenceClipPathOperation>(object.StyleRef().ClipPath());
225 if (!reference_clip)
226 return false;
227 LayoutSVGResourceClipper* resource_clipper =
228 ResolveElementReference(object, *reference_clip);
229 if (!resource_clipper)
230 return false;
231 return !resource_clipper->AsPath();
232 }
233
PathBasedClip(const LayoutObject & clip_path_owner)234 base::Optional<Path> ClipPathClipper::PathBasedClip(
235 const LayoutObject& clip_path_owner) {
236 return PathBasedClipInternal(clip_path_owner,
237 UsesZoomedReferenceBox(clip_path_owner),
238 LocalReferenceBox(clip_path_owner));
239 }
240
241 } // namespace blink
242