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