1 /*
2  * Copyright (C) 2011 University of Szeged
3  * Copyright (C) 2011 Renata Hodovan <reni@webkit.org>
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF SZEGED ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL UNIVERSITY OF SZEGED OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "third_party/blink/renderer/core/layout/svg/layout_svg_rect.h"
29 
30 #include "third_party/blink/renderer/core/svg/svg_length_context.h"
31 #include "third_party/blink/renderer/core/svg/svg_rect_element.h"
32 #include "third_party/blink/renderer/platform/wtf/math_extras.h"
33 
34 namespace blink {
35 
LayoutSVGRect(SVGRectElement * node)36 LayoutSVGRect::LayoutSVGRect(SVGRectElement* node)
37     : LayoutSVGShape(node, kSimple), use_path_fallback_(false) {}
38 
39 LayoutSVGRect::~LayoutSVGRect() = default;
40 
UpdateShapeFromElement()41 void LayoutSVGRect::UpdateShapeFromElement() {
42   NOT_DESTROYED();
43   // Before creating a new object we need to clear the cached bounding box
44   // to avoid using garbage.
45   fill_bounding_box_ = FloatRect();
46   stroke_bounding_box_ = FloatRect();
47   use_path_fallback_ = false;
48 
49   SVGLengthContext length_context(GetElement());
50   const ComputedStyle& style = StyleRef();
51   FloatSize bounding_box_size(ToFloatSize(
52       length_context.ResolveLengthPair(style.Width(), style.Height(), style)));
53 
54   // Spec: "A negative value is an error."
55   if (bounding_box_size.Width() < 0 || bounding_box_size.Height() < 0)
56     return;
57 
58   const SVGComputedStyle& svg_style = style.SvgStyle();
59   // Spec: "A value of zero disables rendering of the element."
60   if (!bounding_box_size.IsEmpty()) {
61     // Fallback to LayoutSVGShape and path-based hit detection if the rect
62     // has rounded corners or a non-scaling or non-simple stroke.
63     // However, only use LayoutSVGShape bounding-box calculations for the
64     // non-scaling stroke case, since the computation below should be accurate
65     // for the other cases.
66     if (HasNonScalingStroke()) {
67       LayoutSVGShape::UpdateShapeFromElement();
68       use_path_fallback_ = true;
69       return;
70     }
71     FloatPoint radii(length_context.ResolveLengthPair(svg_style.Rx(),
72                                                       svg_style.Ry(), style));
73     if (radii.X() > 0 || radii.Y() > 0 || !DefinitelyHasSimpleStroke()) {
74       CreatePath();
75       use_path_fallback_ = true;
76     }
77   }
78 
79   if (!use_path_fallback_)
80     ClearPath();
81 
82   fill_bounding_box_ = FloatRect(
83       length_context.ResolveLengthPair(svg_style.X(), svg_style.Y(), style),
84       bounding_box_size);
85   stroke_bounding_box_ = CalculateStrokeBoundingBox();
86 }
87 
ShapeDependentStrokeContains(const HitTestLocation & location)88 bool LayoutSVGRect::ShapeDependentStrokeContains(
89     const HitTestLocation& location) {
90   NOT_DESTROYED();
91   // The optimized code below does not support the cases that we set
92   // use_path_fallback_ in UpdateShapeFromElement().
93   if (use_path_fallback_)
94     return LayoutSVGShape::ShapeDependentStrokeContains(location);
95 
96   const FloatPoint& point = location.TransformedPoint();
97   const float half_stroke_width = StrokeWidth() / 2;
98   const float half_width = fill_bounding_box_.Width() / 2;
99   const float half_height = fill_bounding_box_.Height() / 2;
100 
101   const FloatPoint fill_bounding_box_center =
102       FloatPoint(fill_bounding_box_.X() + half_width,
103                  fill_bounding_box_.Y() + half_height);
104   const float abs_delta_x = std::abs(point.X() - fill_bounding_box_center.X());
105   const float abs_delta_y = std::abs(point.Y() - fill_bounding_box_center.Y());
106 
107   if (!(abs_delta_x <= half_width + half_stroke_width &&
108         abs_delta_y <= half_height + half_stroke_width))
109     return false;
110 
111   return (half_width - half_stroke_width <= abs_delta_x) ||
112          (half_height - half_stroke_width <= abs_delta_y);
113 }
114 
ShapeDependentFillContains(const HitTestLocation & location,const WindRule fill_rule) const115 bool LayoutSVGRect::ShapeDependentFillContains(const HitTestLocation& location,
116                                                const WindRule fill_rule) const {
117   NOT_DESTROYED();
118   if (use_path_fallback_)
119     return LayoutSVGShape::ShapeDependentFillContains(location, fill_rule);
120   const FloatPoint& point = location.TransformedPoint();
121   return fill_bounding_box_.Contains(point.X(), point.Y());
122 }
123 
124 // Returns true if the stroke is continuous and definitely uses miter joins.
DefinitelyHasSimpleStroke() const125 bool LayoutSVGRect::DefinitelyHasSimpleStroke() const {
126   NOT_DESTROYED();
127   const SVGComputedStyle& svg_style = StyleRef().SvgStyle();
128 
129   // The four angles of a rect are 90 degrees. Using the formula at:
130   // http://www.w3.org/TR/SVG/painting.html#StrokeMiterlimitProperty
131   // when the join style of the rect is "miter", the ratio of the miterLength
132   // to the stroke-width is found to be
133   // miterLength / stroke-width = 1 / sin(45 degrees)
134   //                            = 1 / (1 / sqrt(2))
135   //                            = sqrt(2)
136   //                            = 1.414213562373095...
137   // When sqrt(2) exceeds the miterlimit, then the join style switches to
138   // "bevel". When the miterlimit is greater than or equal to sqrt(2) then
139   // the join style remains "miter".
140   //
141   // An approximation of sqrt(2) is used here because at certain precise
142   // miterlimits, the join style used might not be correct (e.g. a miterlimit
143   // of 1.4142135 should result in bevel joins, but may be drawn using miter
144   // joins).
145   return svg_style.StrokeDashArray()->data.IsEmpty() &&
146          svg_style.JoinStyle() == kMiterJoin &&
147          svg_style.StrokeMiterLimit() >= 1.5;
148 }
149 
150 }  // namespace blink
151