tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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