SVGGeometryElement.cpp (9887B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "SVGGeometryElement.h" 8 9 #include "DOMSVGPoint.h" 10 #include "SVGAnimatedLength.h" 11 #include "SVGCircleElement.h" 12 #include "SVGEllipseElement.h" 13 #include "SVGGeometryProperty.h" 14 #include "SVGPathElement.h" 15 #include "SVGRectElement.h" 16 #include "gfxPlatform.h" 17 #include "mozilla/RefPtr.h" 18 #include "mozilla/SVGContentUtils.h" 19 #include "mozilla/SVGUtils.h" 20 #include "mozilla/dom/DOMPointBinding.h" 21 #include "mozilla/dom/SVGLengthBinding.h" 22 #include "mozilla/gfx/2D.h" 23 #include "nsCOMPtr.h" 24 #include "nsLayoutUtils.h" 25 #include "nsStyleTransformMatrix.h" 26 27 using namespace mozilla::gfx; 28 29 namespace mozilla::dom { 30 31 SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength, 32 0}; 33 34 //---------------------------------------------------------------------- 35 // Implementation 36 37 SVGGeometryElement::SVGGeometryElement( 38 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) 39 : SVGGeometryElementBase(std::move(aNodeInfo)) {} 40 41 SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() { 42 return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1); 43 } 44 45 void SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, 46 const nsAttrValue* aValue, 47 const nsAttrValue* aOldValue, 48 nsIPrincipal* aSubjectPrincipal, 49 bool aNotify) { 50 if (mCachedPath && aNamespaceID == kNameSpaceID_None && 51 AttributeDefinesGeometry(aName)) { 52 mCachedPath = nullptr; 53 } 54 return SVGGeometryElementBase::AfterSetAttr( 55 aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); 56 } 57 58 bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) { 59 if (aName == nsGkAtoms::pathLength) { 60 return true; 61 } 62 63 // Check for SVGAnimatedLength attribute 64 LengthAttributesInfo info = GetLengthInfo(); 65 for (uint32_t i = 0; i < info.mCount; i++) { 66 if (aName == info.mInfos[i].mName) { 67 return true; 68 } 69 } 70 71 return false; 72 } 73 74 bool SVGGeometryElement::GeometryDependsOnCoordCtx() { 75 // Check the SVGAnimatedLength attribute 76 LengthAttributesInfo info = 77 const_cast<SVGGeometryElement*>(this)->GetLengthInfo(); 78 for (uint32_t i = 0; i < info.mCount; i++) { 79 if (info.mValues[i].IsPercentage()) { 80 return true; 81 } 82 } 83 return false; 84 } 85 86 bool SVGGeometryElement::IsMarkable() { return false; } 87 88 void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {} 89 90 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath( 91 const DrawTarget* aDrawTarget, FillRule aFillRule) { 92 // We only cache the path if it matches the backend used for screen painting, 93 // and it's not a capturing drawtarget. A capturing DT might start using the 94 // the Path object on a different thread (OMTP), and we might have a data race 95 // if we keep a handle to it. 96 bool cacheable = aDrawTarget->GetBackendType() == 97 gfxPlatform::GetPlatform()->GetDefaultContentBackend(); 98 99 if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule && 100 aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) { 101 RefPtr<Path> path(mCachedPath); 102 return path.forget(); 103 } 104 RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(aFillRule); 105 RefPtr<Path> path = BuildPath(builder); 106 if (cacheable) { 107 mCachedPath = path; 108 } 109 return path.forget(); 110 } 111 112 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() { 113 RefPtr<DrawTarget> drawTarget = 114 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 115 FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); 116 return GetOrBuildPath(drawTarget, fillRule); 117 } 118 119 // This helper is currently identical to GetOrBuildPathForMeasuring. 120 // We keep it a separate method because for length measuring purpose, 121 // fillRule isn't really needed. Derived class (e.g. SVGPathElement) 122 // may override GetOrBuildPathForMeasuring() to ignore fillRule. And 123 // GetOrBuildPathForMeasuring() itself may be modified in the future. 124 already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() { 125 RefPtr<DrawTarget> drawTarget = 126 gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget(); 127 FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule(); 128 return GetOrBuildPath(drawTarget, fillRule); 129 } 130 131 bool SVGGeometryElement::IsGeometryChangedViaCSS( 132 ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const { 133 nsAtom* name = NodeInfo()->NameAtom(); 134 if (name == nsGkAtoms::rect) { 135 return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); 136 } 137 if (name == nsGkAtoms::circle) { 138 return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); 139 } 140 if (name == nsGkAtoms::ellipse) { 141 return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle); 142 } 143 if (name == nsGkAtoms::path) { 144 return SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle); 145 } 146 return false; 147 } 148 149 FillRule SVGGeometryElement::GetFillRule() { 150 FillRule fillRule = 151 FillRule::FILL_WINDING; // Equivalent to StyleFillRule::Nonzero 152 153 bool res = SVGGeometryProperty::DoForComputedStyle( 154 this, [&](const ComputedStyle* s) { 155 const auto* styleSVG = s->StyleSVG(); 156 157 MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero || 158 styleSVG->mFillRule == StyleFillRule::Evenodd); 159 160 if (styleSVG->mFillRule == StyleFillRule::Evenodd) { 161 fillRule = FillRule::FILL_EVEN_ODD; 162 } 163 }); 164 165 if (!res) { 166 NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule"); 167 } 168 169 return fillRule; 170 } 171 172 static Point GetPointFrom(const DOMPointInit& aPoint) { 173 return Point(aPoint.mX, aPoint.mY); 174 } 175 176 bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) { 177 FlushIfNeeded(); 178 179 RefPtr<Path> path = GetOrBuildPathForHitTest(); 180 if (!path) { 181 return false; 182 } 183 184 auto point = GetPointFrom(aPoint); 185 return path->ContainsPoint(point, {}); 186 } 187 188 bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) { 189 // stroke-* attributes and the d attribute are presentation attributes, so we 190 // flush the layout before building the path. 191 (void)GetPrimaryFrame(FlushType::Layout); 192 193 RefPtr<Path> path = GetOrBuildPathForHitTest(); 194 if (!path) { 195 return false; 196 } 197 198 auto point = GetPointFrom(aPoint); 199 bool res = false; 200 SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) { 201 // Per spec, we should take vector-effect into account. 202 if (s->StyleSVGReset()->HasNonScalingStroke()) { 203 auto mat = SVGContentUtils::GetNonScalingStrokeCTM(this); 204 if (mat.HasNonTranslation()) { 205 // We have non-scaling-stroke as well as a non-translation transform. 206 // We should transform the path first then apply the stroke on the 207 // transformed path to preserve the stroke-width. 208 Path::Transform(path, mat); 209 point = mat.TransformPoint(point); 210 } 211 } 212 213 SVGContentUtils::AutoStrokeOptions strokeOptions; 214 SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr); 215 216 res = path->StrokeContainsPoint(strokeOptions, point, {}); 217 }); 218 219 return res; 220 } 221 222 float SVGGeometryElement::GetTotalLengthForBinding() { 223 FlushIfNeeded(); 224 return GetTotalLength(); 225 } 226 227 already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength( 228 float distance, ErrorResult& rv) { 229 FlushIfNeeded(); 230 231 RefPtr<Path> path = GetOrBuildPathForMeasuring(); 232 if (!path) { 233 rv.ThrowInvalidStateError("No path available for measuring"); 234 return nullptr; 235 } 236 237 return do_AddRef(new DOMSVGPoint(path->ComputePointAtLength( 238 std::clamp(distance, 0.f, path->ComputeLength())))); 239 } 240 241 gfx::Matrix SVGGeometryElement::LocalTransform() const { 242 nsIFrame* f = GetPrimaryFrame(); 243 if (!f || !f->IsTransformed()) { 244 return {}; 245 } 246 return gfx::Matrix(SVGUtils::GetTransformMatrixInUserSpace(f)); 247 } 248 249 float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) { 250 MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum"); 251 if (mPathLength.IsExplicitlySet()) { 252 float zoom = UserSpaceMetrics::GetZoom(this); 253 float authorsPathLengthEstimate = mPathLength.GetAnimValue() * zoom; 254 if (std::isfinite(authorsPathLengthEstimate) && 255 authorsPathLengthEstimate >= 0) { 256 RefPtr<Path> path = GetOrBuildPathForMeasuring(); 257 if (!path) { 258 // The path is empty or invalid so its length must be zero and 259 // we know that 0 / authorsPathLengthEstimate = 0. 260 return 0.0; 261 } 262 if (aFor == eForTextPath) { 263 // For textPath, a transform on the referenced path affects the 264 // textPath layout, so when calculating the actual path length 265 // we need to take that into account. 266 auto matrix = LocalTransform(); 267 if (!matrix.IsIdentity()) { 268 Path::Transform(path, matrix); 269 } 270 } 271 return path->ComputeLength() / authorsPathLengthEstimate; 272 } 273 } 274 return 1.0; 275 } 276 277 already_AddRefed<DOMSVGAnimatedNumber> SVGGeometryElement::PathLength() { 278 return mPathLength.ToDOMAnimatedNumber(this); 279 } 280 281 float SVGGeometryElement::GetTotalLength() { 282 RefPtr<Path> flat = GetOrBuildPathForMeasuring(); 283 return flat ? flat->ComputeLength() : 0.f; 284 } 285 286 void SVGGeometryElement::FlushIfNeeded() { 287 FlushType flushType = 288 GeometryDependsOnCoordCtx() ? FlushType::Layout : FlushType::Style; 289 (void)GetPrimaryFrame(flushType); 290 } 291 292 } // namespace mozilla::dom