tor-browser

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

SVGPathElement.cpp (13019B)


      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 "mozilla/dom/SVGPathElement.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "SVGArcConverter.h"
     12 #include "SVGGeometryProperty.h"
     13 #include "SVGPathSegUtils.h"
     14 #include "gfx2DGlue.h"
     15 #include "gfxPlatform.h"
     16 #include "mozAutoDocUpdate.h"
     17 #include "mozilla/RefPtr.h"
     18 #include "mozilla/SVGContentUtils.h"
     19 #include "mozilla/dom/SVGPathElementBinding.h"
     20 #include "mozilla/dom/SVGPathSegment.h"
     21 #include "mozilla/gfx/2D.h"
     22 #include "nsGkAtoms.h"
     23 #include "nsIFrame.h"
     24 #include "nsStyleConsts.h"
     25 #include "nsStyleStruct.h"
     26 #include "nsWindowSizes.h"
     27 
     28 NS_IMPL_NS_NEW_SVG_ELEMENT(Path)
     29 
     30 using namespace mozilla::gfx;
     31 
     32 namespace mozilla::dom {
     33 
     34 //----------------------------------------------------------------------
     35 // Helper class: AutoChangePathSegListNotifier
     36 // Stack-based helper class to pair calls to WillChangePathSegList and
     37 // DidChangePathSegList.
     38 class MOZ_RAII AutoChangePathSegListNotifier : public mozAutoDocUpdate {
     39 public:
     40  explicit AutoChangePathSegListNotifier(SVGPathElement* aSVGPathElement)
     41      : mozAutoDocUpdate(aSVGPathElement->GetComposedDoc(), true),
     42        mSVGElement(aSVGPathElement) {
     43    MOZ_ASSERT(mSVGElement, "Expecting non-null value");
     44    mSVGElement->WillChangePathSegList(*this);
     45  }
     46 
     47  ~AutoChangePathSegListNotifier() {
     48    mSVGElement->DidChangePathSegList(*this);
     49    if (mSVGElement->GetAnimPathSegList()->IsAnimating()) {
     50      mSVGElement->AnimationNeedsResample();
     51    }
     52  }
     53 
     54 private:
     55  SVGPathElement* const mSVGElement;
     56 };
     57 
     58 JSObject* SVGPathElement::WrapNode(JSContext* aCx,
     59                                   JS::Handle<JSObject*> aGivenProto) {
     60  return SVGPathElement_Binding::Wrap(aCx, this, aGivenProto);
     61 }
     62 
     63 //----------------------------------------------------------------------
     64 // Implementation
     65 
     66 SVGPathElement::SVGPathElement(
     67    already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
     68    : SVGPathElementBase(std::move(aNodeInfo)) {}
     69 
     70 //----------------------------------------------------------------------
     71 // memory reporting methods
     72 
     73 void SVGPathElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
     74                                            size_t* aNodeSize) const {
     75  SVGPathElementBase::AddSizeOfExcludingThis(aSizes, aNodeSize);
     76  *aNodeSize += mD.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
     77 }
     78 
     79 //----------------------------------------------------------------------
     80 // nsINode methods
     81 
     82 NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGPathElement)
     83 
     84 already_AddRefed<SVGPathSegment> SVGPathElement::GetPathSegmentAtLength(
     85    float aDistance) {
     86  FlushIfNeeded();
     87  RefPtr<SVGPathSegment> segment;
     88  if (SVGGeometryProperty::DoForComputedStyle(
     89          this, [&](const ComputedStyle* s) {
     90            const auto& d = s->StyleSVGReset()->mD;
     91            if (d.IsPath()) {
     92              segment = SVGPathData::GetPathSegmentAtLength(
     93                  this, d.AsPath()._0.AsSpan(), aDistance);
     94            }
     95          })) {
     96    return segment.forget();
     97  }
     98  return SVGPathData::GetPathSegmentAtLength(this, mD.GetAnimValue().AsSpan(),
     99                                             aDistance);
    100 }
    101 
    102 static void CreatePathSegments(SVGPathElement* aPathElement,
    103                               const StyleSVGPathData& aPathData,
    104                               nsTArray<RefPtr<SVGPathSegment>>& aValues,
    105                               bool aNormalize) {
    106  if (aNormalize) {
    107    StyleSVGPathData normalizedPathData;
    108    Servo_SVGPathData_NormalizeAndReduce(&aPathData, &normalizedPathData);
    109    Point pathStart(0.0, 0.0);
    110    Point segStart(0.0, 0.0);
    111    Point segEnd(0.0, 0.0);
    112    for (const auto& cmd : normalizedPathData._0.AsSpan()) {
    113      switch (cmd.tag) {
    114        case StylePathCommand::Tag::Close:
    115          segEnd = pathStart;
    116          aValues.AppendElement(new SVGPathSegment(aPathElement, cmd));
    117          break;
    118        case StylePathCommand::Tag::Move:
    119          pathStart = segEnd = cmd.move.point.ToGfxPoint();
    120          aValues.AppendElement(new SVGPathSegment(aPathElement, cmd));
    121          break;
    122        case StylePathCommand::Tag::Line:
    123          segEnd = cmd.line.point.ToGfxPoint();
    124          aValues.AppendElement(new SVGPathSegment(aPathElement, cmd));
    125          break;
    126        case StylePathCommand::Tag::CubicCurve:
    127          segEnd = cmd.cubic_curve.point.ToGfxPoint();
    128          aValues.AppendElement(new SVGPathSegment(aPathElement, cmd));
    129          break;
    130        case StylePathCommand::Tag::Arc: {
    131          const auto& arc = cmd.arc;
    132          segEnd = arc.point.ToGfxPoint();
    133          SVGArcConverter converter(segStart, arc.point.ToGfxPoint(),
    134                                    arc.radii.ToGfxPoint(), arc.rotate,
    135                                    arc.arc_size == StyleArcSize::Large,
    136                                    arc.arc_sweep == StyleArcSweep::Cw);
    137          Point cp1, cp2;
    138          while (converter.GetNextSegment(&cp1, &cp2, &segEnd)) {
    139            auto curve = StylePathCommand::CubicCurve(
    140                StyleEndPoint<StyleCSSFloat>::ToPosition({segEnd.x, segEnd.y}),
    141                StyleCurveControlPoint<StyleCSSFloat>::Absolute({cp1.x, cp1.y}),
    142                StyleCurveControlPoint<StyleCSSFloat>::Absolute(
    143                    {cp2.x, cp2.y}));
    144            aValues.AppendElement(new SVGPathSegment(aPathElement, curve));
    145          }
    146          break;
    147        }
    148        default:
    149          MOZ_ASSERT_UNREACHABLE("Unexpected path command");
    150          break;
    151      }
    152      segStart = segEnd;
    153    }
    154    return;
    155  }
    156  for (const auto& cmd : aPathData._0.AsSpan()) {
    157    aValues.AppendElement(new SVGPathSegment(aPathElement, cmd));
    158  }
    159 }
    160 
    161 void SVGPathElement::GetPathData(const SVGPathDataSettings& aOptions,
    162                                 nsTArray<RefPtr<SVGPathSegment>>& aValues) {
    163  FlushIfNeeded();
    164  if (SVGGeometryProperty::DoForComputedStyle(
    165          this, [&](const ComputedStyle* s) {
    166            const auto& d = s->StyleSVGReset()->mD;
    167            if (d.IsPath()) {
    168              CreatePathSegments(this, d.AsPath(), aValues,
    169                                 aOptions.mNormalize);
    170            }
    171          })) {
    172    return;
    173  }
    174  CreatePathSegments(this, mD.GetAnimValue().RawData(), aValues,
    175                     aOptions.mNormalize);
    176 }
    177 
    178 void SVGPathElement::SetPathData(const Sequence<SVGPathSegmentInit>& aValues) {
    179  AutoChangePathSegListNotifier notifier(this);
    180  mD.SetBaseValueFromPathSegments(aValues);
    181 }
    182 
    183 //----------------------------------------------------------------------
    184 // SVGElement methods
    185 
    186 /* virtual */
    187 bool SVGPathElement::HasValidDimensions() const {
    188  bool hasPath = false;
    189  auto callback = [&](const ComputedStyle* s) {
    190    const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
    191    hasPath =
    192        styleSVGReset->mD.IsPath() && !styleSVGReset->mD.AsPath()._0.IsEmpty();
    193  };
    194 
    195  SVGGeometryProperty::DoForComputedStyle(this, callback);
    196  // If hasPath is false, we may disable the pref of d property, so we fallback
    197  // to check mD.
    198  return hasPath || !mD.GetAnimValue().IsEmpty();
    199 }
    200 
    201 //----------------------------------------------------------------------
    202 // nsIContent methods
    203 
    204 NS_IMETHODIMP_(bool)
    205 SVGPathElement::IsAttributeMapped(const nsAtom* name) const {
    206  return name == nsGkAtoms::d || SVGPathElementBase::IsAttributeMapped(name);
    207 }
    208 
    209 already_AddRefed<Path> SVGPathElement::GetOrBuildPathForMeasuring() {
    210  RefPtr<Path> path;
    211  bool success = SVGGeometryProperty::DoForComputedStyle(
    212      this, [&path](const ComputedStyle* s) {
    213        const auto& d = s->StyleSVGReset()->mD;
    214        if (d.IsNone()) {
    215          return;
    216        }
    217        path = SVGPathData::BuildPathForMeasuring(d.AsPath()._0.AsSpan(),
    218                                                  s->EffectiveZoom().ToFloat());
    219      });
    220  return success ? path.forget()
    221                 : mD.GetAnimValue().BuildPathForMeasuring(1.0f);
    222 }
    223 
    224 //----------------------------------------------------------------------
    225 // SVGGeometryElement methods
    226 
    227 bool SVGPathElement::AttributeDefinesGeometry(const nsAtom* aName) {
    228  return aName == nsGkAtoms::d || aName == nsGkAtoms::pathLength;
    229 }
    230 
    231 bool SVGPathElement::IsMarkable() { return true; }
    232 
    233 void SVGPathElement::GetMarkPoints(nsTArray<SVGMark>* aMarks) {
    234  auto callback = [aMarks](const ComputedStyle* s) {
    235    const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
    236    if (styleSVGReset->mD.IsPath()) {
    237      Span<const StylePathCommand> path =
    238          styleSVGReset->mD.AsPath()._0.AsSpan();
    239      SVGPathData::GetMarkerPositioningData(path, s->EffectiveZoom().ToFloat(),
    240                                            aMarks);
    241    }
    242  };
    243 
    244  if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
    245    return;
    246  }
    247 
    248  mD.GetAnimValue().GetMarkerPositioningData(1.0f, aMarks);
    249 }
    250 
    251 void SVGPathElement::GetAsSimplePath(SimplePath* aSimplePath) {
    252  aSimplePath->Reset();
    253  auto callback = [&](const ComputedStyle* s) {
    254    const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
    255    if (styleSVGReset->mD.IsPath()) {
    256      auto pathData = styleSVGReset->mD.AsPath()._0.AsSpan();
    257      auto maybeRect = SVGPathToAxisAlignedRect(pathData);
    258      if (maybeRect.isSome()) {
    259        const Rect& r = *maybeRect;
    260        float zoom = s->EffectiveZoom().ToFloat();
    261        aSimplePath->SetRect(r.x * zoom, r.y * zoom, r.width * zoom,
    262                             r.height * zoom);
    263      }
    264    }
    265  };
    266 
    267  SVGGeometryProperty::DoForComputedStyle(this, callback);
    268 }
    269 
    270 already_AddRefed<Path> SVGPathElement::BuildPath(PathBuilder* aBuilder) {
    271  // The Moz2D PathBuilder that our SVGPathData will be using only cares about
    272  // the fill rule. However, in order to fulfill the requirements of the SVG
    273  // spec regarding zero length sub-paths when square line caps are in use,
    274  // SVGPathData needs to know our stroke-linecap style and, if "square", then
    275  // also our stroke width. See the comment for
    276  // ApproximateZeroLengthSubpathSquareCaps for more info.
    277 
    278  auto strokeLineCap = StyleStrokeLinecap::Butt;
    279  Float strokeWidth = 0;
    280  RefPtr<Path> path;
    281 
    282  auto callback = [&](const ComputedStyle* s) {
    283    const nsStyleSVG* styleSVG = s->StyleSVG();
    284    // Note: the path that we return may be used for hit-testing, and SVG
    285    // exposes hit-testing of strokes that are not actually painted. For that
    286    // reason we do not check for eStyleSVGPaintType_None or check the stroke
    287    // opacity here.
    288    if (styleSVG->mStrokeLinecap != StyleStrokeLinecap::Butt) {
    289      strokeLineCap = styleSVG->mStrokeLinecap;
    290      strokeWidth = SVGContentUtils::GetStrokeWidth(this, s, nullptr);
    291    }
    292 
    293    const auto& d = s->StyleSVGReset()->mD;
    294    if (d.IsPath()) {
    295      path = SVGPathData::BuildPath(d.AsPath()._0.AsSpan(), aBuilder,
    296                                    strokeLineCap, strokeWidth, {}, {},
    297                                    s->EffectiveZoom().ToFloat());
    298    }
    299  };
    300 
    301  bool success = SVGGeometryProperty::DoForComputedStyle(this, callback);
    302  if (success) {
    303    return path.forget();
    304  }
    305 
    306  // Fallback to use the d attribute if it exists.
    307  return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth,
    308                                     1.0f);
    309 }
    310 
    311 bool SVGPathElement::GetDistancesFromOriginToEndsOfVisibleSegments(
    312    FallibleTArray<double>* aOutput) {
    313  bool ret = false;
    314  auto callback = [&ret, aOutput](const ComputedStyle* s) {
    315    const auto& d = s->StyleSVGReset()->mD;
    316    ret = d.IsNone() ||
    317          SVGPathData::GetDistancesFromOriginToEndsOfVisibleSegments(
    318              d.AsPath()._0.AsSpan(), aOutput);
    319  };
    320 
    321  if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
    322    return ret;
    323  }
    324 
    325  return mD.GetAnimValue().GetDistancesFromOriginToEndsOfVisibleSegments(
    326      aOutput);
    327 }
    328 
    329 static bool PathIsClosed(Span<const StylePathCommand> aPath) {
    330  return !aPath.IsEmpty() && aPath.rbegin()->IsClose();
    331 }
    332 
    333 // Offset paths (including references to SVG Paths) are closed loops only if the
    334 // final command in the path list is a closepath command ("z" or "Z"), otherwise
    335 // they are unclosed intervals.
    336 // https://drafts.fxtf.org/motion/#path-distance
    337 bool SVGPathElement::IsClosedLoop() const {
    338  bool isClosed = false;
    339 
    340  auto callback = [&](const ComputedStyle* s) {
    341    const nsStyleSVGReset* styleSVGReset = s->StyleSVGReset();
    342    if (styleSVGReset->mD.IsPath()) {
    343      isClosed = PathIsClosed(styleSVGReset->mD.AsPath()._0.AsSpan());
    344    }
    345  };
    346 
    347  if (SVGGeometryProperty::DoForComputedStyle(this, callback)) {
    348    return isClosed;
    349  }
    350 
    351  return PathIsClosed(mD.GetAnimValue().AsSpan());
    352 }
    353 
    354 /* static */
    355 bool SVGPathElement::IsDPropertyChangedViaCSS(const ComputedStyle& aNewStyle,
    356                                              const ComputedStyle& aOldStyle) {
    357  return aNewStyle.StyleSVGReset()->mD != aOldStyle.StyleSVGReset()->mD;
    358 }
    359 
    360 }  // namespace mozilla::dom