summaryrefslogtreecommitdiffstats
path: root/dom/svg/SVGGeometryElement.cpp
blob: d012a911b8577caceb6d04119fa750fc70220890 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "SVGGeometryElement.h"

#include "DOMSVGPoint.h"
#include "gfxPlatform.h"
#include "nsCOMPtr.h"
#include "SVGAnimatedLength.h"
#include "SVGCircleElement.h"
#include "SVGEllipseElement.h"
#include "SVGGeometryProperty.h"
#include "SVGPathElement.h"
#include "SVGRectElement.h"
#include "mozilla/dom/DOMPointBinding.h"
#include "mozilla/dom/SVGLengthBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/RefPtr.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/SVGContentUtils.h"

using namespace mozilla::gfx;

namespace mozilla::dom {

SVGElement::NumberInfo SVGGeometryElement::sNumberInfo = {nsGkAtoms::pathLength,
                                                          0};

//----------------------------------------------------------------------
// Implementation

SVGGeometryElement::SVGGeometryElement(
    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
    : SVGGeometryElementBase(std::move(aNodeInfo)) {}

SVGElement::NumberAttributesInfo SVGGeometryElement::GetNumberInfo() {
  return NumberAttributesInfo(&mPathLength, &sNumberInfo, 1);
}

void SVGGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
                                      const nsAttrValue* aValue,
                                      const nsAttrValue* aOldValue,
                                      nsIPrincipal* aSubjectPrincipal,
                                      bool aNotify) {
  if (mCachedPath && aNamespaceID == kNameSpaceID_None &&
      AttributeDefinesGeometry(aName)) {
    mCachedPath = nullptr;
  }
  return SVGGeometryElementBase::AfterSetAttr(
      aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify);
}

bool SVGGeometryElement::AttributeDefinesGeometry(const nsAtom* aName) {
  if (aName == nsGkAtoms::pathLength) {
    return true;
  }

  // Check for SVGAnimatedLength attribute
  LengthAttributesInfo info = GetLengthInfo();
  for (uint32_t i = 0; i < info.mCount; i++) {
    if (aName == info.mInfos[i].mName) {
      return true;
    }
  }

  return false;
}

bool SVGGeometryElement::GeometryDependsOnCoordCtx() {
  // Check the SVGAnimatedLength attribute
  LengthAttributesInfo info =
      const_cast<SVGGeometryElement*>(this)->GetLengthInfo();
  for (uint32_t i = 0; i < info.mCount; i++) {
    if (info.mValues[i].IsPercentage()) {
      return true;
    }
  }
  return false;
}

bool SVGGeometryElement::IsMarkable() { return false; }

void SVGGeometryElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {}

already_AddRefed<Path> SVGGeometryElement::GetOrBuildPath(
    const DrawTarget* aDrawTarget, FillRule aFillRule) {
  // We only cache the path if it matches the backend used for screen painting,
  // and it's not a capturing drawtarget. A capturing DT might start using the
  // the Path object on a different thread (OMTP), and we might have a data race
  // if we keep a handle to it.
  bool cacheable = aDrawTarget->GetBackendType() ==
                   gfxPlatform::GetPlatform()->GetDefaultContentBackend();

  if (cacheable && mCachedPath && mCachedPath->GetFillRule() == aFillRule &&
      aDrawTarget->GetBackendType() == mCachedPath->GetBackendType()) {
    RefPtr<Path> path(mCachedPath);
    return path.forget();
  }
  RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder(aFillRule);
  RefPtr<Path> path = BuildPath(builder);
  if (cacheable) {
    mCachedPath = path;
  }
  return path.forget();
}

already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForMeasuring() {
  RefPtr<DrawTarget> drawTarget =
      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
  FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule();
  return GetOrBuildPath(drawTarget, fillRule);
}

// This helper is currently identical to GetOrBuildPathForMeasuring.
// We keep it a separate method because for length measuring purpose,
// fillRule isn't really needed. Derived class (e.g. SVGPathElement)
// may override GetOrBuildPathForMeasuring() to ignore fillRule. And
// GetOrBuildPathForMeasuring() itself may be modified in the future.
already_AddRefed<Path> SVGGeometryElement::GetOrBuildPathForHitTest() {
  RefPtr<DrawTarget> drawTarget =
      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
  FillRule fillRule = mCachedPath ? mCachedPath->GetFillRule() : GetFillRule();
  return GetOrBuildPath(drawTarget, fillRule);
}

bool SVGGeometryElement::IsGeometryChangedViaCSS(
    ComputedStyle const& aNewStyle, ComputedStyle const& aOldStyle) const {
  nsAtom* name = NodeInfo()->NameAtom();
  if (name == nsGkAtoms::rect) {
    return SVGRectElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
  }
  if (name == nsGkAtoms::circle) {
    return SVGCircleElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
  }
  if (name == nsGkAtoms::ellipse) {
    return SVGEllipseElement::IsLengthChangedViaCSS(aNewStyle, aOldStyle);
  }
  if (name == nsGkAtoms::path) {
    return SVGPathElement::IsDPropertyChangedViaCSS(aNewStyle, aOldStyle);
  }
  return false;
}

FillRule SVGGeometryElement::GetFillRule() {
  FillRule fillRule =
      FillRule::FILL_WINDING;  // Equivalent to StyleFillRule::Nonzero

  bool res = SVGGeometryProperty::DoForComputedStyle(
      this, [&](const ComputedStyle* s) {
        const auto* styleSVG = s->StyleSVG();

        MOZ_ASSERT(styleSVG->mFillRule == StyleFillRule::Nonzero ||
                   styleSVG->mFillRule == StyleFillRule::Evenodd);

        if (styleSVG->mFillRule == StyleFillRule::Evenodd) {
          fillRule = FillRule::FILL_EVEN_ODD;
        }
      });

  if (!res) {
    NS_WARNING("Couldn't get ComputedStyle for content in GetFillRule");
  }

  return fillRule;
}

static Point GetPointFrom(const DOMPointInit& aPoint) {
  return Point(aPoint.mX, aPoint.mY);
}

bool SVGGeometryElement::IsPointInFill(const DOMPointInit& aPoint) {
  // d is a presentation attribute, so make sure style is up to date:
  FlushStyleIfNeeded();

  RefPtr<Path> path = GetOrBuildPathForHitTest();
  if (!path) {
    return false;
  }

  auto point = GetPointFrom(aPoint);
  return path->ContainsPoint(point, {});
}

bool SVGGeometryElement::IsPointInStroke(const DOMPointInit& aPoint) {
  // stroke-* attributes and the d attribute are presentation attributes, so we
  // flush the layout before building the path.
  if (nsCOMPtr<Document> doc = GetComposedDoc()) {
    doc->FlushPendingNotifications(FlushType::Layout);
  }

  RefPtr<Path> path = GetOrBuildPathForHitTest();
  if (!path) {
    return false;
  }

  auto point = GetPointFrom(aPoint);
  bool res = false;
  SVGGeometryProperty::DoForComputedStyle(this, [&](const ComputedStyle* s) {
    // Per spec, we should take vector-effect into account.
    if (s->StyleSVGReset()->HasNonScalingStroke()) {
      auto mat = SVGContentUtils::GetCTM(this, true);
      if (mat.HasNonTranslation()) {
        // We have non-scaling-stroke as well as a non-translation transform.
        // We should transform the path first then apply the stroke on the
        // transformed path to preserve the stroke-width.
        RefPtr<PathBuilder> builder = path->TransformedCopyToBuilder(mat);

        path = builder->Finish();
        point = mat.TransformPoint(point);
      }
    }

    SVGContentUtils::AutoStrokeOptions strokeOptions;
    SVGContentUtils::GetStrokeOptions(&strokeOptions, this, s, nullptr);

    res = path->StrokeContainsPoint(strokeOptions, point, {});
  });

  return res;
}

float SVGGeometryElement::GetTotalLengthForBinding() {
  // d is a presentation attribute, so make sure style is up to date:
  FlushStyleIfNeeded();
  return GetTotalLength();
}

already_AddRefed<DOMSVGPoint> SVGGeometryElement::GetPointAtLength(
    float distance, ErrorResult& rv) {
  // d is a presentation attribute, so make sure style is up to date:
  FlushStyleIfNeeded();
  RefPtr<Path> path = GetOrBuildPathForMeasuring();
  if (!path) {
    rv.ThrowInvalidStateError("No path available for measuring");
    return nullptr;
  }

  return do_AddRef(new DOMSVGPoint(path->ComputePointAtLength(
      clamped(distance, 0.f, path->ComputeLength()))));
}

float SVGGeometryElement::GetPathLengthScale(PathLengthScaleForType aFor) {
  MOZ_ASSERT(aFor == eForTextPath || aFor == eForStroking, "Unknown enum");
  if (mPathLength.IsExplicitlySet()) {
    float authorsPathLengthEstimate = mPathLength.GetAnimValue();
    if (authorsPathLengthEstimate >= 0) {
      RefPtr<Path> path = GetOrBuildPathForMeasuring();
      if (!path) {
        // The path is empty or invalid so its length must be zero and
        // we know that 0 / authorsPathLengthEstimate = 0.
        return 0.0;
      }
      if (aFor == eForTextPath) {
        // For textPath, a transform on the referenced path affects the
        // textPath layout, so when calculating the actual path length
        // we need to take that into account.
        gfxMatrix matrix = PrependLocalTransformsTo(gfxMatrix());
        if (!matrix.IsIdentity()) {
          RefPtr<PathBuilder> builder =
              path->TransformedCopyToBuilder(ToMatrix(matrix));
          path = builder->Finish();
        }
      }
      return path->ComputeLength() / authorsPathLengthEstimate;
    }
  }
  return 1.0;
}

already_AddRefed<DOMSVGAnimatedNumber> SVGGeometryElement::PathLength() {
  return mPathLength.ToDOMAnimatedNumber(this);
}

float SVGGeometryElement::GetTotalLength() {
  RefPtr<Path> flat = GetOrBuildPathForMeasuring();
  return flat ? flat->ComputeLength() : 0.f;
}

void SVGGeometryElement::FlushStyleIfNeeded() {
  // Note: we still can set d property on other elements which don't have d
  // attribute, but we don't look at the d property on them, so here we only
  // care about the element with d attribute, i.e. SVG path element.
  if (GetPathDataAttrName() != nsGkAtoms::d) {
    return;
  }

  RefPtr<Document> doc = GetComposedDoc();
  if (!doc) {
    return;
  }

  doc->FlushPendingNotifications(FlushType::Style);
}

}  // namespace mozilla::dom